Closes#77833
stream_events() only detected client disconnect via send_json()
raising WebSocketDisconnect. When no events were pending (idle
board), send_json was never called, so the poll loop ran forever
even after the client disconnected — leaking one poll task per
disconnect.
Fix: race ws.receive() with a timeout matching the poll interval.
On timeout, poll the DB as before. On disconnect, exit cleanly.