// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Diagnostics; using System.Runtime.InteropServices; namespace System.Threading { // // Implementation of ThreadPoolBoundHandle that sits on top of the CLR's ThreadPool and Overlapped infrastructure // /// /// Represents an I/O handle that is bound to the system thread pool and enables low-level /// components to receive notifications for asynchronous I/O operations. /// public sealed partial class ThreadPoolBoundHandle : IDisposable { private readonly SafeHandle _handle; private bool _isDisposed; private ThreadPoolBoundHandle(SafeHandle handle) { _handle = handle; } /// /// Gets the bound operating system handle. /// /// /// A object that holds the bound operating system handle. /// public SafeHandle Handle { get { return _handle; } } /// /// Returns a for the specific handle, /// which is bound to the system thread pool. /// /// /// A object that holds the operating system handle. The /// handle must have been opened for overlapped I/O on the unmanaged side. /// /// /// for , which /// is bound to the system thread pool. /// /// /// is . /// /// /// has been disposed. /// /// -or- /// /// does not refer to a valid I/O handle. /// /// -or- /// /// refers to a handle that has not been opened /// for overlapped I/O. /// /// -or- /// /// refers to a handle that has already been bound. /// /// /// This method should be called once per handle. /// /// -or- /// /// does not take ownership of , /// it remains the responsibility of the caller to call . /// public static ThreadPoolBoundHandle BindHandle(SafeHandle handle) { if (handle == null) throw new ArgumentNullException(nameof(handle)); if (handle.IsClosed || handle.IsInvalid) throw new ArgumentException(SR.Argument_InvalidHandle, nameof(handle)); try { // ThreadPool.BindHandle will always return true, otherwise, it throws. See the underlying FCall // implementation in ThreadPoolNative::CorBindIoCompletionCallback to see the implementation. bool succeeded = ThreadPool.BindHandle(handle); Debug.Assert(succeeded); } catch (Exception ex) { // BindHandle throws ApplicationException on full CLR and Exception on CoreCLR. // We do not let either of these leak and convert them to ArgumentException to // indicate that the specified handles are invalid. if (ex.HResult == System.HResults.E_HANDLE) // Bad handle throw new ArgumentException(SR.Argument_InvalidHandle, nameof(handle)); if (ex.HResult == System.HResults.E_INVALIDARG) // Handle already bound or sync handle throw new ArgumentException(SR.Argument_AlreadyBoundOrSyncHandle, nameof(handle)); throw; } return new ThreadPoolBoundHandle(handle); } /// /// Returns an unmanaged pointer to a structure, specifying /// a delegate that is invoked when the asynchronous I/O operation is complete, a user-provided /// object providing context, and managed objects that serve as buffers. /// /// /// An delegate that represents the callback method /// invoked when the asynchronous I/O operation completes. /// /// /// A user-provided object that distinguishes this from other /// instances. Can be . /// /// /// An object or array of objects representing the input or output buffer for the operation. Each /// object represents a buffer, for example an array of bytes. Can be . /// /// /// An unmanaged pointer to a structure. /// /// /// /// The unmanaged pointer returned by this method can be passed to the operating system in /// overlapped I/O operations. The structure is fixed in /// physical memory until is called. /// /// /// The buffer or buffers specified in must be the same as those passed /// to the unmanaged operating system function that performs the asynchronous I/O. /// /// /// The buffers specified in are pinned for the duration of /// the I/O operation. /// /// /// /// is . /// /// /// This method was called after the was disposed. /// public unsafe NativeOverlapped* AllocateNativeOverlapped(IOCompletionCallback callback, object state, object pinData) { if (callback == null) throw new ArgumentNullException(nameof(callback)); EnsureNotDisposed(); ThreadPoolBoundHandleOverlapped overlapped = new ThreadPoolBoundHandleOverlapped(callback, state, pinData, preAllocated: null); overlapped._boundHandle = this; return overlapped._nativeOverlapped; } /// /// Returns an unmanaged pointer to a structure, using the callback, /// state, and buffers associated with the specified object. /// /// /// A object from which to create the NativeOverlapped pointer. /// /// /// An unmanaged pointer to a structure. /// /// /// /// The unmanaged pointer returned by this method can be passed to the operating system in /// overlapped I/O operations. The structure is fixed in /// physical memory until is called. /// /// /// /// is . /// /// /// is currently in use for another I/O operation. /// /// /// This method was called after the was disposed, or /// this method was called after was disposed. /// /// public unsafe NativeOverlapped* AllocateNativeOverlapped(PreAllocatedOverlapped preAllocated) { if (preAllocated == null) throw new ArgumentNullException(nameof(preAllocated)); EnsureNotDisposed(); preAllocated.AddRef(); try { ThreadPoolBoundHandleOverlapped overlapped = preAllocated._overlapped; if (overlapped._boundHandle != null) throw new ArgumentException(SR.Argument_PreAllocatedAlreadyAllocated, nameof(preAllocated)); overlapped._boundHandle = this; return overlapped._nativeOverlapped; } catch { preAllocated.Release(); throw; } } /// /// Frees the unmanaged memory associated with a structure /// allocated by the method. /// /// /// An unmanaged pointer to the structure to be freed. /// /// /// /// You must call the method exactly once /// on every unmanaged pointer allocated using the /// method. /// If you do not call the method, you will /// leak memory. If you call the method more /// than once on the same unmanaged pointer, memory will be corrupted. /// /// /// /// is . /// /// /// This method was called after the was disposed. /// public unsafe void FreeNativeOverlapped(NativeOverlapped* overlapped) { if (overlapped == null) throw new ArgumentNullException(nameof(overlapped)); // Note: we explicitly allow FreeNativeOverlapped calls after the ThreadPoolBoundHandle has been Disposed. ThreadPoolBoundHandleOverlapped wrapper = GetOverlappedWrapper(overlapped, this); if (wrapper._boundHandle != this) throw new ArgumentException(SR.Argument_NativeOverlappedWrongBoundHandle, nameof(overlapped)); if (wrapper._preAllocated != null) wrapper._preAllocated.Release(); else Overlapped.Free(overlapped); } /// /// Returns the user-provided object specified when the instance was /// allocated using the . /// /// /// An unmanaged pointer to the structure from which to return the /// asscociated user-provided object. /// /// /// A user-provided object that distinguishes this /// from other instances, otherwise, if one was /// not specified when the instance was allocated using . /// /// /// is . /// public unsafe static object GetNativeOverlappedState(NativeOverlapped* overlapped) { if (overlapped == null) throw new ArgumentNullException(nameof(overlapped)); ThreadPoolBoundHandleOverlapped wrapper = GetOverlappedWrapper(overlapped, null); Debug.Assert(wrapper._boundHandle != null); return wrapper._userState; } private static unsafe ThreadPoolBoundHandleOverlapped GetOverlappedWrapper(NativeOverlapped* overlapped, ThreadPoolBoundHandle expectedBoundHandle) { ThreadPoolBoundHandleOverlapped wrapper; try { wrapper = (ThreadPoolBoundHandleOverlapped)Overlapped.Unpack(overlapped); } catch (NullReferenceException ex) { throw new ArgumentException(SR.Argument_NativeOverlappedAlreadyFree, nameof(overlapped), ex); } return wrapper; } public void Dispose() { // .NET Native's version of ThreadPoolBoundHandle that wraps the Win32 ThreadPool holds onto // native resources so it needs to be disposable. To match the contract, we are also disposable. // We also implement a disposable state to mimic behavior between this implementation and // .NET Native's version (code written against us, will also work against .NET Native's version). _isDisposed = true; } private void EnsureNotDisposed() { if (_isDisposed) throw new ObjectDisposedException(GetType().ToString()); } } }