Skip to content

Commit 755eac1

Browse files
committed
Simplify pattern for shutting down servers.
This avoids having to create manually a future. Also, it's robust to receiving multiple times the signal. Fix #1593.
1 parent 277f0f8 commit 755eac1

File tree

18 files changed

+69
-139
lines changed

18 files changed

+69
-139
lines changed

docs/faq/client.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ You can close the connection.
103103
Here's an example that terminates cleanly when it receives SIGTERM on Unix:
104104

105105
.. literalinclude:: ../../example/faq/shutdown_client.py
106-
:emphasize-lines: 11-13
106+
:emphasize-lines: 10-12
107107

108108
How do I disable TLS/SSL certificate verification?
109109
--------------------------------------------------

docs/faq/server.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ Exit the :func:`~serve` context manager.
310310
Here's an example that terminates cleanly when it receives SIGTERM on Unix:
311311

312312
.. literalinclude:: ../../example/faq/shutdown_server.py
313-
:emphasize-lines: 13-16,19
313+
:emphasize-lines: 14-16
314314

315315
How do I stop a server while keeping existing connections open?
316316
---------------------------------------------------------------

docs/howto/haproxy.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Run server processes
1515
Save this app to ``app.py``:
1616

1717
.. literalinclude:: ../../example/deployment/haproxy/app.py
18-
:emphasize-lines: 24
18+
:language: python
1919

2020
Each server process listens on a different port by extracting an incremental
2121
index from an environment variable set by Supervisor.

docs/howto/nginx.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Run server processes
1515
Save this app to ``app.py``:
1616

1717
.. literalinclude:: ../../example/deployment/nginx/app.py
18-
:emphasize-lines: 21,23
18+
:language: python
1919

2020
We'd like nginx to connect to websockets servers via Unix sockets in order to
2121
avoid the overhead of TCP for communicating between processes running in the

docs/intro/tutorial3.rst

+8-12
Original file line numberDiff line numberDiff line change
@@ -120,14 +120,10 @@ that runs for each request. When it returns an HTTP response, websockets sends
120120
that response instead of opening a WebSocket connection. Here, requests to
121121
``/healthz`` return an HTTP 200 status code.
122122

123-
To catch the ``SIGTERM`` signal, ``main()`` creates a :class:`~asyncio.Future`
124-
called ``stop`` and registers a signal handler that sets the result of this
125-
future. The value of the future doesn't matter; it's only for waiting for
126-
``SIGTERM``.
127-
128-
Then, by using :func:`~asyncio.server.serve` as a context manager and exiting
129-
the context when ``stop`` has a result, ``main()`` ensures that the server
130-
closes connections cleanly and exits on ``SIGTERM``.
123+
``main()`` registers a signal handler that closes the server when receiving the
124+
``SIGTERM`` signal. Then, it waits for the server to be closed. Additionally,
125+
using :func:`~asyncio.server.serve` as a context manager ensures that the server
126+
will always be closed cleanly, even if the program crashes.
131127

132128
Deploy the WebSocket server
133129
---------------------------
@@ -157,14 +153,14 @@ Commit and push your changes:
157153
158154
$ git add .
159155
$ git commit -m "Deploy to Koyeb."
160-
[main ac96d65] Deploy to Koyeb.
161-
3 files changed, 18 insertions(+), 2 deletions(-)
156+
[main 4a4b6e9] Deploy to Koyeb.
157+
3 files changed, 15 insertions(+), 2 deletions(-)
162158
create mode 100644 Procfile
163159
create mode 100644 requirements.txt
164160
$ git push
165161
...
166162
To github.com:python-websockets/websockets-tutorial.git
167-
+ 6bd6032...ac96d65 main -> main
163+
+ 6bd6032...4a4b6e9 main -> main
168164
169165
Sign up or log in to Koyeb.
170166

@@ -239,7 +235,7 @@ Commit your changes:
239235
$ git push
240236
...
241237
To github.com:python-websockets/websockets-tutorial.git
242-
+ ac96d65...0903526 main -> main
238+
+ 4a4b6e9...968eaaa main -> main
243239
244240
Deploy the web application
245241
--------------------------

docs/topics/deployment.rst

+1-2
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,7 @@ Here's an example:
101101
:emphasize-lines: 13-16,19
102102

103103
When exiting the context manager, :func:`~asyncio.server.serve` closes all
104-
connections
105-
with code 1001 (going away). As a consequence:
104+
connections with code 1001 (going away). As a consequence:
106105

107106
* If the connection handler is awaiting
108107
:meth:`~asyncio.server.ServerConnection.recv`, it receives a

example/deployment/fly/app.py

+4-12
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,10 @@ def health_check(connection, request):
1818

1919

