197 lines
6.0 KiB
Python
197 lines
6.0 KiB
Python
"""\
|
|
@file processes.py
|
|
|
|
Copyright (c) 2006-2007, Linden Research, Inc.
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
THE SOFTWARE.
|
|
"""
|
|
|
|
import errno
|
|
import os
|
|
import popen2
|
|
import signal
|
|
import sys
|
|
|
|
from eventlet import coros
|
|
from eventlet import pools
|
|
from eventlet import greenio
|
|
from eventlet import util
|
|
|
|
|
|
class DeadProcess(RuntimeError):
|
|
pass
|
|
|
|
|
|
CHILD_PIDS = []
|
|
|
|
CHILD_EVENTS = {}
|
|
|
|
|
|
def wait_on_children():
|
|
for child_pid in CHILD_PIDS:
|
|
try:
|
|
pid, code = util.__original_waitpid__(child_pid, os.WNOHANG)
|
|
if not pid:
|
|
continue ## Wasn't this one that died
|
|
elif pid == -1:
|
|
print >> sys.stderr, "Got -1! Why didn't python raise?"
|
|
elif pid != child_pid:
|
|
print >> sys.stderr, "pid (%d) != child_pid (%d)" % (pid, child_pid)
|
|
|
|
# Defensively assume we could get a different pid back
|
|
if CHILD_EVENTS.get(pid):
|
|
event = CHILD_EVENTS.pop(pid)
|
|
event.send(code)
|
|
|
|
except OSError, e:
|
|
if e[0] != errno.ECHILD:
|
|
raise e
|
|
elif CHILD_EVENTS.get(child_pid):
|
|
# Already dead; signal, but assume success
|
|
event = CHILD_EVENTS.pop(child_pid)
|
|
event.send(0)
|
|
|
|
|
|
def sig_child(signal, frame):
|
|
from eventlet import api
|
|
api.call_after(0, wait_on_children)
|
|
signal.signal(signal.SIGCHLD, sig_child)
|
|
|
|
|
|
def _add_child_pid(pid):
|
|
"""Add the given integer 'pid' to the list of child
|
|
process ids we are tracking. Return an event object
|
|
that can be used to get the process' exit code.
|
|
"""
|
|
CHILD_PIDS.append(pid)
|
|
event = coros.event()
|
|
CHILD_EVENTS[pid] = event
|
|
return event
|
|
|
|
|
|
class Process(object):
|
|
process_number = 0
|
|
def __init__(self, command, args, dead_callback=lambda:None):
|
|
self.process_number = self.process_number + 1
|
|
Process.process_number = self.process_number
|
|
self.command = command
|
|
self.args = args
|
|
self._dead_callback = dead_callback
|
|
self.run()
|
|
|
|
def run(self):
|
|
self.dead = False
|
|
self.started = False
|
|
self.popen4 = None
|
|
|
|
## We use popen4 so that read() will read from either stdout or stderr
|
|
self.popen4 = popen2.Popen4([self.command] + self.args)
|
|
self.event = _add_child_pid(self.popen4.pid)
|
|
child_stdout_stderr = self.popen4.fromchild
|
|
child_stdin = self.popen4.tochild
|
|
greenio.set_nonblocking(child_stdout_stderr)
|
|
greenio.set_nonblocking(child_stdin)
|
|
self.child_stdout_stderr = greenio.GreenPipe(child_stdout_stderr)
|
|
self.child_stdout_stderr.newlines = '\n' # the default is \r\n, which aren't sent over pipes
|
|
self.child_stdin = greenio.GreenPipe(child_stdin)
|
|
self.child_stdin.newlines = '\n'
|
|
|
|
self.sendall = self.child_stdin.write
|
|
self.send = self.child_stdin.write
|
|
self.recv = self.child_stdout_stderr.read
|
|
self.readline = self.child_stdout_stderr.readline
|
|
|
|
def dead_callback(self):
|
|
self.dead = True
|
|
if self._dead_callback:
|
|
self._dead_callback()
|
|
|
|
def makefile(self, mode, *arg):
|
|
if mode.startswith('r'):
|
|
return self.child_stdout_stderr
|
|
if mode.startswith('w'):
|
|
return self.child_stdin
|
|
raise RuntimeError("Unknown mode", mode)
|
|
|
|
def read(self, amount=None):
|
|
result = self.child_stdout_stderr.read(amount)
|
|
if result == '':
|
|
# This process is dead.
|
|
self.dead_callback()
|
|
raise DeadProcess
|
|
return result
|
|
|
|
def write(self, stuff):
|
|
written = 0
|
|
try:
|
|
written = self.child_stdin.write(stuff)
|
|
self.child_stdin.flush()
|
|
except ValueError, e:
|
|
## File was closed
|
|
assert str(e) == 'I/O operation on closed file'
|
|
if written == 0:
|
|
self.dead_callback()
|
|
raise DeadProcess
|
|
|
|
def flush(self):
|
|
self.child_stdin.flush()
|
|
|
|
def close(self):
|
|
self.child_stdout_stderr.close()
|
|
self.child_stdin.close()
|
|
self.dead_callback()
|
|
|
|
def close_stdin(self):
|
|
self.child_stdin.close()
|
|
|
|
def kill(self, sig=None):
|
|
if sig == None:
|
|
sig = signal.SIGTERM
|
|
os.kill(self.popen4.pid, sig)
|
|
|
|
def getpid(self):
|
|
return self.popen4.pid
|
|
|
|
def wait(self):
|
|
return self.event.wait()
|
|
|
|
|
|
class ProcessPool(pools.Pool):
|
|
def __init__(self, command, args=None, min_size=0, max_size=4):
|
|
"""@param command the command to run
|
|
"""
|
|
self.command = command
|
|
if args is None:
|
|
args = []
|
|
self.args = args
|
|
pools.Pool.__init__(self, min_size, max_size)
|
|
|
|
def create(self):
|
|
"""Generate a process
|
|
"""
|
|
def dead_callback():
|
|
self.current_size -= 1
|
|
return Process(self.command, self.args, dead_callback)
|
|
|
|
def put(self, item):
|
|
if not item.dead:
|
|
if item.popen4.poll() != -1:
|
|
item.dead_callback()
|
|
else:
|
|
pools.Pool.put(self, item)
|