void IDispatchHost.IncreaseThreadCount(string reason) { // check if thread pool is already awaiting another thread if (_threadVelocity > 0) { return; } lock (_syncRoot) { _threadVelocity = 1; // check if thread pool has enough threads if (_threadCount >= _maxParallelThreads) { _threadVelocity = 0; return; } #if EXTRA_DEBUG _log.DebugFormat("IncreaseThreadCount: {1} - {0}", this, reason); #endif } // check if there are threads in the reserve KeyValuePair <DispatchThread, Result <DispatchWorkItem> > reservedThread; if (_reservedThreads.TryPop(out reservedThread)) { AddThread(reservedThread); } else { DispatchThreadScheduler.RequestThread(0, AddThread); } }
private void RemoveThread(string reason, DispatchThread thread, Result <DispatchWorkItem> result) { if (thread == null) { throw new ArgumentNullException("thread"); } if (result == null) { throw new ArgumentNullException("result"); } if (thread.PendingWorkItemCount != 0) { throw new ArgumentException(string.Format("thread #{1} still has work-items in queue (items: {0})", thread.PendingWorkItemCount, thread.Id), "thread"); } // remove thread from list of allocated threads lock (_syncRoot) { _threadVelocity = 0; UnregisterThread(reason, thread); } // check if we can put thread into the reserved list if (_reservedThreads.Count < MinReservedThreads) { if (!_reservedThreads.TryPush(new KeyValuePair <DispatchThread, Result <DispatchWorkItem> >(thread, result))) { throw new NotSupportedException("TryPush failed"); } } else { // return thread to resource manager DispatchThreadScheduler.ReleaseThread(thread, result); } }
/// <summary> /// Shutdown the ElasticThreadPool instance. This method blocks until all pending items have finished processing. /// </summary> public void Dispose() { if (!_disposed) { _disposed = true; _log.DebugFormat("Dispose @{0}", this); // TODO (steveb): make dispose more reliable // 1) we can't wait indefinitively! // 2) we should progressively sleep longer and longer to avoid unnecessary overhead // 3) this pattern feels useful enough to be captured into a helper method // wait until all threads have been decommissioned while (ThreadCount > 0) { AsyncUtil.Sleep(100.Milliseconds()); } // discard all reserved threads KeyValuePair <DispatchThread, Result <DispatchWorkItem> > reserved; while (_reservedThreads.TryPop(out reserved)) { DispatchThreadScheduler.ReleaseThread(reserved.Key, reserved.Value); } DispatchThreadScheduler.UnregisterHost(this); } }
//--- Constructors --- /// <summary> /// Creates a new ElasticThreadPool instance. /// </summary> /// <param name="minReservedThreads">Minium number of threads to reserve for the thread pool.</param> /// <param name="maxParallelThreads">Maximum number of parallel threads used by the thread pool.</param> /// <exception cref="InsufficientResourcesException">The ElasticThreadPool instance was unable to obtain the minimum reserved threads.</exception> public ElasticThreadPool(int minReservedThreads, int maxParallelThreads) { _minReservedThreads = Math.Max(0, Math.Min(minReservedThreads, MAX_RESERVED_THREADS)); _maxParallelThreads = Math.Max(Math.Max(1, minReservedThreads), Math.Min(maxParallelThreads, int.MaxValue)); // initialize reserved threads _activeThreads = new DispatchThread[Math.Min(_maxParallelThreads, Math.Max(_minReservedThreads, Math.Min(16, _maxParallelThreads)))]; if (_minReservedThreads > 0) { DispatchThreadScheduler.RequestThread(_minReservedThreads, AddThread); } DispatchThreadScheduler.RegisterHost(this); _log.DebugFormat("Create @{0}", this); }
// (yurig): This method has been intentionally split from DispatchLoop() // result.Block() inside causes Result<DispatchWorkItem> to never be disposed. // By moving it into its own method we ensure its garbage collection as it is // popped off the stack. Do NOT inline this method into DispatchLoop(). private bool GetNextWorkItem(out Action callback) { if (!_inbox.TryPop(out callback)) { var result = new Result <DispatchWorkItem>(TimeSpan.MaxValue); // reset the dispatch queue for this thread AsyncUtil.CurrentDispatchQueue = null; _queue = null; // check if thread is associated with a host already if (_host == null) { // NOTE (steveb): this is a brand new thread without a host yet // return the thread to the dispatch scheduler DispatchThreadScheduler.ReleaseThread(this, result); } else { // request another work-item _host.RequestWorkItem(this, result); } // block until a work item is available result.Block(); // check if we received a work item or an exception to shutdown if (result.HasException && (result.Exception is DispatchThreadShutdownException)) { // time to shut down _log.DebugFormat("DispatchThread #{0} destroyed", _id); return(false); } callback = result.Value.WorkItem; _queue = result.Value.DispatchQueue; // TODO (steveb): handle the weird case where _queue is null // set the dispatch queue for this thread AsyncUtil.CurrentDispatchQueue = _queue; } return(true); }
//--- Constructors --- /// <summary> /// Creates a new ElasticPriorityThreadPool instance. /// </summary> /// <param name="minReservedThreads">Minium number of threads to reserve for the thread pool.</param> /// <param name="maxParallelThreads">Maximum number of parallel threads used by the thread pool.</param> /// <param name="maxPriority">Maximum priority number (inclusive upper bound).</param> /// <exception cref="InsufficientResourcesException">The ElasticPriorityThreadPool instance was unable to obtain the minimum reserved threads.</exception> public ElasticPriorityThreadPool(int minReservedThreads, int maxParallelThreads, int maxPriority) { _minReservedThreads = Math.Max(0, Math.Min(minReservedThreads, MAX_RESERVED_THREADS)); _maxParallelThreads = Math.Max(Math.Max(1, minReservedThreads), Math.Min(maxParallelThreads, int.MaxValue)); _inbox = new LockFreePriorityQueue <Action>(maxPriority); _prioritizedInbox = new PrioritizedThreadPool[maxPriority]; for (int i = 0; i < maxPriority; ++i) { _prioritizedInbox[i] = new PrioritizedThreadPool(i, this); } // initialize reserved threads _activeThreads = new DispatchThread[Math.Min(_maxParallelThreads, Math.Max(_minReservedThreads, Math.Min(16, _maxParallelThreads)))]; if (_minReservedThreads > 0) { DispatchThreadScheduler.RequestThread(_minReservedThreads, AddThread); } DispatchThreadScheduler.RegisterHost(this); _log.DebugFormat("Create @{0}", this); }
private void DispatchLoop() { // set thread-local self-reference CurrentThread = this; // begin thread loop try { while (true) { // check if queue has a work-item Action callback; if (!_inbox.TryPop(out callback)) { var result = new Result <DispatchWorkItem>(TimeSpan.MaxValue); // reset the dispatch queue for this thread AsyncUtil.CurrentDispatchQueue = null; // check if thread is associated with a host already if (_host == null) { // NOTE (steveb): this is a brand new thread without a host yet // return the thread to the dispatch scheduler DispatchThreadScheduler.ReleaseThread(this, result); } else { // request another work-item _host.RequestWorkItem(this, result); } // block until a work item is available result.Block(); // check if we received a work item or an exception to shutdown if (result.HasException && (result.Exception is DispatchThreadShutdownException)) { // time to shut down _log.DebugFormat("DispatchThread #{0} destroyed", _id); return; } callback = result.Value.WorkItem; _queue = result.Value.DispatchQueue; // TODO (steveb): handle the weird case where _queue is null // set the dispatch queue for this thread AsyncUtil.CurrentDispatchQueue = _queue; } // execute work-item if (callback != null) { try { callback(); } catch (Exception e) { _log.Warn("an unhandled exception occurred while executing the work-item", e); } } } } catch (Exception e) { // something went wrong that shouldn't have! _log.ErrorExceptionMethodCall(e, string.Format("DispatchLoop #{0}: FATAL ERROR", _id)); // TODO (steveb): tell _host about untimely exit; post items in queue to host inbox (o/w we lose them) } finally { CurrentThread = null; } }