2020
async def main():
21-
# Set the stop condition when receiving SIGTERM.
22-
loop = asyncio.get_running_loop()
23-
stop = loop.create_future()
24-
loop.add_signal_handler(signal.SIGTERM, stop.set_result, None)
25-
26-
async with serve(
27-
echo,
28-
host="",
29-
port=8080,
30-
process_request=health_check,
31-
):
32-
await stop
21+
async with serve(echo, "", 8080, process_request=health_check) as server:
22+
loop = asyncio.get_running_loop()
23+
loop.add_signal_handler(signal.SIGTERM, server.close)
24+
await server.wait_closed()
3325

3426

3527
if __name__ == "__main__":

example/deployment/haproxy/app.py

+5-11
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,11 @@ async def echo(websocket):
1313

1414

1515
async def main():
16-
# Set the stop condition when receiving SIGTERM.
17-
loop = asyncio.get_running_loop()
18-
stop = loop.create_future()
19-
loop.add_signal_handler(signal.SIGTERM, stop.set_result, None)
20-
21-
async with serve(
22-
echo,
23-
host="localhost",
24-
port=8000 + int(os.environ["SUPERVISOR_PROCESS_NAME"][-2:]),
25-
):
26-
await stop
16+
port = 8000 + int(os.environ["SUPERVISOR_PROCESS_NAME"][-2:])
17+
async with serve(echo, "localhost", port) as server:
18+
loop = asyncio.get_running_loop()
19+
loop.add_signal_handler(signal.SIGTERM, server.close)
20+
await server.wait_closed()
2721

2822

2923
if __name__ == "__main__":

example/deployment/heroku/app.py

+5-11
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,11 @@ async def echo(websocket):
1313

1414

1515
async def main():
16-
# Set the stop condition when receiving SIGTERM.
17-
loop = asyncio.get_running_loop()
18-
stop = loop.create_future()
19-
loop.add_signal_handler(signal.SIGTERM, stop.set_result, None)
20-
21-
async with serve(
22-
echo,
23-
host="",
24-
port=int(os.environ["PORT"]),
25-
):
26-
await stop
16+
port = int(os.environ["PORT"])
17+
async with serve(echo, "localhost", port) as server:
18+
loop = asyncio.get_running_loop()
19+
loop.add_signal_handler(signal.SIGTERM, server.close)
20+
await server.wait_closed()
2721

2822

2923
if __name__ == "__main__":

example/deployment/koyeb/app.py

+5-12
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,11 @@ def health_check(connection, request):
1919

2020

2121
async def main():
22-
# Set the stop condition when receiving SIGINT.
23-
loop = asyncio.get_running_loop()
24-
stop = loop.create_future()
25-
loop.add_signal_handler(signal.SIGINT, stop.set_result, None)
26-
27-
async with serve(
28-
echo,
29-
host="",
30-
port=int(os.environ["PORT"]),
31-
process_request=health_check,
32-
):
33-
await stop
22+
port = int(os.environ["PORT"])
23+
async with serve(echo, "", port, process_request=health_check) as server:
24+
loop = asyncio.get_running_loop()
25+
loop.add_signal_handler(signal.SIGINT, server.close)
26+
await server.wait_closed()
3427

3528

3629
if __name__ == "__main__":

example/deployment/kubernetes/app.py

+4-12
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,10 @@ def health_check(connection, request):
3131

3232

3333
async def main():
34-
# Set the stop condition when receiving SIGTERM.
35-
loop = asyncio.get_running_loop()
36-
stop = loop.create_future()
37-
loop.add_signal_handler(signal.SIGTERM, stop.set_result, None)
38-
39-
async with serve(
40-
slow_echo,
41-
host="",
42-
port=80,
43-
process_request=health_check,
44-
):
45-
await stop
34+
async with serve(slow_echo, "", 80, process_request=health_check) as server:
35+
loop = asyncio.get_running_loop()
36+
loop.add_signal_handler(signal.SIGTERM, server.close)
37+
await server.wait_closed()
4638

4739

4840
if __name__ == "__main__":

example/deployment/nginx/app.py

+5-10
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,11 @@ async def echo(websocket):
1313

1414

1515
async def main():
16-
# Set the stop condition when receiving SIGTERM.
17-
loop = asyncio.get_running_loop()
18-
stop = loop.create_future()
19-
loop.add_signal_handler(signal.SIGTERM, stop.set_result, None)
20-
21-
async with unix_serve(
22-
echo,
23-
path=f"{os.environ['SUPERVISOR_PROCESS_NAME']}.sock",
24-
):
25-
await stop
16+
path = f"{os.environ['SUPERVISOR_PROCESS_NAME']}.sock"
17+
async with unix_serve(echo, path) as server:
18+
loop = asyncio.get_running_loop()
19+
loop.add_signal_handler(signal.SIGTERM, server.close)
20+
await server.wait_closed()
2621

2722

2823
if __name__ == "__main__":

