Socket ConnectAsync returns successfully with a non-connected socket if canceled
Description
Socket.ConnetAsync sporadically returns successfully but the socket is not connected (Socket.Connected == false). This occurs when canceling ConnectAsync while it's in progress after the server side accepts the socket.
Reproduction Steps
using System.Net;
using System.Net.Sockets;
while (true)
{
using var serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
serverSocket.Bind(new IPEndPoint(IPAddress.Loopback, 0));
serverSocket.Listen();
using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
using var cts = new CancellationTokenSource();
ValueTask connectTask = socket.ConnectAsync(serverSocket.LocalEndPoint!, cts.Token);
using var _ = await serverSocket.AcceptAsync();
cts.Cancel();
try
{
await connectTask;
if (!socket.Connected)
{
Console.Error.WriteLine("unexpected non-connected socket after successfull ConnectAsync");
return 1;
}
}
catch (OperationCanceledException)
{
// Expected if canceled before connect.
}
}
Expected behavior
The ConnectAsync call should either throw OperationCanceledException or return a connected socket.
Actual behavior
ConnectAsync returns but the socket is not connected
Regression?
No response
Known Workarounds
No response
Configuration
.NET Core 6 Linux Debian Buster and macOS
Other information
No response
Tagging subscribers to this area: @dotnet/ncl See info in area-owners.md if you want to be subscribed.
Issue Details
Description
Socket.ConnetAsync sporadically returns successfully but the socket is not connected (Socket.Connected == false). This occurs when canceling ConnectAsync while it's in progress after the server side accepts the socket.
Reproduction Steps
using System.Net;
using System.Net.Sockets;
while (true)
{
using var serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
serverSocket.Bind(new IPEndPoint(IPAddress.Loopback, 0));
serverSocket.Listen();
using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
using var cts = new CancellationTokenSource();
ValueTask connectTask = socket.ConnectAsync(serverSocket.LocalEndPoint!, cts.Token);
using var _ = await serverSocket.AcceptAsync();
cts.Cancel();
try
{
await connectTask;
if (!socket.Connected)
{
Console.Error.WriteLine("unexpected non-connected socket after successfull ConnectAsync");
return 1;
}
}
catch (OperationCanceledException)
{
// Expected if canceled before connect.
}
}
Expected behavior
The ConnectAsync call should either throw OperationCanceledException or return a connected socket.
Actual behavior
ConnectAsync returns but the socket is not connected
Regression?
No response
Known Workarounds
No response
Configuration
.NET Core 6 Linux Debian Buster and macOS
Other information
No response
| Author: | bentoi |
|---|---|
| Assignees: | - |
| Labels: |
|
| Milestone: | - |
I think on macOS this can happen when peer closes the connection. Can you perhaps post complete repro @bentoi that has both sides?
It has both sides already. The test case creates a server socket to accept connections over the loopback interface. The accepted server socket is not disposed while the client socket connection establishment is in progress or canceled. Not that this occurs on both Linux and macOS. I didn't get a chance to check Windows.
I was able to reproduce the issue in main, .NET 7.0, 6.0 and 5.0. I think it should be easy to fix. Not critical. We can investigate it more deeply.
Triage: It is unpleasant behavior, however, it is the only report in last few years (it is an edge case scenario).
Hi. With a slightly different repro, I have been able to get disposed Sockets in Connected=true state. Could this be a potential socket leaking case?
using System.Net;
using System.Net.Sockets;
using System.Reflection;
var disposedProperty = typeof(Socket).GetProperty("Disposed", BindingFlags.NonPublic | BindingFlags.Instance);
while (true)
{
using var serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
serverSocket.Bind(new IPEndPoint(IPAddress.Loopback, 0));
serverSocket.Listen();
using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
using var cts = new CancellationTokenSource();
ValueTask connectTask = socket.ConnectAsync(serverSocket.LocalEndPoint!, cts.Token);
using var _ = await serverSocket.AcceptAsync();
cts.Cancel();
try
{
await connectTask;
var disposed = (bool)disposedProperty.GetValue(socket);
if (disposed && socket.Connected)
{
Console.WriteLine($"Disposed and Connected");
}
}
catch (Exception e)
{
}
}