on.py 5.1 KB

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