more work

This commit is contained in:
John Lancaster
2024-10-20 21:48:19 +00:00
parent 050fe75e71
commit 502c218c35
2 changed files with 34 additions and 61 deletions

View File

@@ -4,29 +4,26 @@ import logging
import signal
from concurrent.futures import ThreadPoolExecutor
from contextlib import ExitStack, contextmanager
from dataclasses import dataclass, field
from threading import Event, Lock
from time import sleep
from typing import Any, Callable
logger = logging.getLogger()
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
loop: asyncio.AbstractEventLoop
executor: ThreadPoolExecutor
stop_event: Event
shutdown_lock: Lock
_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
def __init__(self):
self._stack = ExitStack()
self.stop_event = Event()
self.shutdown_lock = Lock()
loop: asyncio.AbstractEventLoop = field(init=False)
executor: ThreadPoolExecutor = field(init=False)
def __enter__(self):
self.loop = self._stack.enter_context(self.asyncio_context())
@@ -44,6 +41,12 @@ class AppDaemonRunContext:
self._stack.__exit__(exc_type, exc_value, traceback)
logger.debug("Exited context")
def get_running_tasks(self, exclude_current: bool = True) -> list[asyncio.Task]:
return [
t for t in asyncio.all_tasks(self.loop)
if exclude_current and t is not asyncio.current_task()
]
async def shutdown(self, signal):
"""Cleanup tasks tied to the service's shutdown.
@@ -54,14 +57,19 @@ class AppDaemonRunContext:
logger.debug('Setting stop event')
self.stop_event.set()
tasks = self.get_running_tasks()
graceful = (
asyncio.wait_for(t, timeout=2.0)
for t in asyncio.all_tasks()
if t is not asyncio.current_task()
asyncio.wait_for(t, timeout=self.shutdown_grace_period)
for t in tasks
)
logger.debug('Allowing graceful shutdown from stop event')
logger.debug(f'Allowing graceful shutdown from stop event for {self.shutdown_grace_period}s')
await asyncio.gather(*graceful, return_exceptions=True)
for task in tasks:
if task.cancelled():
logger.warning(f'Cancelled {task.get_coro().__qualname__}')
logger.debug("Stopping event loop in context shutdown")
self.loop.stop()
else:
@@ -104,40 +112,3 @@ class AppDaemonRunContext:
return await asyncio.wait_for(coro, timeout)
except asyncio.TimeoutError:
print('Timed out')
if __name__ == "__main__":
import logging
import random
from uuid import uuid4
logging.basicConfig(level="DEBUG", format="{levelname:<8} {message}", style="{")
def dummy_function(delay: float):
id_ = uuid4().hex[:4]
logger.info(f'{id_} sleeping for {delay:.1f}s')
sleep(delay)
logger.info(f'{id_} Done')
async def async_dummy_function(delay: float):
id_ = uuid4().hex[:4]
logger.info(f'{id_} async sleeping for {delay:.1f}s')
await asyncio.sleep(delay)
logger.info(f'{id_} Done async')
with AppDaemonRunContext() as cm:
for _ in range(3):
logger.info('Submitting random dummy_functions')
cm.executor.submit(dummy_function, random.random() * 10.0)
cm.loop.create_task(async_dummy_function(random.random() * 5.0))
try:
logger.info('Running until complete')
cm.loop.run_until_complete(asyncio.gather(*asyncio.all_tasks(cm.loop)))
except asyncio.CancelledError:
logger.error('Cancelled')
if cm.loop.is_closed():
logger.debug('Loop is closed')
if cm.executor._shutdown:
logger.debug('Executor is shut down')