Skip to content

fix(rumqttc): release inflight slot on failed PUBREC in v5 - #1075

Open
eastagiletracker wants to merge 1 commit into
bytebeamio:mainfrom
eastagiletracker:agile-board/v5-failed-pubrec-inflight
Open

eastagiletracker wants to merge 1 commit into
bytebeamio:mainfrom
eastagiletracker:agile-board/v5-failed-pubrec-inflight

Conversation

@eastagiletracker

Copy link
Copy Markdown

This PR proposes releasing the v5 inflight slot when a PUBREC carries a failure reason code, so rejected QoS 2 publishes no longer block the client (Fixes #1060). We include this PR work along with a full history of your repo at https://eastagiletracker.com/projects/1141. You can sign in with your GitHub ID to claim ownership of the project.

Fix description

In rumqttc/src/v5/state.rs, handle_incoming_pubrec clears the stored publish when a PUBREC arrives, but on a failure reason code (anything other than Success / NoMatchingSubscribers, e.g. NotAuthorized or QuotaExceeded) it returns early without decrementing inflight. That exchange is over: no PUBREL follows and no PUBCOMP will ever arrive to do the decrement. So each rejected QoS 2 publish leaks one slot. Once max_outgoing_inflight slots have leaked, EventLoop::select sees inflight_full and stops pulling requests, and every later publish sits in the channel forever. This is the deadlock described in #1060. MQTT 5 section 4.9 says the send quota is incremented "when a PUBREC packet is received with a Return Code of 0x80 or greater".

The fix decrements inflight on that path. If a publish was stashed in collision waiting for the same packet id, the fix sends it right away, just like the existing success path in handle_incoming_puback. Otherwise that publish would wait for the collision timeout even though its id is now free. The success path and v4 are unchanged. I also added a Fixed entry to rumqttc/CHANGELOG.md.

Reproduction on current main (e886a78): I added three unit tests next to the existing PUBREC tests in v5::state::test. Before the fix, cargo test -p rumqttc --lib failed_pubrec fails all three:

test v5::state::test::incoming_failed_pubrec_should_release_inflight_and_pkid ... FAILED
  assertion `left == right` failed  left: 1  right: 0
test v5::state::test::incoming_failed_pubrecs_should_not_block_further_publishes ... FAILED
  assertion `left == right` failed  left: 2  right: 0
test v5::state::test::incoming_failed_pubrec_should_resend_collided_publish ... FAILED
  Invalid network request: None

The second test is the deadlock in #1060. With max_inflight = 2, two rejected QoS 2 publishes leave inflight == max_outgoing_inflight, which is the condition that stalls the event loop.

Verification: cargo test -p rumqttc on a clean main gave 73 unit, 14 reliability (1 ignored) and 5 doc tests passing. With this change it gives 76 unit (the 3 new ones), 14 reliability and 5 doc tests passing, with no new failures. cargo clippy -p rumqttc --lib output is identical before and after, and rustfmt --check is clean on the changed file.

How this was managed

This work was tracked as story https://eastagiletracker.com/projects/1141/stories/1347012 on a board imported from this repo's issues and pull requests (986 stories): https://eastagiletracker.com/projects/1141

board

If you'd rather not receive contributions like this, reply no-more-prs on this pull request and we won't open any further ones on your repositories.


Lawrence W. Sinclair
CEO / East Agile
linkedin.com/in/lwsinclair/
eastagile.com

A PUBREC carrying a failure reason code (>= 0x80) ends the QoS 2 exchange:
no PUBREL is sent and the packet id becomes free. The v5 state cleared the
outgoing publish but never decremented `inflight`, so every rejected QoS 2
publish (e.g. NotAuthorized) leaked one slot. Once `max_outgoing_inflight`
slots leaked, the event loop stopped reading new requests for good.

Decrement `inflight` on a failed PUBREC, as MQTT 5 section 4.9 requires for
the send quota, and resend a publish stashed on a collision with that packet
id, mirroring the PUBACK path. Adds unit tests for the slot release, the
full-queue case and the collision case.
@thehouseisonfire

thehouseisonfire commented Oct 15, 2026 •

Copy link
Copy Markdown

Hi @eastagiletracker, and thank you for the PR. This fix looks about right from a first glance, and the issue has already been fixed over on my fork.

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.

MQTTv5 inflight counter leak on PUBREC failure

2 participants