Skip to content

Fix pub/sub message delivery to idle subscribers (v0.11.9) - #41

Merged
mack42 merged 1 commit into
mainfrom
fix/pubsub-message-delivery
Jul 17, 2026
Merged

Fix pub/sub message delivery to idle subscribers (v0.11.9)#41
mack42 merged 1 commit into
mainfrom
fix/pubsub-message-delivery

Conversation

@mack42

@mack42 mack42 commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Bug

A subscriber never received a published message until it happened to send another byte to the server. Any client that subscribes and then waits passively (the normal pub/sub pattern) missed messages.

Root cause

In src/server/connection.rs, the handle() loop latches in_pubsub once at the top of each iteration:

loop {
    let in_pubsub = ...;               // latched here
    if in_pubsub { /* select! read_buf OR pubsub_rx */ }
    else {
        while let Some(r) = try_process_command() { write }   // SUBSCRIBE handled here
        let n = read_buf(...);         // then blocks on the socket
    }
}

When a fresh subscriber's SUBSCRIBE is processed, it happens inside the else branch (because in_pubsub was false when the iteration began). Control then falls through to the blocking read_bufnot the select! — so pubsub_rx is never polled. The published message is delivered into the channel (that's why PUBLISH correctly returns :1) but nothing drains it until the next loop iteration, which only happens when the client sends more data.

Reproduced with a raw RESP client: the message frame arrived only after poking the subscriber socket with an unrelated PING.

(The earlier idle-read timeout reduced the symptom from "hangs forever" to "delivered after 30s", still well past the tests' 5s wait.)

Fix

After the normal-mode command loop, re-enter the loop if the connection has just entered pub/sub mode, so the next iteration waits on the select! that watches pubsub_rx:

if !self.subscribed_channels.is_empty() || !self.subscribed_patterns.is_empty() {
    continue;
}

Verification

  • Manual raw-RESP reproduction: message now arrives immediately, no poke needed.
  • cargo test full suite green (87 unit + all integration), including the two pubsub tests that previously failed.

Also commits the integration test suite (tests/), previously untracked — the two pubsub tests exercised exactly this bug and now pass, so they're worth keeping as regression coverage.

A subscriber did not receive published messages until it happened to send
another byte to the server. The connection loop latches `in_pubsub` at the
top of each iteration; when a SUBSCRIBE was processed inside the normal-mode
branch, control fell through to a blocking socket read instead of the
select! that also polls pubsub_rx, so a delivered message sat unread in the
channel until the next loop iteration (triggered only by more client input).

Re-enter the loop after processing commands if the connection has entered
pub/sub mode, so the next iteration waits on the pub/sub channel.

Also commit the integration test suite (previously untracked); the two
pubsub tests failed against this bug and now pass.
@mack42 mack42 self-assigned this Jul 17, 2026
@mack42
mack42 merged commit d537c44 into main Jul 17, 2026
1 check failed
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