Skip to content

Commit c158cb2

Browse files
authored
Check for time-sensitive work when worker thread starvation is ongoing (#61930)
- Otherwise timer callbacks may not run when worker threads are continually starved - Fix for #61804 in main
1 parent f24b363 commit c158cb2

File tree

1 file changed

+35
-15
lines changed

1 file changed

+35
-15
lines changed

src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,7 @@ public int Count
389389
}
390390

391391
internal bool loggingEnabled;
392+
private bool _dispatchTimeSensitiveWorkFirst;
392393
internal readonly ConcurrentQueue<object> workItems = new ConcurrentQueue<object>(); // SOS's ThreadPool command depends on this name
393394
internal readonly ConcurrentQueue<IThreadPoolWorkItem>? timeSensitiveWorkQueue =
394395
ThreadPool.SupportsTimeSensitiveWorkItems ? new ConcurrentQueue<IThreadPoolWorkItem>() : null;
@@ -594,27 +595,46 @@ internal static bool Dispatch()
594595
// Before dequeuing the first work item, acknowledge that the thread request has been satisfied
595596
workQueue.MarkThreadRequestSatisfied();
596597

597-
object? workItem;
598+
object? workItem = null;
598599
{
599-
bool missedSteal = false;
600-
workItem = workQueue.Dequeue(tl, ref missedSteal);
600+
#pragma warning disable CS0162 // Unreachable code detected. SupportsTimeSensitiveWorkItems may be a constant in some runtimes.
601+
if (ThreadPool.SupportsTimeSensitiveWorkItems)
602+
{
603+
// Alternate between checking for time-sensitive work or other work first, that way both sets of work items
604+
// get a chance to run in situations where worker threads are starved and work items that run also take over
605+
// the thread, sustaining starvation. For example, if time-sensitive work is always checked last here, timer
606+
// callbacks may not run when worker threads are continually starved.
607+
bool dispatchTimeSensitiveWorkFirst = workQueue._dispatchTimeSensitiveWorkFirst;
608+
workQueue._dispatchTimeSensitiveWorkFirst = !dispatchTimeSensitiveWorkFirst;
609+
if (dispatchTimeSensitiveWorkFirst)
610+
{
611+
workItem = workQueue.TryDequeueTimeSensitiveWorkItem();
612+
}
613+
}
614+
#pragma warning restore CS0162
601615

602616
if (workItem == null)
603617
{
604-
//
605-
// No work.
606-
// If we missed a steal, though, there may be more work in the queue.
607-
// Instead of looping around and trying again, we'll just request another thread. Hopefully the thread
608-
// that owns the contended work-stealing queue will pick up its own workitems in the meantime,
609-
// which will be more efficient than this thread doing it anyway.
610-
//
611-
if (missedSteal)
618+
bool missedSteal = false;
619+
workItem = workQueue.Dequeue(tl, ref missedSteal);
620+
621+
if (workItem == null)
612622
{
613-
workQueue.EnsureThreadRequested();
614-
}
623+
//
624+
// No work.
625+
// If we missed a steal, though, there may be more work in the queue.
626+
// Instead of looping around and trying again, we'll just request another thread. Hopefully the thread
627+
// that owns the contended work-stealing queue will pick up its own workitems in the meantime,
628+
// which will be more efficient than this thread doing it anyway.
629+
//
630+
if (missedSteal)
631+
{
632+
workQueue.EnsureThreadRequested();
633+
}
615634

616-
// Tell the VM we're returning normally, not because Hill Climbing asked us to return.
617-
return true;
635+
// Tell the VM we're returning normally, not because Hill Climbing asked us to return.
636+
return true;
637+
}
618638
}
619639

620640
// A work item was successfully dequeued, and there may be more work items to process. Request a thread to

0 commit comments

Comments
 (0)