示例#1
0
        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();
            }
        }
示例#2
0
        /// <inheritdoc />
        public void BindFileHandle(SafeFileHandle handle)
        {
            Contract.Requires(handle != null);
            Contract.Requires(!handle.IsInvalid);

            FileSystemWin.BindFileHandleToIOCompletionPort(handle, m_completionPort, completionKey: IntPtr.Zero);
        }
示例#3
0
        /// <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);
                }
            }
        }
示例#4
0
        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;
            }
        }
示例#5
0
        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();
        }