Skip to content

Commit

Permalink
Propagate SIGTERM & SIGINT to supervised processes
Browse files Browse the repository at this point in the history
Introduces icky global state, but I can't think of another
way to do this with signals, since those are inherently global.

We exit ourselves after SIGTERM & SIGINT signals have been
propagated. Maybe we should wait for our child processes to
actually finish?
  • Loading branch information
yuvipanda committed Dec 24, 2018
1 parent 161d641 commit 5b2e558
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 0 deletions.
27 changes: 27 additions & 0 deletions simpervisor/atexitasync.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""
asyncio aware version of atexit.
Handles SIGINT and SIGTERM, unlike atexit
"""
import signal
import asyncio
import sys

_handlers = []

signal_handler_set = False

def add_handler(handler):
if not signal_handler_set:
loop = asyncio.get_event_loop()
loop.add_signal_handler(signal.SIGINT, _handle_signal, signal.SIGINT)
loop.add_signal_handler(signal.SIGTERM, _handle_signal, signal.SIGTERM)
_handlers.append(handler)

def remove_handler(handler):
_handlers.remove(handler)

def _handle_signal(signum):
for handler in _handlers:
handler(signum)
sys.exit(0)
8 changes: 8 additions & 0 deletions simpervisor/process.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
"""
Simple asynchronous process supervisor
"""
import signal
import asyncio
import logging
from simpervisor import atexitasync


class SupervisedProcess:
Expand Down Expand Up @@ -35,6 +37,10 @@ def _debug_log(self, action, message, extras=None):
base_extras.update(extras)
self.log.debug(message, extra=base_extras)

def _handle_signal(self, signal):
self.proc.send_signal(signal)
self._debug_log('signal', f'Propagated signal {signal} to {self.name}')

async def start(self):
"""
Start the process
Expand All @@ -54,9 +60,11 @@ async def start(self):

# Spin off a coroutine to watch, reap & restart process if needed
asyncio.ensure_future(self._restart_process_if_needed())
atexitasync.add_handler(self._handle_signal)

async def _restart_process_if_needed(self):
retcode = await self.proc.wait()
atexitasync.remove_handler(self._handle_signal)
self._debug_log(
'exited', f'{self.name} exited with code {retcode}',
{'code': retcode}
Expand Down

0 comments on commit 5b2e558

Please sign in to comment.