Uh oh!
There was an error while loading. Please reload this page.
Restore blocking mode after successful ConnectAsync on Unix - #124200
Conversation
Tagging subscribers to this area: @karelz, @dotnet/ncl |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
stephentoub
commented
Feb 11, 2026
🤖 Copilot Code Review — PR #124200Holistic AssessmentMotivation: The optimization goal is valid. After Approach: The implementation adds Summary: Detailed Findings
|
ba10f3d to
49a3f53CompareThere was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs:688
- The InvokeCallback override only invokes the callback and calls RestoreBlocking when buffer.Length is 0. However, when a connect operation fails (ErrorCode != Success) and a buffer was provided (via DoOperationConnectEx), the callback will never be invoked and RestoreBlocking won't be called.
This can occur when using ConnectEx with a buffer on Unix systems. The callback must always be invoked when the operation is complete, regardless of whether there's a buffer or whether the operation succeeded or failed. RestoreBlocking should also be called on error paths to restore the socket to blocking mode after a failed connection attempt.
Consider updating the logic to:
- Always call RestoreBlocking when the connect fails (ErrorCode != Success), regardless of buffer.Length
- Always invoke the callback when the operation is complete and no follow-up Send is needed
public override void InvokeCallback(bool allowPooling)
{
var cb = Callback!;
int bt = BytesTransferred;
Memory<byte> sa = SocketAddress;
SocketError ec = ErrorCode;
Memory<byte> buffer = Buffer;
if (buffer.Length == 0)
{
AssociatedContext._socket.RestoreBlocking();
// Invoke callback only when we are completely done.
// In case data were provided for Connect we may or may not send them all.
// If we did not we will need follow-up with Send operation
cb(bt, sa, SocketFlags.None, ec);
}
}
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
49a3f53 to
b8c9054Compareliveans
commented
Feb 13, 2026
/azp run runtime-libraries-coreclr outerloop |
|
Azure Pipelines successfully started running 1 pipeline(s). |
b8c9054 to
387bea9CompareWhen TFO sends all data during connect, remains is 0 but buffer.Length is not. Using remains == 0 correctly covers this case for SetBlocking. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs:687
- ConnectOperation.InvokeCallback only invokes the completion callback when buffer.Length == 0. For ConnectAsync with a user-provided buffer, if the connect completes with an error (so no follow-up SendToAsync is started), the callback will never be invoked and the operation can hang. Consider invoking the callback (and restoring blocking mode) when ErrorCode != SocketError.Success as well, regardless of buffer length, reserving the buffer.Length == 0 gate only for the successful connect+send case.
if (buffer.Length == 0)
{
AssociatedContext._socket.SetBlocking();
// Invoke callback only when we are completely done.
// In case data were provided for Connect we may or may not send them all.
// If we did not we will need follow-up with Send operation
cb(bt, sa, SocketFlags.None, ec);
}
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…onnect with buffer When ConnectAsync is called with a non-empty buffer and the connect fails, SendToAsync is never started, but InvokeCallback was skipping the callback because buffer.Length > 0. This caused the operation to hang. Fix by also invoking the callback when ErrorCode != Success. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
fd32493 to
dcb7f3eCompareOn macOS, TFO (connectx) may complete connect+send in a single syscall, leaving the socket in blocking mode even on the async path. Document this platform difference in the test assertion. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Uh oh!
There was an error while loading. Please reload this page.
liveans
commented
Mar 17, 2026
/ba-g failures unrelated |
Summary
On Unix, after a successful
ConnectAsynccompletion, this PR restores the underlying socket to blocking mode if the user hasn't explicitly setSocket.Blocking = false. Thisoptimizes subsequent synchronous operations (
Send/Receive) by using native blocking syscalls instead of emulating blocking on top of epoll/kqueue.When
SendAsync/ReceiveAsync(or other async I/O operations) are called later, the socket is switched back to non-blocking mode via the existingSetHandleNonBlocking()mechanism.
Motivation
Currently, once any async operation is performed on a Unix socket, the underlying file descriptor is permanently set to non-blocking mode via
fcntl(O_NONBLOCK). Subsequentsynchronous operations must then emulate blocking behavior in managed code using epoll/kqueue, which has performance overhead compared to native blocking syscalls.
This change benefits the common usage pattern: