What happens when uvicorn restarts
If you do nothing, every WebSocket connection ends with code 1006 (abnormal closure) on restart. Clients see a network error; reconnection logic kicks in; everyone is fine. But you can do better: send a system.reconnect message giving the client a heads-up, then close cleanly with 1001 (going away).
FastAPI lifespan
Use the lifespan context manager. Whatever runs after yield is shutdown. Close every active WebSocket, then return — uvicorn will not exit until lifespan returns.
Concurrent close with gather
Closing 10,000 connections one at a time takes minutes. Use asyncio.gather(*close_tasks, return_exceptions=True) to fan out and wait for all of them concurrently. return_exceptions=True ensures one bad client does not abort the rest.