Skip to content

QoS1: redelivered persisted in-flight message can be ordered after a newer message on resume #151

Description

@fabracht

Found in the QoS1 backlog rework (branch worktree-qos1-backlog), round-3 review finding #7. Narrow race; no loss or duplication, ordering only.

Severity: minor. Violates [MQTT-4.4.0-1] redelivery ordering in a specific race.

Scenario: After a broker restart, client c reconnects with clean_start=0 holding a persisted outbound QoS1 message in AwaitingPubrec and an empty offline queue. Between register_session inserting c into the clients map and bind() running (load_persisted_inflight -> requeue_front), a concurrent publish for c's topic is routed: the queue is still empty so the router puts it on the QoS1 lane. bind() then re-queues the older in-flight message at the front of the queue. The lane arm (gated only by the outbound window) can deliver the newer message before the drain delivers the older re-queued one.

Why it isn't already fixed: The naive fix (gate the QoS1 lane on an empty backlog) deadlocks the normal backlog case, because drain_backlog already returns early while the lane is non-empty — so with both non-empty nothing progresses. That change was tried and reverted.

Proposed fix: Make c route to the queue rather than the lane until bind() completes (e.g. treat the client as "behind" while unbound), so the racing publish lands in the queue after the re-queued in-flight message. Location: crates/mqtt5/src/broker/client_handler/mod.rs (bind/serve) and the queue's behind() in crates/mqtt5/src/broker/storage/client_queue.rs.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions