目录
ASGI Lifespan 协议概述
协议规范详解
Uvicorn 实现分析
Starlette 集成机制
实际应用示例
最佳实践
ASGI Lifespan 协议概述 什么是 Lifespan 协议? ASGI Lifespan 协议是 ASGI 规范的一个子协议,用于管理应用程序的启动和关闭生命周期。它允许应用程序在服务器启动时执行初始化操作(如连接数据库),在服务器关闭时执行清理操作(如关闭连接)。
核心设计原则 根据 ASGI 官方文档 :
单一连接模式 :整个应用生命周期使用一个持久的连接
Lifespans should be executed once per event loop that will be processing requests
来源:ASGI Lifespan Protocol
事件驱动 :通过特定事件类型触发启动和关闭逻辑
状态管理 :支持在应用状态中持久化数据
The scope["state"] namespace provides a place to store these sorts of things. The server will ensure that a shallow copy of the namespace is passed into each subsequent request/response call into the application.
来源:ASGI Lifespan State
异常安全 :提供完整的错误处理机制
If an exception is raised when calling the application callable with a lifespan.startup message or a scope with type lifespan, the server must continue but not send any lifespan events.
来源:ASGI Lifespan Scope
协议规范详解 Scope 结构 根据 ASGI Lifespan Scope 规范 :
1 2 3 4 5 6 7 8 9 { "type" : "lifespan" , "asgi" : { "version" : "3.0" , "spec_version" : "2.0" }, "state" : {} }
The lifespan scope exists for the duration of the event loop. The scope information passed in scope contains basic metadata:
type (Unicode string ) – "lifespan"
asgi["version"] (Unicode string ) – The version of the ASGI spec
asgi["spec_version"] (Unicode string ) – The version of this spec being used. Optional; if missing defaults to "1.0"
state Optional(dict[Unicode string, Any] ) – An empty namespace where the application can persist state to be used when handling subsequent requests
来源:ASGI Lifespan Scope
事件类型 根据 ASGI Lifespan 事件规范 :
接收事件(receive) 1 2 3 4 5 6 7 8 9 { "type" : "lifespan.startup" } { "type" : "lifespan.shutdown" }
Startup - receive event : Sent to the application when the server is ready to startup and receive connections, but before it has started to do so.
Shutdown - receive event : Sent to the application when the server has stopped accepting connections and closed all active connections.
来源:ASGI Lifespan Events
发送事件(send) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 { "type" : "lifespan.startup.complete" } { "type" : "lifespan.startup.failed" , "message" : "错误信息" } { "type" : "lifespan.shutdown.complete" } { "type" : "lifespan.shutdown.failed" , "message" : "错误信息" }
Startup Complete - send event : Sent by the application when it has completed its startup. A server must wait for this message before it starts processing connections.
Startup Failed - send event : Sent by the application when it has failed to complete its startup. If a server sees this it should log/print the message provided and then exit.
Shutdown Complete - send event : Sent by the application when it has completed its cleanup. A server must wait for this message before terminating.
Shutdown Failed - send event : Sent by the application when it has failed to complete its cleanup. If a server sees this it should log/print the message provided and then terminate.
来源:ASGI Lifespan Events
标准应用实现 根据 ASGI Lifespan 协议示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 async def app (scope, receive, send ): if scope['type' ] == 'lifespan' : while True : message = await receive() if message['type' ] == 'lifespan.startup' : await send({'type' : 'lifespan.startup.complete' }) elif message['type' ] == 'lifespan.shutdown' : await send({'type' : 'lifespan.shutdown.complete' }) return else : pass
A possible implementation of this protocol is given below:
来源:ASGI Lifespan Protocol
Uvicorn 实现分析 基于 Uvicorn 源码 的分析:
核心实现类 LifespanOn 类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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 ] = {}
启动流程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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()) 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." )
关闭流程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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." )
核心通信机制 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 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 ()
事件处理 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 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()
配置选择机制 1 2 3 4 5 6 LIFESPAN: dict [str , str ] = { "auto" : "uvicorn.lifespan.on:LifespanOn" , "on" : "uvicorn.lifespan.on:LifespanOn" , "off" : "uvicorn.lifespan.off:LifespanOff" , }
Starlette 集成机制 基于 Starlette 源码 的分析:
事件路由 1 2 3 4 5 6 async def __call__ (self, scope: Scope, receive: Receive, send: Send ) -> None : scope["app" ] = self if self.middleware_stack is None : self.middleware_stack = self.build_middleware_stack() await self.middleware_stack(scope, receive, send)
Lifespan 处理 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 async def lifespan (self, scope: Scope, receive: Receive, send: Send ) -> None : """ Handle ASGI lifespan messages, which allows us to manage application startup and shutdown events. """ started = False app: Any = scope.get("app" ) await receive() try : async with self.lifespan_context(app) as maybe_state: if maybe_state is not None : if "state" not in scope: raise RuntimeError('The server does not support "state" in the lifespan scope.' ) scope["state" ].update(maybe_state) await send({"type" : "lifespan.startup.complete" }) started = True await receive() except BaseException: exc_text = traceback.format_exc() if started: await send({"type" : "lifespan.shutdown.failed" , "message" : exc_text}) else : await send({"type" : "lifespan.startup.failed" , "message" : exc_text}) raise else : await send({"type" : "lifespan.shutdown.complete" })
关键设计特点 根据 Starlette Lifespan 实现 :
一次性处理 :lifespan 方法执行一次,处理整个应用生命周期
阻塞等待 :通过两次 await receive() 分别处理 startup 和 shutdown 事件
异常安全 :完整的错误处理和状态管理
状态传递 :支持将 lifespan 结果传递给应用状态
The lifespan protocol runs as a sibling task alongside your main application, allowing both to execute concurrently.
来源:Starlette Lifespan 文档
实际应用示例 基础应用 基于 FastAPI Lifespan 指南 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from contextlib import asynccontextmanagerfrom fastapi import FastAPIdb_connection = None @asynccontextmanager async def lifespan (app: FastAPI ): global db_connection print ("Connecting to database..." ) db_connection = await connect_database() yield {"database" : db_connection} print ("Closing database connection..." ) await db_connection.close() app = FastAPI(lifespan=lifespan)
You can define lifespan events (startup and shutdown) with FastAPI using a lifespan parameter in the FastAPI app.
来源:FastAPI Lifespan 指南
复杂资源管理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from contextlib import AsyncExitStack@asynccontextmanager async def complex_lifespan (app: FastAPI ): async with AsyncExitStack() as stack: db = await stack.enter_async_context(connect_database()) cache = await stack.enter_async_context(connect_redis()) session_pool = await stack.enter_async_context(create_session_pool()) def cleanup_logs (): cleanup_log_files() stack.callback(cleanup_logs) yield { "database" : db, "cache" : cache, "sessions" : session_pool }
FastMCP 集成示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from fastmcp import FastMCPmcp = FastMCP("MyServer" ) @mcp.lifespan async def my_lifespan (server ): print ("Initializing MCP server..." ) await server.setup_tools() await server.connect_to_database() yield {"server" : server} print ("Shutting down MCP server..." ) await server.cleanup_tools() await server.close_database() app = mcp.create_app()
最佳实践 1. 资源管理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @asynccontextmanager async def proper_lifespan (app: FastAPI ): resources = {} try : resources["db" ] = await connect_database() resources["cache" ] = await connect_redis() resources["http_client" ] = aiohttp.ClientSession() yield resources finally : if "http_client" in resources: await resources["http_client" ].close() if "cache" in resources: await resources["cache" ].close() if "db" in resources: await resources["db" ].close()
2. 错误处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @asynccontextmanager async def robust_lifespan (app: FastAPI ): try : db = await connect_database() yield {"database" : db} except Exception as e: logger.error(f"Failed to initialize database: {e} " ) yield {} finally : try : if 'db' in locals (): await db.close() except Exception as e: logger.error(f"Failed to close database: {e} " )
3. 状态管理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @asynccontextmanager async def stateful_lifespan (app: FastAPI ): app_state = { "startup_time" : time.time(), "version" : "1.0.0" , "environment" : os.getenv("ENV" , "development" ) } db = await connect_database() app_state["database" ] = db app.state.config = app_state yield app_state await db.close()
4. 性能监控 1 2 3 4 5 6 7 8 9 10 11 12 13 @asynccontextmanager async def monitored_lifespan (app: FastAPI ): start_time = time.time() try : metrics = await setup_metrics_collection() yield {"metrics" : metrics} finally : runtime = time.time() - start_time logger.info(f"Application ran for {runtime:.2 f} seconds" )
总结 ASGI Lifespan 协议是现代异步 Python Web 应用的核心组件,它提供了:
标准化的生命周期管理 :统一的启动和关闭流程
异常安全保证 :确保资源正确清理
状态管理 :支持应用状态持久化
协议兼容性 :与所有 ASGI 服务器兼容
The lifespan messages allow for an application to initialise and shutdown in the context of a running event loop. An example of this would be creating a connection pool and subsequently closing the connection pool to release the connections.
来源:ASGI Lifespan Protocol
通过合理使用 Lifespan 协议,可以构建更加健壮和可维护的异步 Web 应用。
参考资源