public IOCompletionManager(int?numberOfCompletionPortThreads = null, string name = "<unnamed>") { Contract.Requires(!numberOfCompletionPortThreads.HasValue || numberOfCompletionPortThreads > 0); int effectiveNumberOfCompletionPortThreads = numberOfCompletionPortThreads ?? GetDefaultNumberOfCompletionPortThreads(); Contract.Assume(effectiveNumberOfCompletionPortThreads > 0); m_completionPort = FileSystemWin.CreateIOCompletionPort(); m_completionPortRefCount = 1; // Referenced by this manager. m_completionPortWorkers = new Thread[effectiveNumberOfCompletionPortThreads]; m_overlappedPool = new OverlappedPool(); long handleValue = m_completionPort.DangerousGetHandle().ToInt64(); ThreadStart workerEntry = CompletionWorkerThreadProc; for (int i = 0; i < effectiveNumberOfCompletionPortThreads; i++) { var newThread = new Thread(workerEntry) { Name = I($"IOCompletionManagerWorker (port handle 0x{handleValue:X}; '{name}')"), IsBackground = true }; m_completionPortWorkers[i] = newThread; newThread.Start(); } }
/// <inheritdoc /> public void BindFileHandle(SafeFileHandle handle) { Contract.Requires(handle != null); Contract.Requires(!handle.IsInvalid); FileSystemWin.BindFileHandleToIOCompletionPort(handle, m_completionPort, completionKey: IntPtr.Zero); }
/// <inheritdoc /> public unsafe void ReadFileOverlapped( IIOCompletionTarget target, SafeFileHandle handle, byte *pinnedBuffer, int bytesToRead, long fileOffset) { Contract.Requires(target != null); Contract.Requires(handle != null && !handle.IsInvalid); Contract.Requires(pinnedBuffer != null); TaggedOverlapped *overlapped = m_overlappedPool.ReserveOverlappedWithTarget(target); TraceStartIfEnabled(overlapped, target); bool needOverlappedRelease = true; try { FileAsyncIOResult result = FileSystemWin.ReadFileOverlapped( handle, pinnedBuffer, bytesToRead, fileOffset, (Overlapped *)overlapped); if (result.Status != FileAsyncIOStatus.Pending) { Contract.Assert( result.Status == FileAsyncIOStatus.Succeeded || result.Status == FileAsyncIOStatus.Failed); // We could call the target directly. // However, since the target may itself issue more I/O, we need to prevent unbounded stack growth. // TODO: We could set a recursion-limit to allow some fraction of repeated IOs to complete synchronously, without // queueing to the threadpool. Sync completions are the common case for files cached in memory. ReleaseOvelappedAndQueueCompletionNotification(overlapped, result); } // At this point overlapped is either needed (pending status) // or already released by ReleaseOvelappedAndQueueCompletionNotification needOverlappedRelease = false; } finally { if (needOverlappedRelease) { IIOCompletionTarget releasedTarget = ReleaseOverlappedAndGetTarget(overlapped); Contract.Assume(releasedTarget == target); } } }
private unsafe void CompletionWorkerThreadProc() { { int count; do { count = Volatile.Read(ref m_completionPortRefCount); if (count < 1) { // Manager disposed before this thread started. return; } }while (Interlocked.CompareExchange(ref m_completionPortRefCount, count + 1, comparand: count) != count); } try { while (true) { FileSystemWin.IOCompletionPortDequeueResult result = FileSystemWin.GetQueuedCompletionStatus(m_completionPort); Contract.Assume( result.Status != FileSystemWin.IOCompletionPortDequeueStatus.CompletionPortClosed, "We terminate all workers before closing the port (otherwise we risk a handle-recycle race)."); Contract.Assert(result.Status == FileSystemWin.IOCompletionPortDequeueStatus.Succeeded); if (result.DequeuedOverlapped == null) { // Completion port is being closed; this is a poison message. Contract.Assume(result.CompletionKey == s_queueCloseCompletionKey); break; } // The OVERLAPPED* attached to each packet is unique to each I/O request. It should be one // that we allocated earlier with AllocateOverlapped. We took care to place a request identifier // immediately after the OVERLAPPED, so we can find the completion target. Overlapped *deqeuedOverlapped = result.DequeuedOverlapped; var taggedOverlapped = (TaggedOverlapped *)deqeuedOverlapped; ReleaseOvelappedAndQueueCompletionNotification(taggedOverlapped, result.CompletedIO); } DecrementCompletionPortRefCount(); } catch (Exception ex) { ExceptionUtilities.FailFast("Catastrophic failure in I/O completion worker", ex); throw; } }
private void DisposeInternal() { // Cleanup is delicate. We must ensure that no workers are blocked on calls to GetQueuedCompletionStatus and that // no workers will newly call GetQueuedCompletionStatus, before it is safe to close the completion port handle. // Though we can inspect the result of GetQueuedCompletionStatus to determine that a port was closed *while blocked*, // we only depend on that as a sanity check that this cleanup works correctly; otherwise, we could have been unlucky // and instead tried to newly block concurrently with handle close (thus blocking on a potentially-recycled handle value). // So, we co-ordinate to ensure that all workers threads have exited before the completion port is closed. // Send enough poison messages to the port so that all workers wake up and exit. // We do this even when !disposing i.e., in the finalizer; this is okay since m_completionPort is a CriticalFinalizerObject // and therefore will have its finalizer called after this one (FileStream also depends on this for its SafeFileHandle, for example). for (int i = 0; i < m_completionPortWorkers.Length; i++) { FileSystemWin.PostQueuedCompletionStatus(m_completionPort, s_queueCloseCompletionKey); } // After all threads are finished, the reference count should end up at 1 (the manager reference). // We may have to yield to other threads to let them finish. Since we don't want to use any // synchronization calls in the finializer thread, just poll and yield. SpinWait spinner = default(SpinWait); while (Volatile.Read(ref m_completionPortRefCount) > 1) { spinner.SpinOnce(); } // All workers are expected to exit (or not start). The ref count will eventually reach 1. // Note that via these acrobatics we avoid blocking the finalizer thread on worker exit or calling anything on Thread. m_completionPortRefCount = 0; // All threads are completed and we can get rid of the pool. // This will prevent any new IO from starting. m_overlappedPool.Dispose(); // The port can be closed as well m_completionPort.Dispose(); }