Skip to content

Commit ae60a73

Browse files
committed
Handle race where process exits immediately before killing it
See also 5b1f3dc Closes Supervisor#1387 Closes Supervisor#975 Closes Supervisor#688 Closes Supervisor#536
1 parent 36c9f82 commit ae60a73

File tree

3 files changed

+41
-2
lines changed

3 files changed

+41
-2
lines changed

CHANGES.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
4.2.2.dev0 (Next Release)
22
-------------------------
33

4+
- Fixed a bug where ``supervisord`` could crash if a subprocess exited
5+
immediately before trying to kill it.
6+
47
- Fixed a bug where the ``stdout_syslog`` and ``stderr_syslog`` options
58
of a ``[program:x]`` section could not be used unless file logging for
69
the same program had also been configured. The file and syslog options

supervisor/process.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,8 @@ def give_up(self):
403403
self.change_state(ProcessStates.FATAL)
404404

405405
def kill(self, sig):
406-
"""Send a signal to the subprocess. This may or may not kill it.
406+
"""Send a signal to the subprocess with the intention to kill
407+
it (to make it exit). This may or may not actually kill it.
407408
408409
Return None if the signal was sent, or an error message string
409410
if an error occurred or if the subprocess is not running.
@@ -463,7 +464,17 @@ def kill(self, sig):
463464
pid = -self.pid
464465

465466
try:
466-
options.kill(pid, sig)
467+
try:
468+
options.kill(pid, sig)
469+
except OSError as exc:
470+
if exc.errno == errno.ESRCH:
471+
msg = ("unable to signal %s (pid %s), it probably just exited "
472+
"on its own: %s" % (processname, self.pid, str(exc)))
473+
options.logger.debug(msg)
474+
# we could change the state here but we intentionally do
475+
# not. we will do it during normal SIGCHLD processing.
476+
return None
477+
raise
467478
except:
468479
tb = traceback.format_exc()
469480
msg = 'unknown problem killing %s (%s):%s' % (processname,

supervisor/tests/test_process.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -884,6 +884,31 @@ def test_kill_from_running_error(self):
884884
self.assertEqual(event1.__class__, events.ProcessStateStoppingEvent)
885885
self.assertEqual(event2.__class__, events.ProcessStateUnknownEvent)
886886

887+
def test_kill_from_running_error_ESRCH(self):
888+
options = DummyOptions()
889+
config = DummyPConfig(options, 'test', '/test')
890+
options.kill_exception = OSError(errno.ESRCH,
891+
os.strerror(errno.ESRCH))
892+
instance = self._makeOne(config)
893+
L = []
894+
from supervisor.states import ProcessStates
895+
from supervisor import events
896+
events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
897+
instance.pid = 11
898+
instance.state = ProcessStates.RUNNING
899+
instance.kill(signal.SIGTERM)
900+
self.assertEqual(options.logger.data[0], 'killing test (pid 11) with '
901+
'signal SIGTERM')
902+
self.assertEqual(options.logger.data[1], 'unable to signal test (pid 11), '
903+
'it probably just exited on its own: %s' %
904+
str(options.kill_exception))
905+
self.assertTrue(instance.killing)
906+
self.assertEqual(instance.pid, 11) # unchanged
907+
self.assertEqual(instance.state, ProcessStates.STOPPING)
908+
self.assertEqual(len(L), 1)
909+
event1 = L[0]
910+
self.assertEqual(event1.__class__, events.ProcessStateStoppingEvent)
911+
887912
def test_kill_from_stopping(self):
888913
options = DummyOptions()
889914
config = DummyPConfig(options, 'test', '/test')

0 commit comments

Comments
 (0)