diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.EventWaitHandle.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.EventWaitHandle.cs index 012baaeaf55b35..e5cc4322de2ccc 100644 --- a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.EventWaitHandle.cs +++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.EventWaitHandle.cs @@ -23,6 +23,9 @@ internal static partial class Kernel32 [LibraryImport(Libraries.Kernel32, EntryPoint = "CreateEventExW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)] internal static partial SafeWaitHandle CreateEventEx(IntPtr lpSecurityAttributes, string? name, uint flags, uint desiredAccess); + [LibraryImport(Libraries.Kernel32, EntryPoint = "CreateEventExW", SetLastError = true)] + internal static partial IntPtr CreateEventExNoName(IntPtr lpSecurityAttributes, IntPtr name, uint flags, uint desiredAccess); + [LibraryImport(Libraries.Kernel32, EntryPoint = "OpenEventW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)] internal static partial SafeWaitHandle OpenEvent(uint desiredAccess, [MarshalAs(UnmanagedType.Bool)] bool inheritHandle, string name); } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.Windows.cs index ad3614376ab396..8d1a016bc037f5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.Windows.cs @@ -16,8 +16,6 @@ namespace System.IO { public static partial class RandomAccess { - private static readonly IOCompletionCallback s_callback = AllocateCallback(); - internal static unsafe void SetFileLength(SafeFileHandle handle, long length) { var eofInfo = new Interop.Kernel32.FILE_END_OF_FILE_INFO @@ -69,30 +67,22 @@ _ when IsEndOfFile(errorCode, handle, fileOffset) => 0, private static unsafe int ReadSyncUsingAsyncHandle(SafeFileHandle handle, Span buffer, long fileOffset) { - handle.EnsureThreadPoolBindingInitialized(); - - CallbackResetEvent resetEvent = new CallbackResetEvent(handle.ThreadPoolBinding!); + IntPtr eventHandle = CreateSyncOverlappedEvent(); NativeOverlapped* overlapped = null; try { - overlapped = GetNativeOverlappedForAsyncHandle(handle, fileOffset, resetEvent); + overlapped = AllocNativeOverlappedWithEventHandle(handle, fileOffset, eventHandle); fixed (byte* pinned = &MemoryMarshal.GetReference(buffer)) { Interop.Kernel32.ReadFile(handle, pinned, buffer.Length, IntPtr.Zero, overlapped); int errorCode = FileStreamHelpers.GetLastWin32ErrorAndDisposeHandleIfInvalid(handle); - if (errorCode == Interop.Errors.ERROR_IO_PENDING) - { - resetEvent.WaitOne(); - errorCode = Interop.Errors.ERROR_SUCCESS; - } - - if (errorCode == Interop.Errors.ERROR_SUCCESS) + if (errorCode is Interop.Errors.ERROR_IO_PENDING or Interop.Errors.ERROR_SUCCESS) { int result = 0; - if (Interop.Kernel32.GetOverlappedResult(handle, overlapped, ref result, bWait: false)) + if (Interop.Kernel32.GetOverlappedResult(handle, overlapped, ref result, bWait: true)) { Debug.Assert(result >= 0 && result <= buffer.Length, $"GetOverlappedResult returned {result} for {buffer.Length} bytes request"); return result; @@ -100,21 +90,9 @@ private static unsafe int ReadSyncUsingAsyncHandle(SafeFileHandle handle, SpanInternalLow = IntPtr.Zero; return 0; } @@ -125,10 +103,10 @@ private static unsafe int ReadSyncUsingAsyncHandle(SafeFileHandle handle, SpanEventHandle = resetEvent.SafeWaitHandle.DangerousGetHandle(); + // From https://learn.microsoft.com/windows/win32/api/ioapiset/nf-ioapiset-getqueuedcompletionstatus: + // "If the file handle associated with the completion packet was previously associated with an I/O completion port + // [...] setting the low-order bit of hEvent in the OVERLAPPED structure prevents the I/O completion + // from being queued to a completion port." + result->EventHandle = eventHandle | 1; return result; } @@ -761,17 +741,6 @@ private static NativeOverlapped GetNativeOverlappedForSyncHandle(SafeFileHandle return result; } - private static unsafe IOCompletionCallback AllocateCallback() - { - return new IOCompletionCallback(Callback); - - static void Callback(uint errorCode, uint numBytes, NativeOverlapped* pOverlapped) - { - CallbackResetEvent state = (CallbackResetEvent)ThreadPoolBoundHandle.GetNativeOverlappedState(pOverlapped)!; - state.ReleaseRefCount(pOverlapped); - } - } - internal static bool IsEndOfFile(int errorCode, SafeFileHandle handle, long fileOffset) { switch (errorCode) @@ -798,32 +767,6 @@ internal static bool IsEndOfFile(int errorCode, SafeFileHandle handle, long file private static bool IsEndOfFileForNoBuffering(SafeFileHandle fileHandle, long fileOffset) => fileHandle.IsNoBuffering && fileHandle.CanSeek && fileOffset >= fileHandle.GetFileLength(); - // We need to store the reference count (see the comment in ReleaseRefCount) and an EventHandle to signal the completion. - // We could keep these two things separate, but since ManualResetEvent is sealed and we want to avoid any extra allocations, this type has been created. - // It's basically ManualResetEvent with reference count. - private sealed class CallbackResetEvent : EventWaitHandle - { - private readonly ThreadPoolBoundHandle _threadPoolBoundHandle; - private int _freeWhenZero = 2; // one for the callback and another for the method that calls GetOverlappedResult - - internal CallbackResetEvent(ThreadPoolBoundHandle threadPoolBoundHandle) : base(initialState: false, EventResetMode.ManualReset) - { - _threadPoolBoundHandle = threadPoolBoundHandle; - } - - internal unsafe void ReleaseRefCount(NativeOverlapped* pOverlapped) - { - // Each SafeFileHandle opened for async IO is bound to ThreadPool. - // It requires us to provide a callback even if we want to use EventHandle and use GetOverlappedResult to obtain the result. - // There can be a race condition between the call to GetOverlappedResult and the callback invocation, - // so we need to track the number of references, and when it drops to zero, then free the native overlapped. - if (Interlocked.Decrement(ref _freeWhenZero) == 0) - { - _threadPoolBoundHandle.FreeNativeOverlapped(pOverlapped); - } - } - } - // Abstracts away the type signature incompatibility between Memory and ReadOnlyMemory. private interface IMemoryHandler {