Skip to content

Commit f9403a2

Browse files
authored
docs: add lifespan page (#2656)
1 parent 374bb67 commit f9403a2

File tree

3 files changed

+127
-1
lines changed

3 files changed

+127
-1
lines changed

docs/concepts/lifespan.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
Since Uvicorn is an ASGI server, it supports the
2+
[ASGI lifespan protocol](https://asgi.readthedocs.io/en/latest/specs/lifespan.html).
3+
This allows you to run **startup** and **shutdown** events for your application.
4+
5+
The lifespan protocol is useful for initializing resources that need to be available throughout
6+
the lifetime of the application, such as database connections, caches, or other services.
7+
8+
Keep in mind that the lifespan is executed **only once per application instance**. If you have
9+
multiple workers, each worker will execute the lifespan independently.
10+
11+
## Lifespan Architecture
12+
13+
The lifespan protocol runs as a sibling task alongside your main application, allowing both to execute concurrently.
14+
15+
Let's see how Uvicorn handles the lifespan and main application tasks:
16+
17+
```mermaid
18+
sequenceDiagram
19+
participant Server as Uvicorn Server
20+
participant LifespanTask as Lifespan Task
21+
participant AppTask as Application Task
22+
participant UserApp as User Application
23+
24+
Note over Server: ✅ Server starts
25+
26+
Server->>+LifespanTask: spawn_task(lifespan_handler)
27+
28+
LifespanTask->>UserApp: {"type": "lifespan.startup"}
29+
30+
Note over UserApp: Initialize databases, caches, etc.
31+
32+
UserApp-->>LifespanTask: {"type": "lifespan.startup.complete"}
33+
LifespanTask->>Server: ✅ Startup complete
34+
35+
Server->>+AppTask: spawn_task(application_handler)
36+
Note over AppTask: ✅ Ready for requests
37+
38+
rect rgb(240, 248, 255)
39+
Note over LifespanTask, AppTask: Both tasks running concurrently
40+
41+
par Lifespan maintains state
42+
LifespanTask->>LifespanTask: Keep lifespan connection alive
43+
and Application serves requests
44+
AppTask->>UserApp: HTTP/WebSocket requests
45+
UserApp-->>AppTask: Responses
46+
end
47+
end
48+
49+
Note over Server: Shutdown signal received
50+
51+
Server->>AppTask: Stop accepting new connections
52+
AppTask->>AppTask: Complete pending requests
53+
54+
LifespanTask->>UserApp: {"type": "lifespan.shutdown"}
55+
56+
Note over UserApp: Cleanup databases, caches, etc.
57+
58+
UserApp-->>LifespanTask: {"type": "lifespan.shutdown.complete"}
59+
60+
LifespanTask->>-Server: Lifespan task complete
61+
AppTask->>-Server: Application task complete
62+
63+
Note over Server: ✅ Server stopped
64+
```
65+
66+
Having the lifespan task run as a sibling task is a deliberate design choice. It could have been implemented as a parent task that spawns the
67+
application task. This decision has the implication that if you create a [`ContextVar`][contextvars.ContextVar]
68+
in the lifespan task, it will not be available in the application task.
69+
70+
## Usage
71+
72+
Let's see an example of a minimal (but complete) ASGI application that implements the lifespan protocol:
73+
74+
```python title="ASGI application with lifespan" hl_lines="3-11"
75+
async def app(scope, receive, send):
76+
if scope['type'] == 'lifespan':
77+
while True:
78+
message = await receive()
79+
if message['type'] == 'lifespan.startup':
80+
print("Application is starting up...")
81+
await send({'type': 'lifespan.startup.complete'})
82+
elif message['type'] == 'lifespan.shutdown':
83+
print("Application is shutting down...")
84+
await send({'type': 'lifespan.shutdown.complete'})
85+
return
86+
elif scope['type'] == 'http':
87+
await send({
88+
'type': 'http.response.start',
89+
'status': 200,
90+
'headers': [(b'content-type', b'text/plain')],
91+
})
92+
await send({'type': 'http.response.body', 'body': b'Hello, World!'})
93+
else:
94+
raise RuntimeError("This server doesn't support WebSocket.")
95+
```
96+
97+
You can run the above application with `uvicorn main:app`. Then you'll see the print statements when the
98+
application starts. You can also try to send some HTTP requests to it, and it will respond with "Hello, World!".
99+
And if you stop the server (`CTRL + C`), it will print `"Application is shutting down..."`.
100+
101+
## Disabling Lifespan
102+
103+
If you want to disable the lifespan protocol, you can do so by setting the `lifespan` option to `off` when running Uvicorn:
104+
105+
```bash
106+
uvicorn main:app --lifespan off
107+
```
108+
109+
By default, Uvicorn will automatically enable the lifespan protocol if the application supports it.

mkdocs.yml

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,10 @@ validation:
4646
unrecognized_links: warn
4747

4848
nav:
49-
- Introduction: index.md
49+
- Welcome: index.md
5050
- Settings: settings.md
51+
- Concepts:
52+
- Lifespan: concepts/lifespan.md
5153
- Deployment:
5254
- Deployment: deployment/index.md
5355
- Docker: deployment/docker.md
@@ -77,6 +79,20 @@ markdown_extensions:
7779
emoji_generator: !!python/name:material.extensions.emoji.to_svg
7880
- pymdownx.tasklist:
7981
custom_checkbox: true
82+
- pymdownx.extra:
83+
pymdownx.superfences:
84+
custom_fences:
85+
- name: mermaid
86+
class: mermaid
87+
format: !!python/name:pymdownx.superfences.fence_code_format
88+
89+
plugins:
90+
- search
91+
- mkdocstrings:
92+
handlers:
93+
python:
94+
import:
95+
- url: https://docs.python.org/3/objects.inv
8096

8197
plugins:
8298
- search

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,5 @@ httpx==0.28.1
3030
# Documentation
3131
mkdocs==1.6.1
3232
mkdocs-material==9.6.13
33+
mkdocstrings-python==1.16.12
3334
mkdocs-llmstxt==0.2.0

0 commit comments

Comments
 (0)