TimeoutError signals that an asynchronous operation exceeded its allowed time. asyncio.wait_for raises it after the limit and automatically cancels the wrapped task. section 2.4.2
Catch the exception when a timeout is expected, then inspect or handle the task. Use asyncio.shield when the operation should continue after the caller’s wait ends. section 2.4.2
Timed task
A two-second delay wrapped in wait_for(timeout=1) raises TimeoutError after roughly one second, and the task reports cancelled=True. Shielding a ten-second task with a five-second wait instead reports the timeout while allowing the task to finish.
Listing 3.10 A graceful shutdown
import asyncio
from asyncio import AbstractEventLoop
import socket
import logging
import signal
from typing import List
async def echo(connection: socket,
loop: AbstractEventLoop) -> None:
try:
while data := await loop.sock_recv(connection, 1024):
print('got data!')
if data == b'boom\r\n':
raise Exception("Unexpected network error")
await loop.sock_sendall(connection, data)
except Exception as ex:
logging.exception(ex)
finally:
connection.close()
echo_tasks = []
async def connection_listener(server_socket, loop):
while True:
connection, address = await loop.sock_accept(server_socket)
connection.setblocking(False)
print(f"Got a connection from {address}")
echo_task = asyncio.create_task(echo(connection, loop))
echo_tasks.append(echo_task)
class GracefulExit(SystemExit):
pass
def shutdown():
raise GracefulExit()
async def close_echo_tasks(echo_tasks: List[asyncio.Task]):
waiters = [asyncio.wait_for(task, 2) for task in echo_tasks]
for task in waiters:
try:
await task
except asyncio.exceptions.TimeoutError:
# We expect a timeout error here
pass
async def main():
server_socket = socket.socket()
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_address = ('127.0.0.1', 8000)
server_socket.setblocking(False)
server_socket.bind(server_address)
server_socket.listen()
for signame in {'SIGINT', 'SIGTERM'}:
loop.add_signal_handler(getattr(signal, signame), shutdown)
await connection_listener(server_socket, loop)
loop = asyncio.new_event_loop()
try:
loop.run_until_complete(main())
except GracefulExit:
loop.run_until_complete(close_echo_tasks(echo_tasks))
finally:
loop.close()
A graceful echo-server shutdown gives pending tasks two seconds to finish, catches expected TimeoutErrors, and then terminates remaining work. section 3.6.2