example/deployment/render/app.py

+4-12
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,10 @@ def health_check(connection, request):
1818

1919

2020
async def main():
21-
# Set the stop condition when receiving SIGTERM.
22-
loop = asyncio.get_running_loop()
23-
stop = loop.create_future()
24-
loop.add_signal_handler(signal.SIGTERM, stop.set_result, None)
25-
26-
async with serve(
27-
echo,
28-
host="",
29-
port=8080,
30-
process_request=health_check,
31-
):
32-
await stop
21+
async with serve(echo, "", 8080, process_request=health_check) as server:
22+
loop = asyncio.get_running_loop()
23+
loop.add_signal_handler(signal.SIGTERM, server.close)
24+
await server.wait_closed()
3325

3426

3527
if __name__ == "__main__":

example/deployment/supervisor/app.py

+4-12
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,10 @@ async def echo(websocket):
1212

1313

1414
async def main():
15-
# Set the stop condition when receiving SIGTERM.
16-
loop = asyncio.get_running_loop()
17-
stop = loop.create_future()
18-
loop.add_signal_handler(signal.SIGTERM, stop.set_result, None)
19-
20-
async with serve(
21-
echo,
22-
host="",
23-
port=8080,
24-
reuse_port=True,
25-
):
26-
await stop
15+
async with serve(echo, "", 8080, reuse_port=True) as server:
16+
loop = asyncio.get_running_loop()
17+
loop.add_signal_handler(signal.SIGTERM, server.close)
18+
await server.wait_closed()
2719

2820

2921
if __name__ == "__main__":

example/faq/shutdown_client.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@
66
from websockets.asyncio.client import connect
77

88
async def client():
9-
uri = "ws://localhost:8765"
10-
async with connect(uri) as websocket:
9+
async with connect("ws://localhost:8765") as websocket:
1110
# Close the connection when receiving SIGTERM.
1211
loop = asyncio.get_running_loop()
1312
loop.add_signal_handler(signal.SIGTERM, loop.create_task, websocket.close())

example/faq/shutdown_server.py

+7-9
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,15 @@
55

66
from websockets.asyncio.server import serve
77

8-
async def echo(websocket):
8+
async def handler(websocket):
99
async for message in websocket:
10-
await websocket.send(message)
10+
...
1111

1212
async def server():
13-
# Set the stop condition when receiving SIGTERM.
14-
loop = asyncio.get_running_loop()
15-
stop = loop.create_future()
16-
loop.add_signal_handler(signal.SIGTERM, stop.set_result, None)
17-
18-
async with serve(echo, "localhost", 8765):
19-
await stop
13+
async with serve(handler, "localhost", 8765) as server:
14+
# Close the server when receiving SIGTERM.
15+
loop = asyncio.get_running_loop()
16+
loop.add_signal_handler(signal.SIGTERM, server.close)
17+
await server.wait_closed()
2018

2119
asyncio.run(server())

example/tutorial/step3/app.py

+4-7
Original file line numberDiff line numberDiff line change
@@ -190,14 +190,11 @@ def health_check(connection, request):
190190

191191

192192
async def main():
193-
# Set the stop condition when receiving SIGTERM.
194-
loop = asyncio.get_running_loop()
195-
stop = loop.create_future()
196-
loop.add_signal_handler(signal.SIGTERM, stop.set_result, None)
197-
198193
port = int(os.environ.get("PORT", "8001"))
199-
async with serve(handler, "", port, process_request=health_check):
200-
await stop
194+
async with serve(handler, "", port, process_request=health_check) as server:
195+
loop = asyncio.get_running_loop()
196+
loop.add_signal_handler(signal.SIGTERM, server.close)
197+
await server.wait_closed()
201198

202199

203200
if __name__ == "__main__":

experiments/compression/server.py

+8-11
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,6 @@ async def handler(ws):
3535

3636

3737
async def server():
38-
loop = asyncio.get_running_loop()
39-
stop = loop.create_future()
40-
41-
# Set the stop condition when receiving SIGTERM.
42-
print("Stop the server with:")
43-
print(f"kill -TERM {os.getpid()}")
44-
print()
45-
loop.add_signal_handler(signal.SIGTERM, stop.set_result, None)
46-
4738
async with serve(
4839
handler,
4940
"localhost",
@@ -55,9 +46,15 @@ async def server():
5546
compress_settings={"memLevel": ML},
5647
)
5748
],
58-
):
49+
) as server:
50+
print("Stop the server with:")
51+
print(f"kill -TERM {os.getpid()}")
52+
print()
53+
loop = asyncio.get_running_loop()
54+
loop.add_signal_handler(signal.SIGTERM, server.close)
55+
5956
tracemalloc.start()
60-
await stop
57+
await server.wait_closed()
6158

6259

6360
asyncio.run(server())

0 commit comments

Comments
 (0)