Repository navigation
Fix infinite blocking in TrySendMultipartMessage at low bandwidth - #1157
VincentVbs wants to merge 1 commit into
Conversation
Apply timeout to all frames in TrySendMultipartMessage, not just the first one. Previously, only the first frame used TrySendFrame with timeout, while subsequent frames used blocking SendMoreFrame/SendFrame methods. This caused infinite hangs when network bandwidth was extremely low (< 10 kbit/s). The fix ensures all frames respect the timeout parameter, preventing infinite blocking and allowing graceful failure when send operations cannot complete. Added three unit tests to validate the fix: - TrySendMultipartMessage_AppliesTimeoutToAllFrames - TrySendMultipartMessage_FailsGracefullyOnMiddleFrameTimeout - TrySendMultipartMessage_FailsGracefullyOnLastFrameTimeout Fixes zeromq#1127
drewnoakes
left a comment
There was a problem hiding this comment.
Thanks, this looks great too.
|
|
||
| return true; | ||
| // Send the last frame with timeout (no 'more' flag) | ||
| return TrySendFrame(socket, timeout, message.Last.Buffer, message.Last.MessageSize, false); |
There was a problem hiding this comment.
| return TrySendFrame(socket, timeout, message.Last.Buffer, message.Last.MessageSize, false); | |
| return TrySendFrame(socket, timeout, message.Last.Buffer, message.Last.MessageSize, more: false); |
There was a problem hiding this comment.
Pull request overview
This PR updates TrySendMultipartMessage so timeout-aware sends are used for every frame of a NetMQMessage, addressing the reported hang when multipart sends encounter extreme backpressure. It also adds focused unit tests around timeout handling for successful sends and failures on later frames.
Changes:
- Reworked
TrySendMultipartMessage(TimeSpan, NetMQMessage)to callTrySendFramefor every frame instead of mixing one timed send with later blocking sends. - Removed the old special-case path that timed only the first frame and blocked on the rest.
- Added three tests covering timeout propagation and failure behavior on middle/last frames.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
src/NetMQ/OutgoingSocketExtensions.cs |
Changes multipart send logic so all NetMQMessage frames use the timeout-aware send path. |
src/NetMQ.Tests/OutgoingSocketExtensionsTests.cs |
Adds unit tests intended to validate timeout handling across all frames and timeout failures on later frames. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| bool sentSuccessfully = TrySendFrame(socket, timeout, message[i].Buffer, message[i].MessageSize, true); | ||
| if (!sentSuccessfully) | ||
| return false; | ||
| } | ||
|
|
||
| for (int i = 1; i < message.FrameCount - 1; i++) | ||
| { | ||
| socket.SendMoreFrame(message[i].Buffer, message[i].MessageSize); | ||
| } | ||
|
|
||
| socket.SendFrame(message.Last.Buffer, message.Last.MessageSize); | ||
|
|
||
| return true; | ||
| // Send the last frame with timeout (no 'more' flag) | ||
| return TrySendFrame(socket, timeout, message.Last.Buffer, message.Last.MessageSize, false); |
| // CRITICAL: ALL frames must respect the timeout parameter | ||
| Assert.Equal(timeout, actualTimeout); |
| // Verify timeout is applied to all frames | ||
| Assert.Equal(timeout, actualTimeout); |
|
|
||
| var socket = new MockOutgoingSocket((ref Msg msg, TimeSpan actualTimeout, bool more) => | ||
| { | ||
| Assert.Equal(timeout, actualTimeout); |
|
Looks like there's a test failure. I like where this is going. |
|
@VincentVbs are you interested in continuing with this PR? |
|
Thanks, @drewnoakes, for following up with me and for giving me the opportunity to contribute to this project. |
|
No problem at all @VincentVbs, it's great that you added to the project. Even incomplete PRs can help move things along.
Some projects ask contributors to do that, but generally I think the main reason to do that is to protect your own time against working on something that the project won't accept. Sometimes it's hard to know whether it's acceptable before the investigation is finished though. Don't worry about it.
Just work on anything that seems interesting to you. You can discuss beforehand if you like. We're happy to have any contribution here on NetMQ. Other projects in the zeromq org may have different requirements. |
Apply timeout to all frames in TrySendMultipartMessage, not just the first one. Previously, only the first frame used TrySendFrame with timeout, while subsequent frames used blocking SendMoreFrame/SendFrame methods. This caused infinite hangs when network bandwidth was extremely low (< 10 kbit/s).
The fix ensures all frames respect the timeout parameter, preventing infinite blocking and allowing graceful failure when send operations cannot complete.
Added three unit tests to validate the fix:
Fixes #1127