runtime: Closing the AsyncWaitHandle from the result of socket.BeginConnect crashes the application in .NET 6.0

Description

The simple code below works on all versions of .NET before 6.0, but crashes with “Unhandled exception. System.ObjectDisposedException: Safe handle has been closed.” on net6.0:

using System;
using System.Net;
using System.Net.Sockets;

namespace Net60SafeHandleBug
{
    class Program
    {
        static void Main(string[] args)
        {
            int port = 1234;
            IPAddress.TryParse("127.0.0.1", out IPAddress ipAddress);
            Socket socket = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            IAsyncResult asyncResult = socket.BeginConnect(ipAddress, port, null, null);
            // do something, then suddenly quit...
            socket.Close();
            asyncResult.AsyncWaitHandle.Close(); // <-- ruh roh

            Console.WriteLine("Press any key to quit...");
            Console.ReadKey();
        }
    }
}

Reproduction Steps

Build the above code as a console app. It will crash when targeting net6.0, but work fine with any older version of .NET.

Expected behavior

The application should succeed. The documentation for IAsyncResult.AsyncWaitHandle does not mention that one must wait for the handle to be signaled or call the APM End* method before closing, but it does say that the handle should be closed when the application is done using it.

Actual behavior

The application crashes with the following:

System.ObjectDisposedException
  HResult=0x80131622
  Message=Safe handle has been closed.
Object name: 'SafeHandle'.
  Source=System.Private.CoreLib
  StackTrace:
   at System.Runtime.InteropServices.SafeHandle.DangerousAddRef(Boolean& success)
   at Interop.Kernel32.SetEvent(SafeWaitHandle handle)
   at System.Threading.EventWaitHandle.Set()
   at System.Threading.ManualResetEventSlim.Set(Boolean duringCancellation)
   at System.Threading.Tasks.Task.FinishStageTwo()
   at System.Threading.Tasks.Task.FinishSlow(Boolean userDelegateExecute)
   at System.Threading.Tasks.Task.TrySetException(Object exceptionObject)
   at System.Threading.Tasks.ValueTask.ValueTaskSourceAsTask.<>c.<.cctor>b__4_0(Object state)
   at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.InvokeContinuation(Action`1 continuation, Object state, Boolean forceAsync, Boolean requiresExecutionContextFlow)
   at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.OnCompleted(SocketAsyncEventArgs _)
   at System.Net.Sockets.SocketAsyncEventArgs.OnCompletedInternal()
   at System.Net.Sockets.SocketAsyncEventArgs.HandleCompletionPortCallbackError(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* nativeOverlapped)
   at System.Net.Sockets.SocketAsyncEventArgs.<>c.<.cctor>b__179_0(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* nativeOverlapped)
   at System.Threading.ThreadPoolBoundHandleOverlapped.CompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* nativeOverlapped)
   at System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pNativeOverlapped)

Regression?

This is a regression in .NET 6.0, because it works in all the prior versions.

Known Workarounds

No response

Configuration

Tested on net461, netcoreapp2.0, net5.0, and net6.0 running on Windows 10 (x64). Did not test on other platforms.

Other information

No response

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 21 (10 by maintainers)

Most upvoted comments

We believe that Socket.Close() is supposed to cancel or abort any outstanding APM operation: “The Close method closes the remote host connection and releases all managed and unmanaged resources associated with the Socket.” We shouldn’t have to wait for the outstanding socket operations to complete, and since there’s no other way to cancel the BeginConnect, Socket.Close() is supposed to do that, and this is how it has worked since the early days of .NET Framework. After Socket.Close is called, there should be no operation attempting to set the wait handle, so we should be able to close the handle.