Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Make counting of IO completion work items more precise on Windows
- Follow-up to #106854. Issue: #104284.
- Before the change, the modified test case often yields 5 or 6 completed work items, due to queue-processing work items that happen to not process any user work items. After the change, it always yields 4.
- Looks like it doesn't hurt to have more-precise counting, and there was a request to backport a fix to .NET 8, where it's more necessary to fix the issue
  • Loading branch information
Koundinya Veluri committed Feb 21, 2025
commit a544893f68fd291d68a37b330ac49131f87cef56
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ public static void Increment(object threadLocalCountObject)
Unsafe.As<ThreadLocalNode>(threadLocalCountObject).Increment();
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decrement(object threadLocalCountObject)
{
Debug.Assert(threadLocalCountObject is ThreadLocalNode);
Unsafe.As<ThreadLocalNode>(threadLocalCountObject).Decrement();
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Add(object threadLocalCountObject, uint count)
{
Expand Down Expand Up @@ -134,6 +141,18 @@ public void Increment()
OnAddOverflow(1);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Decrement()
{
if (_count != 0)
{
_count--;
return;
}

OnAddOverflow(-1);
}

public void Add(uint count)
{
Debug.Assert(count != 0);
Expand All @@ -149,7 +168,7 @@ public void Add(uint count)
}

[MethodImpl(MethodImplOptions.NoInlining)]
private void OnAddOverflow(uint count)
private void OnAddOverflow(long count)
{
Debug.Assert(count != 0);

Expand All @@ -161,7 +180,7 @@ private void OnAddOverflow(uint count)
counter._lock.Acquire();
try
{
counter._overflowCount += (long)_count + count;
counter._overflowCount += _count + count;
_count = 0;
}
finally
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,20 @@ public static long CompletedWorkItemCount
{
get
{
return PortableThreadPool.ThreadPoolInstance.CompletedWorkItemCount;
long count = PortableThreadPool.ThreadPoolInstance.CompletedWorkItemCount;

// Ensure that the returned value is monotonically increasing
long lastCount = s_lastCompletedWorkItemCount;
if (count > lastCount)
{
s_lastCompletedWorkItemCount = count;
}
else
{
count = lastCount;
}

return count;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,23 @@ public static long CompletedWorkItemCount
{
get
{
return ThreadPool.UseWindowsThreadPool ? WindowsThreadPool.CompletedWorkItemCount : PortableThreadPool.ThreadPoolInstance.CompletedWorkItemCount;
long count =
UseWindowsThreadPool
? WindowsThreadPool.CompletedWorkItemCount
: PortableThreadPool.ThreadPoolInstance.CompletedWorkItemCount;

// Ensure that the returned value is monotonically increasing
long lastCount = s_lastCompletedWorkItemCount;
if (count > lastCount)
{
s_lastCompletedWorkItemCount = count;
}
else
{
count = lastCount;
}

return count;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1204,6 +1204,9 @@ void IThreadPoolWorkItem.Execute()
Interlocked.MemoryBarrier();
if (!_workItems.TryDequeue(out T workItem))
{
// Discount a work item here to avoid counting this queue processing work item
ThreadInt64PersistentCounter.Decrement(
ThreadPoolWorkQueueThreadLocals.threadLocals!.threadLocalCompletionCountObject!);
return;
}

Expand Down Expand Up @@ -1247,7 +1250,7 @@ void IThreadPoolWorkItem.Execute()
currentThread.ResetThreadPoolThread();
}

// Discount a work item here to avoid counting most of the queue processing work items
// Discount a work item here to avoid counting this queue processing work item
if (completedCount > 1)
{
ThreadInt64PersistentCounter.Add(tl.threadLocalCompletionCountObject!, completedCount - 1);
Expand Down Expand Up @@ -1454,6 +1457,8 @@ public static partial class ThreadPool

internal static readonly ThreadPoolWorkQueue s_workQueue = new ThreadPoolWorkQueue();

private static long s_lastCompletedWorkItemCount;

/// <summary>Shim used to invoke <see cref="IAsyncStateMachineBox.MoveNext"/> of the supplied <see cref="IAsyncStateMachineBox"/>.</summary>
internal static readonly Action<object?> s_invokeAsyncStateMachineBox = static state =>
{
Expand Down
31 changes: 20 additions & 11 deletions src/libraries/System.Threading.ThreadPool/tests/ThreadPoolTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1273,22 +1273,31 @@ public static unsafe void ThreadPoolCompletedWorkItemCountTest()
// Run in a separate process to test in a clean thread pool environment such that we don't count external work items
RemoteExecutor.Invoke(() =>
{
using var manualResetEvent = new ManualResetEventSlim(false);
const int WorkItemCount = 4;

var overlapped = new Overlapped();
NativeOverlapped* nativeOverlapped = overlapped.Pack((errorCode, numBytes, innerNativeOverlapped) =>
int completedWorkItemCount = 0;
using var allWorkItemsCompleted = new AutoResetEvent(false);

IOCompletionCallback callback =
(errorCode, numBytes, innerNativeOverlapped) =>
{
Overlapped.Free(innerNativeOverlapped);
if (Interlocked.Increment(ref completedWorkItemCount) == WorkItemCount)
{
allWorkItemsCompleted.Set();
}
};
for (int i = 0; i < WorkItemCount; i++)
{
Overlapped.Free(innerNativeOverlapped);
manualResetEvent.Set();
}, null);
ThreadPool.UnsafeQueueNativeOverlapped(new Overlapped().Pack(callback, null));
}

ThreadPool.UnsafeQueueNativeOverlapped(nativeOverlapped);
manualResetEvent.Wait();
allWorkItemsCompleted.CheckedWait();

// Allow work item(s) to be marked as completed during this time, should be only one
ThreadTestHelpers.WaitForCondition(() => ThreadPool.CompletedWorkItemCount == 1);
// Allow work items to be marked as completed during this time
ThreadTestHelpers.WaitForCondition(() => ThreadPool.CompletedWorkItemCount >= WorkItemCount);
Thread.Sleep(50);
Assert.Equal(1, ThreadPool.CompletedWorkItemCount);
Assert.Equal(WorkItemCount, ThreadPool.CompletedWorkItemCount);
}).Dispose();
}

Expand Down