Skip to content

Commit d793aa3

Browse files
authored
Add work item dumping support to SOS' ThreadPool command (dotnet#20872)
Adds a -wi switch to the ThreadPool command that will enumerate all queues dumping out all found work items.
1 parent c60869f commit d793aa3

File tree

4 files changed

+165
-14
lines changed

4 files changed

+165
-14
lines changed

src/System.Private.CoreLib/shared/System/Collections/Concurrent/ConcurrentQueue.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public class ConcurrentQueue<T> : IProducerConsumerCollection<T>, IReadOnlyColle
5656
/// <summary>The current tail segment.</summary>
5757
private volatile ConcurrentQueueSegment<T> _tail;
5858
/// <summary>The current head segment.</summary>
59-
private volatile ConcurrentQueueSegment<T> _head;
59+
private volatile ConcurrentQueueSegment<T> _head; // SOS's ThreadPool command depends on this name
6060

6161
/// <summary>
6262
/// Initializes a new instance of the <see cref="ConcurrentQueue{T}"/> class.

src/System.Private.CoreLib/shared/System/Collections/Concurrent/ConcurrentQueueSegment.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ internal sealed class ConcurrentQueueSegment<T>
2020
// http://www.1024cores.net/home/lock-free-algorithms/queues/bounded-mpmc-queue
2121

2222
/// <summary>The array of items in this queue. Each slot contains the item in that slot and its "sequence number".</summary>
23-
internal readonly Slot[] _slots;
23+
internal readonly Slot[] _slots; // SOS's ThreadPool command depends on this name
2424
/// <summary>Mask for quickly accessing a position within the queue's array.</summary>
2525
internal readonly int _slotsMask;
2626
/// <summary>The head and tail positions, with padding to help avoid false sharing contention.</summary>
@@ -33,7 +33,7 @@ internal sealed class ConcurrentQueueSegment<T>
3333
internal bool _frozenForEnqueues;
3434
#pragma warning disable 0649 // some builds don't assign to this field
3535
/// <summary>The segment following this one in the queue, or null if this segment is the last in the queue.</summary>
36-
internal ConcurrentQueueSegment<T> _nextSegment;
36+
internal ConcurrentQueueSegment<T> _nextSegment; // SOS's ThreadPool command depends on this name
3737
#pragma warning restore 0649
3838

3939
/// <summary>Creates the segment.</summary>
@@ -315,7 +315,7 @@ public bool TryEnqueue(T item)
315315
internal struct Slot
316316
{
317317
/// <summary>The item.</summary>
318-
public T Item;
318+
public T Item; // SOS's ThreadPool command depends on this being at the beginning of the struct when T is a reference type
319319
/// <summary>The sequence number for this slot, used to synchronize between enqueuers and dequeuers.</summary>
320320
public int SequenceNumber;
321321
}

src/System.Private.CoreLib/src/System/Threading/ThreadPool.cs

+6-6
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ public static void Remove(WorkStealingQueue queue)
110110
internal sealed class WorkStealingQueue
111111
{
112112
private const int INITIAL_SIZE = 32;
113-
internal volatile object[] m_array = new object[INITIAL_SIZE];
113+
internal volatile object[] m_array = new object[INITIAL_SIZE]; // SOS's ThreadPool command depends on this name
114114
private volatile int m_mask = INITIAL_SIZE - 1;
115115

116116
#if DEBUG
@@ -377,7 +377,7 @@ public object TrySteal(ref bool missedSteal)
377377
}
378378

379379
internal bool loggingEnabled;
380-
internal readonly ConcurrentQueue<object> workItems = new ConcurrentQueue<object>();
380+
internal readonly ConcurrentQueue<object> workItems = new ConcurrentQueue<object>(); // SOS's ThreadPool command depends on this name
381381

382382
private Internal.PaddingFor32 pad1;
383383

@@ -933,7 +933,7 @@ public virtual void Execute()
933933

934934
internal sealed class QueueUserWorkItemCallback : QueueUserWorkItemCallbackBase
935935
{
936-
private WaitCallback _callback;
936+
private WaitCallback _callback; // SOS's ThreadPool command depends on this name
937937
private readonly object _state;
938938
private readonly ExecutionContext _context;
939939

@@ -972,7 +972,7 @@ public override void Execute()
972972

973973
internal sealed class QueueUserWorkItemCallback<TState> : QueueUserWorkItemCallbackBase
974974
{
975-
private Action<TState> _callback;
975+
private Action<TState> _callback; // SOS's ThreadPool command depends on this name
976976
private readonly TState _state;
977977
private readonly ExecutionContext _context;
978978

@@ -1011,7 +1011,7 @@ public override void Execute()
10111011

10121012
internal sealed class QueueUserWorkItemCallbackDefaultContext : QueueUserWorkItemCallbackBase
10131013
{
1014-
private WaitCallback _callback;
1014+
private WaitCallback _callback; // SOS's ThreadPool command depends on this name
10151015
private readonly object _state;
10161016

10171017
internal static readonly ContextCallback s_executionContextShim = state =>
@@ -1038,7 +1038,7 @@ public override void Execute()
10381038

10391039
internal sealed class QueueUserWorkItemCallbackDefaultContext<TState> : QueueUserWorkItemCallbackBase
10401040
{
1041-
private Action<TState> _callback;
1041+
private Action<TState> _callback; // SOS's ThreadPool command depends on this name
10421042
private readonly TState _state;
10431043

10441044
internal static readonly ContextCallback s_executionContextShim = state =>

src/ToolBox/SOS/Strike/strike.cpp

+155-4
Original file line numberDiff line numberDiff line change
@@ -4518,8 +4518,7 @@ DECLARE_API(DumpAsync)
45184518
int offset = GetObjFieldOffset(ar.Address, ar.MT, W("m_stateFlags"), TRUE, &stateFlagsField);
45194519
if (offset != 0)
45204520
{
4521-
sos::Object obj = TO_TADDR(ar.Address);
4522-
MOVE(ar.TaskStateFlags, obj.GetAddress() + offset);
4521+
MOVE(ar.TaskStateFlags, ar.Address + offset);
45234522
}
45244523

45254524
// Get the async state machine object's StateMachine field.
@@ -8161,18 +8160,24 @@ DECLARE_API(ThreadPool)
81618160

81628161
if ((Status = threadpool.Request(g_sos)) == S_OK)
81638162
{
8164-
BOOL doHCDump = FALSE;
8163+
BOOL doHCDump = FALSE, doWorkItemDump = FALSE, dml = FALSE;
81658164

81668165
CMDOption option[] =
81678166
{ // name, vptr, type, hasValue
8168-
{"-ti", &doHCDump, COBOOL, FALSE}
8167+
{"-ti", &doHCDump, COBOOL, FALSE},
8168+
{"-wi", &doWorkItemDump, COBOOL, FALSE},
8169+
#ifndef FEATURE_PAL
8170+
{"/d", &dml, COBOOL, FALSE},
8171+
#endif
81698172
};
81708173

81718174
if (!GetCMDOption(args, option, _countof(option), NULL, 0, NULL))
81728175
{
81738176
return Status;
81748177
}
81758178

8179+
EnableDMLHolder dmlHolder(dml);
8180+
81768181
ExtOut ("CPU utilization: %d%%\n", threadpool.cpuUtilization);
81778182
ExtOut ("Worker Thread:");
81788183
ExtOut (" Total: %d", threadpool.NumWorkingWorkerThreads + threadpool.NumIdleWorkerThreads + threadpool.NumRetiredWorkerThreads);
@@ -8215,6 +8220,152 @@ DECLARE_API(ThreadPool)
82158220
workRequestPtr = workRequestData.NextWorkRequest;
82168221
}
82178222

8223+
if (doWorkItemDump && g_snapshot.Build())
8224+
{
8225+
// Display a message if the heap isn't verified.
8226+
sos::GCHeap gcheap;
8227+
if (!gcheap.AreGCStructuresValid())
8228+
{
8229+
DisplayInvalidStructuresMessage();
8230+
}
8231+
8232+
// Walk every heap item looking for the global queue and local queues.
8233+
ExtOut("\nQueued work items:\n%" POINTERSIZE "s %" POINTERSIZE "s %s\n", "Queue", "Address", "Work Item");
8234+
HeapStat stats;
8235+
for (sos::ObjectIterator itr = gcheap.WalkHeap(); !IsInterrupt() && itr != NULL; ++itr)
8236+
{
8237+
if (_wcscmp(itr->GetTypeName(), W("System.Threading.ThreadPoolWorkQueue")) == 0)
8238+
{
8239+
// We found a global queue (there should be only one, given one AppDomain).
8240+
// Get its workItems ConcurrentQueue<IThreadPoolWorkItem>.
8241+
int offset = GetObjFieldOffset(itr->GetAddress(), itr->GetMT(), W("workItems"));
8242+
if (offset > 0)
8243+
{
8244+
DWORD_PTR workItemsConcurrentQueuePtr;
8245+
MOVE(workItemsConcurrentQueuePtr, itr->GetAddress() + offset);
8246+
if (sos::IsObject(workItemsConcurrentQueuePtr, false))
8247+
{
8248+
// We got the ConcurrentQueue. Get its head segment.
8249+
sos::Object workItemsConcurrentQueue = TO_TADDR(workItemsConcurrentQueuePtr);
8250+
offset = GetObjFieldOffset(workItemsConcurrentQueue.GetAddress(), workItemsConcurrentQueue.GetMT(), W("_head"));
8251+
if (offset > 0)
8252+
{
8253+
// Now, walk from segment to segment, each of which contains an array of work items.
8254+
DWORD_PTR segmentPtr;
8255+
MOVE(segmentPtr, workItemsConcurrentQueue.GetAddress() + offset);
8256+
while (sos::IsObject(segmentPtr, false))
8257+
{
8258+
sos::Object segment = TO_TADDR(segmentPtr);
8259+
8260+
// Get the work items array. It's an array of Slot structs, which starts with the T.
8261+
offset = GetObjFieldOffset(segment.GetAddress(), segment.GetMT(), W("_slots"));
8262+
if (offset <= 0)
8263+
{
8264+
break;
8265+
}
8266+
8267+
DWORD_PTR slotsPtr;
8268+
MOVE(slotsPtr, segment.GetAddress() + offset);
8269+
if (!sos::IsObject(slotsPtr, false))
8270+
{
8271+
break;
8272+
}
8273+
8274+
// Walk every element in the array, outputting details on non-null work items.
8275+
DacpObjectData slotsArray;
8276+
if (slotsArray.Request(g_sos, TO_CDADDR(slotsPtr)) == S_OK && slotsArray.ObjectType == OBJ_ARRAY)
8277+
{
8278+
for (int i = 0; i < slotsArray.dwNumComponents; i++)
8279+
{
8280+
CLRDATA_ADDRESS workItemPtr;
8281+
MOVE(workItemPtr, TO_CDADDR(slotsArray.ArrayDataPtr + (i * slotsArray.dwComponentSize))); // the item object reference is at the beginning of the Slot
8282+
if (workItemPtr != NULL && sos::IsObject(workItemPtr, false))
8283+
{
8284+
sos::Object workItem = TO_TADDR(workItemPtr);
8285+
stats.Add((DWORD_PTR)workItem.GetMT(), (DWORD)workItem.GetSize());
8286+
DMLOut("%" POINTERSIZE "s %s %S", "[Global]", DMLObject(workItem.GetAddress()), workItem.GetTypeName());
8287+
if ((offset = GetObjFieldOffset(workItem.GetAddress(), workItem.GetMT(), W("_callback"))) > 0 ||
8288+
(offset = GetObjFieldOffset(workItem.GetAddress(), workItem.GetMT(), W("m_action"))) > 0)
8289+
{
8290+
CLRDATA_ADDRESS delegatePtr;
8291+
MOVE(delegatePtr, workItem.GetAddress() + offset);
8292+
CLRDATA_ADDRESS md;
8293+
if (TryGetMethodDescriptorForDelegate(delegatePtr, &md))
8294+
{
8295+
NameForMD_s((DWORD_PTR)md, g_mdName, mdNameLen);
8296+
ExtOut(" => %S", g_mdName);
8297+
}
8298+
}
8299+
ExtOut("\n");
8300+
}
8301+
}
8302+
}
8303+
8304+
// Move to the next segment.
8305+
DacpFieldDescData segmentField;
8306+
offset = GetObjFieldOffset(segment.GetAddress(), segment.GetMT(), W("_nextSegment"), TRUE, &segmentField);
8307+
if (offset <= 0)
8308+
{
8309+
break;
8310+
}
8311+
8312+
MOVE(segmentPtr, segment.GetAddress() + offset);
8313+
if (segmentPtr == NULL)
8314+
{
8315+
break;
8316+
}
8317+
}
8318+
}
8319+
}
8320+
}
8321+
}
8322+
else if (_wcscmp(itr->GetTypeName(), W("System.Threading.ThreadPoolWorkQueue+WorkStealingQueue")) == 0)
8323+
{
8324+
// We found a local queue. Get its work items array.
8325+
int offset = GetObjFieldOffset(itr->GetAddress(), itr->GetMT(), W("m_array"));
8326+
if (offset > 0)
8327+
{
8328+
// Walk every element in the array, outputting details on non-null work items.
8329+
DWORD_PTR workItemArrayPtr;
8330+
MOVE(workItemArrayPtr, itr->GetAddress() + offset);
8331+
DacpObjectData workItemArray;
8332+
if (workItemArray.Request(g_sos, TO_CDADDR(workItemArrayPtr)) == S_OK && workItemArray.ObjectType == OBJ_ARRAY)
8333+
{
8334+
for (int i = 0; i < workItemArray.dwNumComponents; i++)
8335+
{
8336+
CLRDATA_ADDRESS workItemPtr;
8337+
MOVE(workItemPtr, TO_CDADDR(workItemArray.ArrayDataPtr + (i * workItemArray.dwComponentSize)));
8338+
if (workItemPtr != NULL && sos::IsObject(workItemPtr, false))
8339+
{
8340+
sos::Object workItem = TO_TADDR(workItemPtr);
8341+
stats.Add((DWORD_PTR)workItem.GetMT(), (DWORD)workItem.GetSize());
8342+
DMLOut("%s %s %S", DMLObject(itr->GetAddress()), DMLObject(workItem.GetAddress()), workItem.GetTypeName());
8343+
if ((offset = GetObjFieldOffset(workItem.GetAddress(), workItem.GetMT(), W("_callback"))) > 0 ||
8344+
(offset = GetObjFieldOffset(workItem.GetAddress(), workItem.GetMT(), W("m_action"))) > 0)
8345+
{
8346+
CLRDATA_ADDRESS delegatePtr;
8347+
MOVE(delegatePtr, workItem.GetAddress() + offset);
8348+
CLRDATA_ADDRESS md;
8349+
if (TryGetMethodDescriptorForDelegate(delegatePtr, &md))
8350+
{
8351+
NameForMD_s((DWORD_PTR)md, g_mdName, mdNameLen);
8352+
ExtOut(" => %S", g_mdName);
8353+
}
8354+
}
8355+
ExtOut("\n");
8356+
}
8357+
}
8358+
}
8359+
}
8360+
}
8361+
}
8362+
8363+
// Output a summary.
8364+
stats.Sort();
8365+
stats.Print();
8366+
ExtOut("\n");
8367+
}
8368+
82188369
if (doHCDump)
82198370
{
82208371
ExtOut ("--------------------------------------\n");

0 commit comments

Comments
 (0)