Restore blocking mode after successful ConnectAsync on Unix#124200
Restore blocking mode after successful ConnectAsync on Unix#124200liveans merged 6 commits intodotnet:mainfrom
Conversation
|
Tagging subscribers to this area: @karelz, @dotnet/ncl |
src/libraries/System.Net.Sockets/tests/FunctionalTests/Connect.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Sockets/src/System/Net/Sockets/SafeSocketHandle.Unix.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Sockets/src/System/Net/Sockets/SafeSocketHandle.Unix.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Sockets/tests/FunctionalTests/Connect.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Sockets/tests/FunctionalTests/Connect.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Sockets/tests/FunctionalTests/Connect.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs
Show resolved
Hide resolved
src/libraries/System.Net.Sockets/src/System/Net/Sockets/SafeSocketHandle.Unix.cs
Show resolved
Hide resolved
🤖 Copilot Code Review — PR #124200Holistic AssessmentMotivation: The optimization goal is valid. After Approach: The implementation adds Summary: Detailed Findings
|
ba10f3d to
49a3f53
Compare
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: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);
}
}
src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs
Outdated
Show resolved
Hide resolved
49a3f53 to
b8c9054
Compare
|
/azp run runtime-libraries-coreclr outerloop |
|
Azure Pipelines successfully started running 1 pipeline(s). |
b8c9054 to
387bea9
Compare
src/libraries/System.Net.Sockets/src/System/Net/Sockets/SafeSocketHandle.Unix.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs
Outdated
Show resolved
Hide resolved
116f840 to
b8d76ea
Compare
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When 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);
}
…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
dcb7f3e
Compare
On 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>
src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs
Show resolved
Hide resolved
src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Sockets/tests/FunctionalTests/Connect.Unix.cs
Outdated
Show resolved
Hide resolved
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs
Show resolved
Hide resolved
|
/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: