context manager work for startup/shutdown

This commit is contained in:
John Lancaster
2024-10-21 02:41:19 +00:00
parent 502c218c35
commit 0abbb9e546
3 changed files with 171 additions and 96 deletions

View File

@@ -5,41 +5,38 @@ import signal
from concurrent.futures import ThreadPoolExecutor
from contextlib import ExitStack, contextmanager
from dataclasses import dataclass, field
from threading import Event, Lock
from threading import Event
from typing import Any, Callable
logger = logging.getLogger(__name__)
def handler(signum, frame):
print('Signal handler called with signal', signum)
raise OSError("Couldn't open device!")
@dataclass
class AppDaemonRunContext:
_stack: ExitStack = field(default_factory=ExitStack)
stop_event: Event = field(default_factory=Event)
shutdown_lock: Lock = field(default_factory=Lock)
shutdown_grace_period: float = 1.0
shutdown_grace_period: float = 0.75
loop: asyncio.AbstractEventLoop = field(init=False)
executor: ThreadPoolExecutor = field(init=False)
def __enter__(self):
self.loop = self._stack.enter_context(self.asyncio_context())
logger.debug("Entered asyncio context")
logger.debug("Created event loop")
signals = (signal.SIGHUP, signal.SIGTERM, signal.SIGINT)
for s in signals:
self.loop.add_signal_handler(s, lambda s=s: self.loop.create_task(self.shutdown(s)))
self.executor = self._stack.enter_context(self.thread_context())
logger.debug("Entered threadpool context")
logger.debug("Started thread pool")
return self
def __exit__(self, exc_type, exc_value, traceback):
self._stack.__exit__(exc_type, exc_value, traceback)
logger.debug("Exited context")
logger.debug(f'Closing context from {self.__class__.__name__}')
self._stack.close()
def get_running_tasks(self, exclude_current: bool = True) -> list[asyncio.Task]:
return [
@@ -47,7 +44,7 @@ class AppDaemonRunContext:
if exclude_current and t is not asyncio.current_task()
]
async def shutdown(self, signal):
async def shutdown(self, signal=signal.SIGTERM):
"""Cleanup tasks tied to the service's shutdown.
https://www.roguelynn.com/words/asyncio-graceful-shutdowns/
@@ -68,9 +65,9 @@ class AppDaemonRunContext:
for task in tasks:
if task.cancelled():
logger.warning(f'Cancelled {task.get_coro().__qualname__}')
logger.warning(f'Cancelled {task.get_name()}')
logger.debug("Stopping event loop in context shutdown")
logger.info("Stopping asyncio event loop")
self.loop.stop()
else:
logger.warning('Already started shutdown')
@@ -83,14 +80,14 @@ class AppDaemonRunContext:
finally:
loop.close()
if loop.is_closed():
logger.debug("Closed the event loop.")
logger.debug("Gracefully closed event loop.")
@contextmanager
def thread_context(self):
with ThreadPoolExecutor(max_workers=5) as executor:
yield executor
if executor._shutdown:
logger.debug('Shut down the ThreadPoolExecutor')
logger.debug('Gracefully shut down ThreadPoolExecutor')
async def run_in_executor(
self,