[dbsp] Retransmit unacknowledged multihost exchange messages - #6784
Closed
ryzhyk wants to merge 1 commit into
Closed
[dbsp] Retransmit unacknowledged multihost exchange messages#6784ryzhyk wants to merge 1 commit into
ryzhyk wants to merge 1 commit into
Conversation
A message lost while its connection stays up wedged the exchange forever.
The sender only retransmits when it reconnects, and it only reconnects when
the socket reports an error, so nothing resent the message: the receiver
waited for it, the sender waited for its acknowledgement, and in a
synchronous exchange no new message could arrive to drive the sender,
because the workers on both ends were waiting for that one.
`operators_multihost_dynamic` hung on this reliably at 16 workers over 4
hosts, and took `multihost` and `sharded_accumulator_multihost` down with it
when they ran together. The wedged state, dumped from a live hang:
tx peer=12..16 min_sequence=6784 channel_seq=6779 queued=5
receiver on that host expects sequence 6779
The sender had sent 6779..6783 and was waiting for acknowledgements; the
receiver had seen none of them. Every serve task on both ends sat in
`read_message` with nothing to read, and no worker could produce another
message.
Retransmit from the oldest unacknowledged message when an acknowledgement
does not arrive within a timeout. The receiver already drops messages whose
sequence number it has seen, so a needless retransmission costs one message.
This closes the liveness hole for any cause of loss rather than for one
particular cause.
The tests inject connection, send, and read failures at a 1% rate, which is
what made this reachable there; the same hole exists in production, where a
lost message without a broken connection stalls the pipeline.
Member
|
What I don't understand is how a message can be lost when the connection stays up. TCP shouldn't lose messages. Do you have an idea? |
Contributor
Author
|
Member
|
@ryzhyk I think I can do better, I'll submit an alternative PR |
4 tasks
Member
|
I am proposing #6793 instead of this PR. |
blp
added a commit
that referenced
this pull request
Aug 4, 2026
A sender that has nothing new to send and hasn't been acknowledged can't tell a half-open connection from a receiver that's simply behind, so retransmitting unconditionally after a timeout risks resending a large backlog into a receiver that's merely busy. Probe with a ping instead: a few dozen bytes, built by reusing ExchangeHeader with a reserved sequence number and zero-length payloads. The receiver's serve loop can only answer anything -- a real acknowledgement or a pong -- once it's back at read_message, so the ping write doubles as the same "force a stale connection error to surface" attempt a blind retransmit would make, at a fraction of the cost. Actual retransmits are not necessary within a single connection, since TCP doesn't lose data; we only retransmit if the connection drops and reconnects. Adds a deterministic test driving the wire protocol directly (ping before retransmit), verified to catch the regression when the confirmed-retransmit path is disabled. Presented as an alternative to #6784 Signed-off-by: Ben Pfaff <blp@feldera.com>
mihaibudiu
pushed a commit
to mihaibudiu/dbsp
that referenced
this pull request
Aug 4, 2026
A sender that has nothing new to send and hasn't been acknowledged can't tell a half-open connection from a receiver that's simply behind, so retransmitting unconditionally after a timeout risks resending a large backlog into a receiver that's merely busy. Probe with a ping instead: a few dozen bytes, built by reusing ExchangeHeader with a reserved sequence number and zero-length payloads. The receiver's serve loop can only answer anything -- a real acknowledgement or a pong -- once it's back at read_message, so the ping write doubles as the same "force a stale connection error to surface" attempt a blind retransmit would make, at a fraction of the cost. Actual retransmits are not necessary within a single connection, since TCP doesn't lose data; we only retransmit if the connection drops and reconnects. Adds a deterministic test driving the wire protocol directly (ping before retransmit), verified to catch the regression when the confirmed-retransmit path is disabled. Presented as an alternative to feldera#6784 Signed-off-by: Ben Pfaff <blp@feldera.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
@blp , multihost tests were reliably getting stuck on my laptop. Since deterministic reproductions of non-deterministic bugs are rare and valuable, I ran claude on it, which came back with this. Could you please take over this PR to validate the diagnostics and the fix?
A message lost while its connection stays up wedged the exchange forever. The sender only retransmits when it reconnects, and it only reconnects when the socket reports an error, so nothing resent the message: the receiver waited for it, the sender waited for its acknowledgement, and in a synchronous exchange no new message could arrive to drive the sender, because the workers on both ends were waiting for that one.
operators_multihost_dynamichung on this reliably at 16 workers over 4 hosts, and tookmultihostandsharded_accumulator_multihostdown with it when they ran together. The wedged state, dumped from a live hang:The sender had sent 6779..6783 and was waiting for acknowledgements; the receiver had seen none of them. Every serve task on both ends sat in
read_messagewith nothing to read, and no worker could produce another message.Retransmit from the oldest unacknowledged message when an acknowledgement does not arrive within a timeout. The receiver already drops messages whose sequence number it has seen, so a needless retransmission costs one message. This closes the liveness hole for any cause of loss rather than for one particular cause.
The tests inject connection, send, and read failures at a 1% rate, which is what made this reachable there; the same hole exists in production, where a lost message without a broken connection stalls the pipeline.
Describe Manual Test Plan
Checklist
Breaking Changes?
Mark if you think the answer is yes for any of these components:
Describe Incompatible Changes