Skip to content

Streams: consolidate SSE LISTEN into the preforking web master (1 per host) #382

Description

@mhenrixon

Problem

Split out of #381 (item 2). On preforking web servers, each Puma worker lazily builds its own Pgbus::Web::Streamer::Instance — and with it one dedicated direct LISTEN connection (lib/pgbus/web/streamer/listener.rb, created per worker on the first SSE subscriber via Pgbus::Web::Streamer.current). A host with N Puma workers pins N direct connections; on transaction-pool PgBouncer platforms those come out of the scarce direct slice of max_connections.

Goal: one streams LISTEN connection per host — the listener lives in the Puma master, workers are woken over IPC.

Why this is not a trivial port of the supervisor-side change (#381 item 1)

The job-side consolidation only has to deliver a payload-free wake byte for a mostly-static queue set. The streamer Listener's contract is much richer, and each piece has to survive the master→worker process boundary:

  1. Synchronous ensure_listening ack (listener.rb:134-138): Dispatcher#handle_connect blocks until LISTEN is actually active before issuing read_after, otherwise a broadcast committed in the gap is lost (neither in the read_after result nor delivered as a wake). Cross-process, this becomes a worker→master request/ack round trip with a bounded timeout, per new stream subscription.
  2. Per-subscriber dynamic channel sets: the LISTEN set follows SSE subscriber registration/GC per worker (ensure_listening / lazy remove_listening). The master must maintain a refcounted union across workers and GC channels only when no worker holds a subscriber.
  3. Ephemeral wakes carry payloads (listener.rb:253-278): WakeMessage.payload is the only copy of the broadcast HTML — it cannot be dropped and must be framed and fanned out to the right workers over the pipe/socket, not just a wake byte. Durable wakes (payload nil) are droppable under backpressure; ephemeral are not — the IPC channel needs that distinction.
  4. Lazy instantiation: nothing starts at worker boot today (lib/puma/plugin/pgbus_streams.rb only registers teardown hooks); a master-owned listener needs a before_fork-time start plus per-worker inherited channels, and a story for single-mode / non-preforking servers (keep the current per-process listener behind a scope config, mirroring worker_notify_scope).

Sketch

Acceptance

  • Preforking web server with N workers: exactly 1 streams LISTEN connection per host.
  • No lost broadcasts across the subscribe/read_after gap (the ack contract holds cross-process).
  • Ephemeral wakes are never dropped by the IPC layer; durable-wake backpressure semantics preserved (dispatch_queue_limit).
  • Single-mode Puma / non-preforking servers keep working with the per-process listener.

Depends on

Metadata

Metadata

Assignees

No one assigned

    Labels

    connectionsConnection handling, failover, self-healingenhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions