Skip to content

TcpListener regression: SocketException at InputShare app #41585

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
jiangzeng01 opened this issue Aug 31, 2020 · 10 comments · Fixed by #41745
Closed

TcpListener regression: SocketException at InputShare app #41585

jiangzeng01 opened this issue Aug 31, 2020 · 10 comments · Fixed by #41745
Assignees
Labels
area-System.Net.Sockets bug tenet-compatibility Incompatibility with previous versions or .NET Framework
Milestone

Comments

@jiangzeng01
Copy link
Contributor

jiangzeng01 commented Aug 31, 2020

Application Name: Input Share
OS: Windows 10 RS5
CPU: X64
.NET Build Number: 5.0.100-rc.1.20430.2

Verify Scenarios:
1). Windows 10 RS5 X64 + .NET Core SDK build 5.0.100-rc.1.20430.2: Fail
2). Windows 10 RS5 X64 + .NET Core SDK build 5.0.100-preview.8 : Pass
3). Windows 10 RS5 X64 + .NET Core SDK build 3.1.300 Pass

DevDiv bug:
https://devdiv.visualstudio.com/DefaultCollection/DevDiv/_workitems/edit/1188270
Minimal Repro App:
https://github.com/jiangzeng01/socketexceptionrepro.git

Findings :
When we stop TcpListener, it BeingAccesSocket call back, it gets different exception in net5 rc1, it makes the app crash :
We start listener like this :

private static TcpListener listener;
listener = new TcpListener(IPAddress.Any, 4441);
listener.Start(6);
listener.BeginAcceptSocket(Listener_AcceptSocketCallback, null);
label1.Text = "started listening";

We stop like this :

listener.Stop();

In callBack function (Listener_AcceptSocketCallback) : 

private static void Listener_AcceptSocketCallback(IAsyncResult ar)
{
try
{
Socket client = listener.EndAcceptSocket(ar);
}
catch (ObjectDisposedException ex)
{
MessageBox.Show("ObjectDisposedException on 3.1 and 5 prev");
//caught on 3.1 and 5 prev, doesn't catch on .net5 rc1
}
catch (Exception ex)
{
 //since we stop it, we think ObjectDisposedException should be caught, but it comes to here in   //.net5 Rc1
  MessageBox.Show($"Catch on .net5 RC {ex.Message}");
 	listener.BeginAcceptSocket(Listener_AcceptSocketCallback, null);
}
}

Exception from InputShare app:

ISClientListener-> An error occurred while accepting client socket: The I/O operation has been aborted because of either a thread exit or an application request.
--------------------------------------
UNHANDLED EXCEPTION
Not listening. You must call the Start() method before calling this method.
   at System.Net.Sockets.TcpListener.AcceptSocketAsync()
   at System.Net.Sockets.TcpListener.BeginAcceptSocket(AsyncCallback callback, Object state)
   at InputshareLib.Net.ISClientListener.Listener_AcceptSocketCallback(IAsyncResult ar) in C:\Users\Appcompat\Documents\inputsharesource\Inputshare\InputshareLib\Net\ISClientListener.cs:line 72
   at System.Threading.Tasks.TaskToApm.TaskAsyncResult.InvokeCallback()
   at System.Threading.Tasks.AwaitTaskContinuation.<>c.<.cctor>b__17_0(Object state)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location ---
   at System.Threading.Tasks.AwaitTaskContinuation.RunCallback(ContextCallback callback, Object state, Task& currentTask)
--- End of stack trace from previous location ---
   at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__140_1(Object state)
   at System.Threading.QueueUserWorkItemCallbackDefaultContext.Execute()
   at System.Threading.ThreadPoolWorkQueue.Dispatch()
System.Net.Sockets

CC @dotnet-actwx-bot

@ghost
Copy link

ghost commented Aug 31, 2020

Tagging subscribers to this area: @dotnet/ncl
See info in area-owners.md if you want to be subscribed.

@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added the untriaged New issue has not been triaged by the area owner label Aug 31, 2020
@antonfirsov antonfirsov self-assigned this Aug 31, 2020
@karelz karelz added this to the 5.0.0 milestone Aug 31, 2020
@karelz karelz added bug and removed untriaged New issue has not been triaged by the area owner labels Aug 31, 2020
@karelz karelz changed the title [5.0.100-rc.1.20430.2] SocketException at InputShare app Regression: SocketException at InputShare app Aug 31, 2020
@antonfirsov
Copy link
Member

antonfirsov commented Aug 31, 2020

TcpClient propagates the exception from Socket, where the related Accept behavior has been changed in #40476.

There is a big inconsistency between the various code paths (Sync, APM, EAP/Task) when disposing a socket during accept. Only the APM path seems to throw ObjectDisposedException:

EAP/Task System.Net.Sockets.SocketException The I/O operation has been aborted because of either a thread exit or an application request. (995)
Sync System.Net.Sockets.SocketException A blocking operation was interrupted by a call to WSACancelBlockingCall. (10004)
APM System.ObjectDisposedException Cannot access a disposed object.

Repro: add the following test to Accept.cs:

[Fact]
public async Task AcceptAsync_WhenDisposed_ThrowsObjectDisposedException()
{
    Socket listen = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    int port = listen.BindToAnonymousPort(IPAddress.Loopback);
    listen.Listen(1);

    using Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    
    Task<Socket> acceptTask = AcceptAsync(listen);

    // give some time for the OS calls:
    await Task.Delay(100);
    listen.Dispose();
    _ = client.ConnectAsync(new IPEndPoint(IPAddress.Loopback, port));

    Exception ex = await Assert.ThrowsAnyAsync<Exception>(() => acceptTask);

    string socketErrorCode = ex is SocketException sx ? $"({sx.ErrorCode})" : "";
    _output.WriteLine($"{ex.GetType()} | {ex.Message} {socketErrorCode}");
}

@geoffkizer @scalablecory should we synchronize the behavior for 5.0? If yes, should we map to ObjectDisposedException in all those cases?

@antonfirsov antonfirsov changed the title Regression: SocketException at InputShare app Socket/TcpClient: inconsistent exceptions being thrown when closing during Accept ("Regression: SocketException at InputShare app") Aug 31, 2020
@antonfirsov antonfirsov changed the title Socket/TcpClient: inconsistent exceptions being thrown when closing during Accept ("Regression: SocketException at InputShare app") Socket/TcpClient: inconsistent exceptions when closing during Accept ("Regression: SocketException at InputShare app") Aug 31, 2020
@antonfirsov antonfirsov changed the title Socket/TcpClient: inconsistent exceptions when closing during Accept ("Regression: SocketException at InputShare app") Socket: inconsistent exceptions when closing during Accept / TcpClient regression: SocketException at InputShare app Aug 31, 2020
@antonfirsov antonfirsov changed the title Socket: inconsistent exceptions when closing during Accept / TcpClient regression: SocketException at InputShare app Socket: inconsistent exceptions when closing during Accept / TcpListener regression: SocketException at InputShare app Aug 31, 2020
@scalablecory
Copy link
Contributor

scalablecory commented Aug 31, 2020

It's expected for Task-based async to throw with SocketError.OperationAborted; we shouldn't change that. Actually I'm surprised that APM doesn't. Are you sure you aren't seeing a pre-disposed socket VS an I/O in progress and then a dispose?

Do we have a regression from 3.x or is this just things being different?

@antonfirsov
Copy link
Member

antonfirsov commented Sep 1, 2020

Are you sure you aren't seeing a pre-disposed socket VS an I/O in progress and then a dispose?

@scalablecory if my understanding is correct, with APM there is no way / no point to distinguish those cases from API users perspective. The exception is always being thrown when EndAccept(IAR) gets called. This is how it is implemented currently:

public Socket EndAccept(IAsyncResult asyncResult)
{
return EndAcceptCommon(out _, out _, asyncResult);
}
private Socket EndAcceptCommon(out byte[]? buffer, out int bytesTransferred, IAsyncResult asyncResult)
{
ThrowIfDisposed();

From the perspective of how APM API-s work, it seems actually logical to throw ObjectDisposedException here.

Since we delegate the exception throwing logic from TcpListener to Socket now, this exception is no longer thrown since BeginAccept / EndAccept utilizes the EAP Socket path now in TcpListener. This is an unexprected breaking change (="regression") in 5.0.

Maybe I'm wrong here and exceptions should not be consistent, but we need a decision ASAP. If we can't clarify this, maybe we should just revert #40476 to prevent the breaking change. The inconsistency between the implementation (Socket VS TcpListener) is probably not a very good state for the product code anyways.

@geoffkizer
Copy link
Contributor

Making this work like 3.1 is easy enough, we should just add the ThrowIfDisposed check to EndAccept.

I agree that it would be nice to clean up our exceptions to be more consistent here -- we should look at that for 6.0. But right now let's just address this particular issue.

@geoffkizer
Copy link
Contributor

What's particularly weird about this is that TcpListener does not actually implement IDisposable, yet we throw ObjectDisposedException anyway. Strange.

@antonfirsov antonfirsov added the tenet-compatibility Incompatibility with previous versions or .NET Framework label Sep 3, 2020
antonfirsov added a commit that referenced this issue Sep 7, 2020
According to the old (pre- #40476) code and the documentation, we should throw ObjectDisposedException in EndAcceptSocket and EndAcceptTcpClient.

Fixes #41585
@antonfirsov antonfirsov changed the title Socket: inconsistent exceptions when closing during Accept / TcpListener regression: SocketException at InputShare app TcpListener regression: SocketException at InputShare app Sep 7, 2020
@jiangzeng01
Copy link
Contributor Author

jiangzeng01 commented Sep 8, 2020

@antonfirsov @karelz Just want to confirm will the fix go into RC1 branch? I saw you have mentioned in the PR that the fix will not included in RC1 and document it as known issue.

@antonfirsov
Copy link
Member

@jiangzeng01 unfortunately the change will land in RC2 only.

@karelz
Copy link
Member

karelz commented Sep 8, 2020

@jiangzeng01 that is correct, this should be only documented as a known issue for RC1.

@karelz
Copy link
Member

karelz commented Sep 10, 2020

Fixed in 6.0 (PR #41745) and in 5.0 RC2 (PR #41938) ... 5.0 RC1 will be only documented as known issue.

@ghost ghost locked as resolved and limited conversation to collaborators Dec 7, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Net.Sockets bug tenet-compatibility Incompatibility with previous versions or .NET Framework
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants