1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
|
from __future__ import annotations
import asyncio
import logging
from asyncio import Queue
from typing import Any, Union
from uvicorn import Config
from uvicorn._types import (
LifespanScope,
LifespanShutdownCompleteEvent,
LifespanShutdownEvent,
LifespanShutdownFailedEvent,
LifespanStartupCompleteEvent,
LifespanStartupEvent,
LifespanStartupFailedEvent,
)
LifespanReceiveMessage = Union[LifespanStartupEvent, LifespanShutdownEvent]
LifespanSendMessage = Union[
LifespanStartupFailedEvent,
LifespanShutdownFailedEvent,
LifespanStartupCompleteEvent,
LifespanShutdownCompleteEvent,
]
STATE_TRANSITION_ERROR = "Got invalid state transition on lifespan protocol."
class LifespanOn:
def __init__(self, config: Config) -> None:
if not config.loaded:
config.load()
self.config = config
self.logger = logging.getLogger("uvicorn.error")
self.startup_event = asyncio.Event()
self.shutdown_event = asyncio.Event()
self.receive_queue: Queue[LifespanReceiveMessage] = asyncio.Queue()
self.error_occured = False
self.startup_failed = False
self.shutdown_failed = False
self.should_exit = False
self.state: dict[str, Any] = {}
async def startup(self) -> None:
self.logger.info("Waiting for application startup.")
loop = asyncio.get_event_loop()
main_lifespan_task = loop.create_task(self.main()) # noqa: F841
# Keep a hard reference to prevent garbage collection
# See https://github.com/encode/uvicorn/pull/972
startup_event: LifespanStartupEvent = {"type": "lifespan.startup"}
await self.receive_queue.put(startup_event)
await self.startup_event.wait()
if self.startup_failed or (self.error_occured and self.config.lifespan == "on"):
self.logger.error("Application startup failed. Exiting.")
self.should_exit = True
else:
self.logger.info("Application startup complete.")
async def shutdown(self) -> None:
if self.error_occured:
return
self.logger.info("Waiting for application shutdown.")
shutdown_event: LifespanShutdownEvent = {"type": "lifespan.shutdown"}
await self.receive_queue.put(shutdown_event)
await self.shutdown_event.wait()
if self.shutdown_failed or (self.error_occured and self.config.lifespan == "on"):
self.logger.error("Application shutdown failed. Exiting.")
self.should_exit = True
else:
self.logger.info("Application shutdown complete.")
async def main(self) -> None:
try:
app = self.config.loaded_app
scope: LifespanScope = {
"type": "lifespan",
"asgi": {"version": self.config.asgi_version, "spec_version": "2.0"},
"state": self.state,
}
await app(scope, self.receive, self.send)
except BaseException as exc:
self.asgi = None
self.error_occured = True
if self.startup_failed or self.shutdown_failed:
return
if self.config.lifespan == "auto":
msg = "ASGI 'lifespan' protocol appears unsupported."
self.logger.info(msg)
else:
msg = "Exception in 'lifespan' protocol\n"
self.logger.error(msg, exc_info=exc)
finally:
self.startup_event.set()
self.shutdown_event.set()
async def send(self, message: LifespanSendMessage) -> None:
assert message["type"] in (
"lifespan.startup.complete",
"lifespan.startup.failed",
"lifespan.shutdown.complete",
"lifespan.shutdown.failed",
)
if message["type"] == "lifespan.startup.complete":
assert not self.startup_event.is_set(), STATE_TRANSITION_ERROR
assert not self.shutdown_event.is_set(), STATE_TRANSITION_ERROR
self.startup_event.set()
elif message["type"] == "lifespan.startup.failed":
assert not self.startup_event.is_set(), STATE_TRANSITION_ERROR
assert not self.shutdown_event.is_set(), STATE_TRANSITION_ERROR
self.startup_event.set()
self.startup_failed = True
if message.get("message"):
self.logger.error(message["message"])
elif message["type"] == "lifespan.shutdown.complete":
assert self.startup_event.is_set(), STATE_TRANSITION_ERROR
assert not self.shutdown_event.is_set(), STATE_TRANSITION_ERROR
self.shutdown_event.set()
elif message["type"] == "lifespan.shutdown.failed":
assert self.startup_event.is_set(), STATE_TRANSITION_ERROR
assert not self.shutdown_event.is_set(), STATE_TRANSITION_ERROR
self.shutdown_event.set()
self.shutdown_failed = True
if message.get("message"):
self.logger.error(message["message"])
async def receive(self) -> LifespanReceiveMessage:
return await self.receive_queue.get()
|