Skip to content

Add multi-worker serve mode for Sendspin Party#199

Draft
maximmaxim345 wants to merge 32 commits intomainfrom
wip/scalable-demo
Draft

Add multi-worker serve mode for Sendspin Party#199
maximmaxim345 wants to merge 32 commits intomainfrom
wip/scalable-demo

Conversation

@maximmaxim345
Copy link
Copy Markdown
Member

Summary

This PR adds multi-worker support to sendspin serve so multiple worker processes can accept Sendspin Party listeners while sharing the
same decoded audio timeline.

It introduces a coordinator/worker architecture where:

  • the coordinator decodes the source once and fans out timestamped PCM chunks
  • each worker runs its own HTTP/WebSocket server and serves the embedded web player
  • the web UI can report total connected listeners across workers

Changes

  • add --workers to sendspin serve
  • add multi-worker serve coordinator/worker implementation
  • add IPC message types for coordinator/worker communication
  • add /api/status endpoint for total listener count
  • update the embedded web player to poll and display listener count
  • reject --client when used with multi-worker mode
  • pause decoding when all listeners disconnect in multi-worker mode
  • handle worker startup failures and unexpected worker crashes correctly
  • fix worker reconnect behavior after the last client disconnects
  • document multi-worker mode in README.md

Notes

  • Multi-worker mode runs one HTTP/WebSocket server per worker
  • place a reverse proxy/load balancer in front of the worker ports
  • --client remains supported in single-worker serve mode only

asyncio.wait_for + run_in_executor with a blocking queue.get() leaves
orphaned threads that consume messages and discard them when the future
is cancelled. Use queue.get(timeout=0.5) instead so the thread exits
cleanly on timeout.
The web player on a worker port (e.g. :9002) fetches the coordinator's
/api/status (e.g. :9000) which is cross-origin. Add
Access-Control-Allow-Origin: *
to allow the request.
Cancel the running task on SIGINT so it breaks out of blocking audio
decode. Ignore repeated Ctrl+C after the first one.
When the last client disconnects from a worker, the stream is stopped.
Catch StreamStoppedError and clear the stream reference so the worker
skips audio chunks until a new client connects and creates a fresh
stream.
- Add worker crash detection: coordinator checks process liveness every
  30s and removes dead workers from the redirect pool. Shuts down if all
  workers crash.
- Validate --workers >= 1 in CLI
- Add CORS header to single-worker /api/status for consistency
- Use _queue.Empty instead of bare Exception in _drain_status_queue
- Remove unused TYPE_CHECKING import and empty pass block
- Redirect handler now uses live _active_worker_ports (reflects crash
removal)
- Frontend: hide listener count when stopped, show min 1 when listening
Track already-reported crashes so _check_worker_health doesn't print
the same crash message every 30s for workers that are already dead.
Clear _active_group alongside _stream on StreamStoppedError so that
the next connecting client creates a fresh group and stream instead of
being added to a stale group with no active stream.
The coordinator was pacing itself to real-time (sleeping when ahead > 0),
resulting in only ~250ms of buffer on workers. Allow up to 5s of buffer
ahead, matching single-worker mode's max_buffer_us=5_000_000.
Workers now use consecutive ports starting at --port instead of --port+1.
Prevents reconnect race where a client joins a dead group whose
stream was already stopped by aiosendspin.
Multi-worker mode does not support outbound client connections.
Previously the --client flag was silently ignored.
Track successful vs failed workers separately. Exit if all workers
fail. Remove dead audio queues for failed workers.
Previously only shut down when all workers crashed.
Coordinator now loops between waiting for clients and streaming,
instead of continuously decoding when nobody is listening.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant