Restore blocking mode after successful ConnectAsync on Unix#124200
Restore blocking mode after successful ConnectAsync on Unix#124200liveans wants to merge 1 commit 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
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);
}
}
| [Fact] | ||
| public async Task ConnectAsync_Failure_SocketIsRestoredToBlocking() | ||
| { | ||
| using Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); | ||
|
|
||
| await Assert.ThrowsAsync<SocketException>(async () => | ||
| await client.ConnectAsync(new IPEndPoint(IPAddress.Loopback, 1))); | ||
|
|
||
| Assert.False(IsSocketNonBlocking(client)); | ||
| } |
There was a problem hiding this comment.
The test suite is missing coverage for a failed ConnectAsync with a data buffer. When using ConnectEx with a buffer (via SocketAsyncEventArgs.SetBuffer), if the connection fails, the callback may not be invoked due to the bug in ConnectOperation.InvokeCallback (lines 671-688 in SocketAsyncContext.Unix.cs).
Consider adding a test that:
- Creates a SocketAsyncEventArgs with a buffer via SetBuffer
- Attempts to connect to an unreachable endpoint (e.g., port 1)
- Verifies that the Completed callback is invoked with an error
- Verifies that the socket is restored to blocking mode after the failed connect
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). |
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: