Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions src/NetMQ.Tests/OutgoingSocketExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -345,5 +345,110 @@ public void TrySendFrameBiggerBufferThanLength()

Assert.True(socket.TrySendFrame(TimeSpan.Zero, buffer, data.Length));
}

/// <summary>
/// Test that TrySendMultipartMessage applies timeout to ALL frames, not just the first.
/// This validates the fix for issue #1127 where low bandwidth caused infinite blocking
/// because only the first frame respected the timeout parameter.
/// </summary>
[Fact]
public void TrySendMultipartMessage_AppliesTimeoutToAllFrames()
{
var timeout = TimeSpan.FromMilliseconds(500);
var frameCount = 0;

var socket = new MockOutgoingSocket((ref Msg msg, TimeSpan actualTimeout, bool more) =>
{
// CRITICAL: ALL frames must respect the timeout parameter
Assert.Equal(timeout, actualTimeout);
Comment on lines +362 to +363

frameCount++;

// Verify 'more' flag is correct for each frame
if (frameCount < 3)
Assert.True(more, $"Frame {frameCount} should have 'more' flag set");
else
Assert.False(more, "Last frame should not have 'more' flag");

return true;
});

var message = new NetMQMessage();
message.Append(new byte[] { 1 }); // Frame 1: Identity
message.Append(new byte[] { }); // Frame 2: Empty frame
message.Append(new byte[] { 2 }); // Frame 3: Payload

bool result = socket.TrySendMultipartMessage(timeout, message);

Assert.True(result, "Send should succeed when all frames are sent");
Assert.Equal(3, frameCount);
}

/// <summary>
/// Test that TrySendMultipartMessage fails gracefully when a middle frame times out.
/// This validates that the timeout is respected on ALL frames, preventing infinite blocking.
/// </summary>
[Fact]
public void TrySendMultipartMessage_FailsGracefullyOnMiddleFrameTimeout()
{
var timeout = TimeSpan.FromMilliseconds(500);
var frameCount = 0;

var socket = new MockOutgoingSocket((ref Msg msg, TimeSpan actualTimeout, bool more) =>
{
// Verify timeout is applied to all frames
Assert.Equal(timeout, actualTimeout);
Comment on lines +399 to +400

frameCount++;

// Simulate timeout on the second frame
if (frameCount == 2)
return false; // Timeout!

return true;
});

var message = new NetMQMessage();
message.Append(new byte[] { 1 });
message.Append(new byte[] { });
message.Append(new byte[] { 2 });

bool result = socket.TrySendMultipartMessage(timeout, message);

Assert.False(result, "Send should fail when a middle frame times out");
Assert.Equal(2, frameCount); // Should stop after the failed frame
}

/// <summary>
/// Test that TrySendMultipartMessage fails gracefully when the last frame times out.
/// </summary>
[Fact]
public void TrySendMultipartMessage_FailsGracefullyOnLastFrameTimeout()
{
var timeout = TimeSpan.FromMilliseconds(500);
var frameCount = 0;

var socket = new MockOutgoingSocket((ref Msg msg, TimeSpan actualTimeout, bool more) =>
{
Assert.Equal(timeout, actualTimeout);
frameCount++;

// Simulate timeout on the last frame
if (frameCount == 3)
return false;

return true;
});

var message = new NetMQMessage();
message.Append(new byte[] { 1 });
message.Append(new byte[] { });
message.Append(new byte[] { 2 });

bool result = socket.TrySendMultipartMessage(timeout, message);

Assert.False(result, "Send should fail when the last frame times out");
Assert.Equal(3, frameCount);
}
}
}
20 changes: 6 additions & 14 deletions src/NetMQ/OutgoingSocketExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -456,25 +456,17 @@ public static bool TrySendMultipartMessage(this IOutgoingSocket socket, TimeSpan
{
if (message.FrameCount == 0)
throw new ArgumentException("message is empty", nameof(message));
else if (message.FrameCount == 1)
{
return TrySendFrame(socket, timeout, message[0].Buffer, message[0].MessageSize);
}
else

// Send all frames except the last with timeout and 'more' flag
for (int i = 0; i < message.FrameCount - 1; i++)
{
bool sentSuccessfully = TrySendFrame(socket, timeout, message[0].Buffer, message[0].MessageSize, true);
bool sentSuccessfully = TrySendFrame(socket, timeout, message[i].Buffer, message[i].MessageSize, true);
Comment thread
VincentVbs marked this conversation as resolved.
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);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return TrySendFrame(socket, timeout, message.Last.Buffer, message.Last.MessageSize, false);
return TrySendFrame(socket, timeout, message.Last.Buffer, message.Last.MessageSize, more: false);

Comment on lines +463 to +469
}

#endregion
Expand Down
Loading