Files
LangBot/src/langbot/pkg/core/boot.py
T

120 lines
4.2 KiB
Python

from __future__ import annotations
import traceback
import asyncio
import contextlib
import os
from . import app
from . import stage
from ..telemetry import diagnostics
from ..utils import constants, importutil
# Import startup stage implementation to register
from . import stages
importutil.import_modules_in_pkg(stages)
stage_order = [
'LoadConfigStage',
'GenKeysStage',
'SetupLoggerStage',
'BuildAppStage',
'ShowNotesStage',
]
async def make_app(loop: asyncio.AbstractEventLoop) -> app.Application:
# Determine if it is debug mode
if 'DEBUG' in os.environ and os.environ['DEBUG'] in ['true', '1']:
constants.debug_mode = True
ap = app.Application()
ap.event_loop = loop
try:
# Execute startup stage
for stage_name in stage_order:
stage_cls = stage.preregistered_stages[stage_name]
stage_inst = stage_cls()
if stage_name == 'GenKeysStage':
# Optional diagnostics must not make startup depend on package
# metadata, session markers, or its background transport.
ap.diagnostics = None
space_config = ap.instance_config.data.get('space', {})
if not space_config.get('disable_telemetry', False) and not space_config.get(
'disable_beta_diagnostics', False
):
manager = None
try:
manager = diagnostics.DiagnosticsManager(ap, marker_path='data/labels/beta_diagnostics_session')
await manager.start_session()
manager.start()
ap.diagnostics = manager
except BaseException as exc:
if manager is not None:
# Cleanup faults cannot replace the startup fault.
try:
await manager.shutdown(drain_timeout=0)
except asyncio.CancelledError:
if not isinstance(exc, asyncio.CancelledError):
raise
except Exception:
pass
if not isinstance(exc, Exception):
raise
await diagnostics.observe('lifecycle', 'startup.' + stage_name, source='startup', ap=ap)(stage_inst.run)(ap)
await ap.initialize()
except BaseException:
# ``main()`` cannot clean up a partially built application because
# ``make_app()`` has not returned it yet. Release managers, pools and
# child processes that earlier startup stages already attached.
with contextlib.suppress(BaseException):
await ap.shutdown()
raise
return ap
async def main(loop: asyncio.AbstractEventLoop):
app_inst: app.Application | None = None
runtime_loop = asyncio.get_running_loop()
shutdown_requested = asyncio.Event()
run_task: asyncio.Task | None = None
try:
import signal
def signal_handler(sig, frame):
print('[Signal] Program exit.')
runtime_loop.call_soon_threadsafe(shutdown_requested.set)
signal.signal(signal.SIGINT, signal_handler)
if hasattr(signal, 'SIGTERM'):
signal.signal(signal.SIGTERM, signal_handler)
app_inst = await make_app(loop)
if app_inst is None:
return
run_task = asyncio.create_task(app_inst.run())
shutdown_task = asyncio.create_task(shutdown_requested.wait())
done, pending = await asyncio.wait((run_task, shutdown_task), return_when=asyncio.FIRST_COMPLETED)
if shutdown_task in done:
await app_inst.shutdown()
if not run_task.done():
run_task.cancel()
for task in pending:
task.cancel()
results = await asyncio.gather(run_task, shutdown_task, return_exceptions=True)
run_result = results[0]
if isinstance(run_result, BaseException) and not isinstance(run_result, asyncio.CancelledError):
raise run_result
except Exception:
traceback.print_exc()
finally:
if app_inst is not None:
await app_inst.shutdown()