Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Making twisted.runner.procmon.ProcessMonitor wait for children to exit
#!/bin/sh
echo Executed: "$0" "$@"
for t in `seq 15`; do
trap 'echo '"$0"' '"$@"' trapped '"$t"' >> traps.txt' $t
done
echo "$0" "$@": sleeping
for f in `seq 10`; do
sleep 1
echo "$0" "$@": "$f"
done
echo "$0" "$@": done sleeping
"""
When you issue a SIGINT or SIGTERM to
twisted.runner.procmon.ProcessMonitor, its child processes get signalled
but it immediately disconnects from them and exits, even if they haven't
exited. This causes problems when we're trying to monitor a process that
has critical on-exit cleanup code.
This is a subclass modifying ProcessMonitor in such a way that it will
wait for all child processes to exit before allowing the reactor to
exit.
"""
from twisted.internet import defer
from twisted.runner.procmon import ProcessMonitor
from twisted.application import service
class WaitingProcessMonitor(ProcessMonitor):
def __init__(self, *args):
# we need to track exiting-but-not-exited processes,
# so override __init__ to add that state
ProcessMonitor.__init__(self, *args)
self.exiting = {}
def stopService(self):
# override stopService to return a deferred whose callback will
# fire when all the processes have been cleaned up.
#
# Sadly we must copypasta some of ProcessMonitor.stopService
# since we need to modify its internal behavior; we can't just
# wrap it
# Copied from superclass
service.Service.stopService(self)
# Copied from superclass
for name, delayedCall in self.restart.items():
if delayedCall.active():
delayedCall.cancel()
# Our modification
return defer.gatherResults(
[self.stopProcess(name) for name in self.processes],
consumeErrors=True)
def stopProcess(self, name):
# override stopProcess to return a deferred whose callback will
# fire when the process that we've asked to stop (with SIGTERM)
# actually does.
#
# Sadly, twisted doesn't record which signal it received
# when it calls stop(), so always uses SIGTERM to stop child
# processes.
#
# Be careful when testing at the command line: a CTRL+C sent to
# your shell to generate an easy SIGINT will also be propagated
# to the process group: the monitored children will in that
# situation end up being signalled twice!
ProcessMonitor.stopProcess(self, name)
d = defer.Deferred()
self.exiting[name] = d
return d
def connectionLost(self, name):
# override connectionLost to cooperate with the new stuff we
# added to stopProcess: if we're expecting this process to stop,
# call the callback on the deferred we set up to wait for it.
if name in self.exiting:
self.exiting[name].callback(None)
del self.exiting[name]
ProcessMonitor.connectionLost(self, name)
application = service.Application("Process Monitor")
s = WaitingProcessMonitor()
s.setServiceParent(application)
# _script.sh is a little dummy script that ignores signals and exits
# after 10 seconds. This is meant to sort-of simulate a script that,
# when signalled, does not immediately exit, but takes some time to
# perform some cleanup tasks before doing so. This also lets us verify
# that it is SIGKILL'd if it takes too long.
s.addProcess('foo', ['./_script.sh'])
# vim:set ft=python:
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment