Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
6d1a22f
feat(serve): add IPC message types for multi-worker mode
maximmaxim345 Mar 26, 2026
90615ec
feat(serve): add ServeWorker subprocess for multi-worker mode
maximmaxim345 Mar 26, 2026
b9f8dba
feat(serve): add coordinator URL injection and /api/status endpoint
maximmaxim345 Mar 26, 2026
2b3b531
feat(serve): add ServeCoordinator for multi-worker orchestration
maximmaxim345 Mar 26, 2026
ee4087e
feat(serve): wire up --workers flag to multi-worker mode
maximmaxim345 Mar 26, 2026
0127d72
feat(serve): add listener count display to web player
maximmaxim345 Mar 26, 2026
6ad459f
test(serve): add integration test for multi-worker mode
maximmaxim345 Mar 26, 2026
45ce1a0
fix(serve): fix race condition in coordinator status queue polling
maximmaxim345 Mar 26, 2026
6162be7
fix(serve): add CORS header to coordinator /api/status endpoint
maximmaxim345 Mar 26, 2026
0358b69
fix(serve): make Ctrl+C immediately cancel the coordinator
maximmaxim345 Mar 26, 2026
38e7ff3
feat(serve): log per-worker client counts every 30s
maximmaxim345 Mar 26, 2026
6b93907
fix(serve): handle StreamStoppedError when all clients disconnect
maximmaxim345 Mar 26, 2026
734efed
fix(serve): address code review findings
maximmaxim345 Mar 26, 2026
a5b565b
refactor(serve): remove coordinator HTTP server, add shared listener …
maximmaxim345 Mar 26, 2026
8da8b4a
refactor(serve): replace coordinator_url with total_listeners in worker
maximmaxim345 Mar 26, 2026
9ce4bec
refactor(serve): replace coordinator_url with total_listeners in server
maximmaxim345 Mar 26, 2026
0b3d6c9
refactor(serve): remove coordinator URL from web player
maximmaxim345 Mar 26, 2026
f9a79d7
test(serve): update integration test for proxy-friendly architecture
maximmaxim345 Mar 26, 2026
c87ba90
fix(serve): deduplicate worker crash messages in health check
maximmaxim345 Mar 26, 2026
bfcbefc
chore: fix stale comment referencing coordinator in web player
maximmaxim345 Mar 26, 2026
b054ae5
fix(serve): reset active group when all clients disconnect from worker
maximmaxim345 Mar 27, 2026
8fcb729
fix(serve): increase coordinator buffer to 5s for multi-worker mode
maximmaxim345 Mar 27, 2026
62c8091
fix(serve): fix port off-by-one in multi-worker mode
maximmaxim345 Mar 27, 2026
81a5bee
fix(serve): clear worker group on last client disconnect
maximmaxim345 Mar 27, 2026
dc27b5f
fix(serve): error when --client used with --workers
maximmaxim345 Mar 27, 2026
a28fc56
fix(serve): handle worker startup failures properly
maximmaxim345 Mar 27, 2026
12bedb5
fix(serve): shut down all workers if any single worker crashes
maximmaxim345 Mar 27, 2026
fd7cb39
feat(serve): pause audio decoding when no clients connected
maximmaxim345 Mar 27, 2026
8251954
chore(serve): remove dead _client_connected_event, update buffer comment
maximmaxim345 Mar 27, 2026
0dac80e
chore(serve): change listener count text to 'x tuned in'
maximmaxim345 Mar 27, 2026
a762343
docs: add multi-worker mode documentation to README
maximmaxim345 Mar 27, 2026
80be94b
fix: serve partial worker startup handling
maximmaxim345 Mar 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -390,3 +390,15 @@ uvx sendspin serve /path/to/media.mp3
# Connect to specific clients
sendspin serve --demo --client ws://192.168.1.50:8927/sendspin --client ws://192.168.1.51:8927/sendspin
```

### Multi-Worker Mode

For serving many concurrent listeners, use `--workers` to run multiple server processes behind a reverse proxy:

```bash
sendspin serve --demo --workers 4
```

This spawns 4 worker processes on consecutive ports starting from `--port` (default 8927), so ports 8927-8930. Place a reverse proxy (e.g., nginx, Caddy) in front with load balancing across these ports.

Note: `--client` is not supported with `--workers`.
19 changes: 18 additions & 1 deletion sendspin/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,12 @@ def _build_parser() -> argparse.ArgumentParser:
default=[],
help="Client URL to connect to (can be specified multiple times)",
)
serve_parser.add_argument(
"--workers",
type=int,
default=1,
help="Number of server worker processes (default: 1)",
)

# Daemon subcommand
daemon_parser = subparsers.add_parser(
Expand Down Expand Up @@ -503,7 +509,7 @@ def _resolve_preferred_format(

async def _run_serve_mode(args: argparse.Namespace) -> int:
"""Run the server mode."""
from sendspin.serve import ServeConfig, run_server
from sendspin.serve import ServeConfig, run_server, run_server_multi

# Load settings for serve mode
settings = await get_serve_settings()
Expand Down Expand Up @@ -539,6 +545,17 @@ async def _run_serve_mode(args: argparse.Namespace) -> int:
name=args.name,
clients=args.clients or settings.clients,
)
if args.workers < 1:
print("Error: --workers must be at least 1")
return 1

if args.workers > 1 and serve_config.clients:
print("Error: --client is not supported with --workers")
return 1

if args.workers > 1:
return await run_server_multi(serve_config, workers=args.workers, log_level=args.log_level)

return await run_server(serve_config)


Expand Down
15 changes: 15 additions & 0 deletions sendspin/serve/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,3 +286,18 @@ def on_server_event(server: SendspinServer, event: SendspinEvent) -> None:
await server.close()

return 0


async def run_server_multi(config: ServeConfig, *, workers: int, log_level: str) -> int:
"""Run the multi-worker Sendspin server."""
from sendspin.serve.coordinator import ServeCoordinator

coordinator = ServeCoordinator(
source=config.source,
source_format=config.source_format,
port=config.port,
name=config.name,
workers=workers,
log_level=log_level,
)
return await coordinator.run()
Loading