Discord stream caps are check-then-write: concurrent starts can exceed the per-server and per-node limits #223

Open
opened 2026-10-04 21:36:58 -04:00 by logan · 1 comment
Owner

Found reviewing PR #220 (server-26#213 item 3). Not fixed there; low severity.

Where:

  • drb-c2-core/app/internal/discord_streams.py start_stream: the per-server cap (max_streams) counts live discord_streams docs, then leases later, outside any transaction.
  • drb-c2-core/app/routers/discord_web.py start_node_stream (PR #220): the per-node cap (discord_node_max_streams) counts this node's live streams, then calls start_stream.

Failure: two starts for different channels arriving together (two browser tabs, or the web picker plus /radio play) both read N-1 and both write, so a server or node ends up with cap+1 streams. The lease itself is a Firestore transaction, so no bot token is double-leased; only the cap is soft.

Fix shape: count and place in one transaction (or a per-guild / per-node asyncio lock, given one c2-core process already serialises publishes per node via _publish_locks).

Found reviewing PR #220 (server-26#213 item 3). Not fixed there; low severity. **Where:** - `drb-c2-core/app/internal/discord_streams.py` `start_stream`: the per-server cap (`max_streams`) counts live `discord_streams` docs, then leases later, outside any transaction. - `drb-c2-core/app/routers/discord_web.py` `start_node_stream` (PR #220): the per-node cap (`discord_node_max_streams`) counts this node's live streams, then calls `start_stream`. **Failure:** two starts for different channels arriving together (two browser tabs, or the web picker plus `/radio play`) both read N-1 and both write, so a server or node ends up with cap+1 streams. The lease itself is a Firestore transaction, so no bot token is double-leased; only the cap is soft. **Fix shape:** count and place in one transaction (or a per-guild / per-node asyncio lock, given one c2-core process already serialises publishes per node via `_publish_locks`).
Author
Owner

PR #227 (server-26#213 follow-up) fixes the per-guild half: start_stream now holds a per-guild asyncio lock around the already-on and server-cap checks and the create, with a test that fails if the lock is removed. The per-node cap in discord_web.start_node_stream (discord_node_max_streams) is still check-then-write across guilds, so this stays open for that half.

PR #227 (server-26#213 follow-up) fixes the **per-guild** half: `start_stream` now holds a per-guild asyncio lock around the already-on and server-cap checks and the create, with a test that fails if the lock is removed. The **per-node** cap in `discord_web.start_node_stream` (`discord_node_max_streams`) is still check-then-write across guilds, so this stays open for that half.
logan added the area/discord
kind
behavior-bug
priority
p3
type
bug
labels 2026-10-05 02:05:05 -04:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: logan/server-26#223