From adb5877856a26118f26d8da83c59162561858489 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Tue, 19 Feb 2019 13:30:41 -0800 Subject: [PATCH 01/11] Expose and test APIs for some threading metrics (CoreFX) - API review: https://github.com/dotnet/corefx/issues/35500 - Depends on https://github.com/dotnet/coreclr/pull/22754, https://github.com/dotnet/corert/pull/7066 --- .../System/Threading/ThreadTestHelpers.cs | 17 ++- .../tests/Configurations.props | 1 + .../System.Threading.Overlapped.Tests.csproj | 8 +- ...BoundHandle_IntegrationTests.netcoreapp.cs | 19 ++++ .../System.Threading.Thread.Tests.csproj | 2 +- .../tests/ThreadTests.netcoreapp.cs | 1 + .../ref/System.Threading.ThreadPool.cs | 3 + .../tests/Configurations.props | 1 + .../System.Threading.ThreadPool.Tests.csproj | 4 +- .../tests/ThreadPoolTests.netcoreapp.cs | 105 ++++++++++++++++++ .../tests/Configurations.props | 1 + .../tests/System.Threading.Timer.Tests.csproj | 4 +- src/System.Threading/ref/System.Threading.cs | 1 + .../tests/Configurations.props | 1 + .../tests/MonitorTests.netcoreapp.cs | 23 ++++ .../tests/System.Threading.Tests.csproj | 7 +- 16 files changed, 186 insertions(+), 12 deletions(-) create mode 100644 src/System.Threading.Overlapped/tests/ThreadPoolBoundHandle_IntegrationTests.netcoreapp.cs create mode 100644 src/System.Threading/tests/MonitorTests.netcoreapp.cs diff --git a/src/Common/tests/System/Threading/ThreadTestHelpers.cs b/src/Common/tests/System/Threading/ThreadTestHelpers.cs index d5735754ff1b..ee33e2b9fc51 100644 --- a/src/Common/tests/System/Threading/ThreadTestHelpers.cs +++ b/src/Common/tests/System/Threading/ThreadTestHelpers.cs @@ -125,11 +125,22 @@ public static void WaitForConditionWithoutBlocking(Func condition) public static void WaitForConditionWithCustomDelay(Func condition, Action delay) { - var startTime = DateTime.Now; - while (!condition()) + if (condition()) + { + return; + } + + var startTime = Environment.TickCount; + while (true) { - Assert.True((DateTime.Now - startTime).TotalMilliseconds < UnexpectedTimeoutMilliseconds); delay(); + + if (condition()) + { + return; + } + + Assert.True(Environment.TickCount - startTime < UnexpectedTimeoutMilliseconds); } } diff --git a/src/System.Threading.Overlapped/tests/Configurations.props b/src/System.Threading.Overlapped/tests/Configurations.props index e31e3bc489fb..ba03084f3192 100644 --- a/src/System.Threading.Overlapped/tests/Configurations.props +++ b/src/System.Threading.Overlapped/tests/Configurations.props @@ -1,6 +1,7 @@  + netcoreapp; netstandard; uap-Windows_NT; diff --git a/src/System.Threading.Overlapped/tests/System.Threading.Overlapped.Tests.csproj b/src/System.Threading.Overlapped/tests/System.Threading.Overlapped.Tests.csproj index 5fc24ef59604..7ac20c89c97f 100644 --- a/src/System.Threading.Overlapped/tests/System.Threading.Overlapped.Tests.csproj +++ b/src/System.Threading.Overlapped/tests/System.Threading.Overlapped.Tests.csproj @@ -2,13 +2,14 @@ {861A3318-35AD-46ac-8257-8D5D2479BAD9} true - netstandard-Debug;netstandard-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release + netcoreapp-Debug;netcoreapp-Release;netstandard-Debug;netstandard-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release true + @@ -21,4 +22,9 @@ + + + CommonTest\System\Threading\ThreadTestHelpers.cs + + \ No newline at end of file diff --git a/src/System.Threading.Overlapped/tests/ThreadPoolBoundHandle_IntegrationTests.netcoreapp.cs b/src/System.Threading.Overlapped/tests/ThreadPoolBoundHandle_IntegrationTests.netcoreapp.cs new file mode 100644 index 000000000000..7b0aebbd4a53 --- /dev/null +++ b/src/System.Threading.Overlapped/tests/ThreadPoolBoundHandle_IntegrationTests.netcoreapp.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; +using System.Threading.Tests; +using Xunit; + +public partial class ThreadPoolBoundHandleTests +{ + [Fact] + [PlatformSpecific(TestPlatforms.Windows)] // ThreadPoolBoundHandle.BindHandle is not supported on Unix + public unsafe void MultipleOperationsOverSingleHandle_CompletedWorkItemCountTest() + { + long initialCompletedWorkItemCount = ThreadPool.CompletedWorkItemCount; + MultipleOperationsOverMultipleHandles(); + ThreadTestHelpers.WaitForCondition(() => ThreadPool.CompletedWorkItemCount - initialCompletedWorkItemCount >= 2); + } +} diff --git a/src/System.Threading.Thread/tests/System.Threading.Thread.Tests.csproj b/src/System.Threading.Thread/tests/System.Threading.Thread.Tests.csproj index 1b617e47d9e6..4cbbd6a58611 100644 --- a/src/System.Threading.Thread/tests/System.Threading.Thread.Tests.csproj +++ b/src/System.Threading.Thread/tests/System.Threading.Thread.Tests.csproj @@ -17,7 +17,7 @@ - CommonTest\System\Threading\ThreadPoolHelpers.cs + CommonTest\System\Threading\ThreadTestHelpers.cs STAMain diff --git a/src/System.Threading.Thread/tests/ThreadTests.netcoreapp.cs b/src/System.Threading.Thread/tests/ThreadTests.netcoreapp.cs index 79e306397a75..d8f9c84138e2 100644 --- a/src/System.Threading.Thread/tests/ThreadTests.netcoreapp.cs +++ b/src/System.Threading.Thread/tests/ThreadTests.netcoreapp.cs @@ -8,6 +8,7 @@ namespace System.Threading.Threads.Tests { public static partial class ThreadTests { + [Fact] public static void GetCurrentProcessorId() { Assert.True(Thread.GetCurrentProcessorId() >= 0); diff --git a/src/System.Threading.ThreadPool/ref/System.Threading.ThreadPool.cs b/src/System.Threading.ThreadPool/ref/System.Threading.ThreadPool.cs index ea6804807460..56fe570dd5de 100644 --- a/src/System.Threading.ThreadPool/ref/System.Threading.ThreadPool.cs +++ b/src/System.Threading.ThreadPool/ref/System.Threading.ThreadPool.cs @@ -21,9 +21,11 @@ public static partial class ThreadPool [System.ObsoleteAttribute("ThreadPool.BindHandle(IntPtr) has been deprecated. Please use ThreadPool.BindHandle(SafeHandle) instead.", false)] public static bool BindHandle(System.IntPtr osHandle) { throw null; } public static bool BindHandle(System.Runtime.InteropServices.SafeHandle osHandle) { throw null; } + public static long CompletedWorkItemCount { get { throw null; } } public static void GetAvailableThreads(out int workerThreads, out int completionPortThreads) { throw null; } public static void GetMaxThreads(out int workerThreads, out int completionPortThreads) { throw null; } public static void GetMinThreads(out int workerThreads, out int completionPortThreads) { throw null; } + public static long PendingWorkItemCount { get { throw null; } } public static bool QueueUserWorkItem(System.Threading.WaitCallback callBack) { throw null; } public static bool QueueUserWorkItem(System.Threading.WaitCallback callBack, object state) { throw null; } public static bool QueueUserWorkItem(System.Action callBack, TState state, bool preferLocal) { throw null; } @@ -34,6 +36,7 @@ public static partial class ThreadPool public static System.Threading.RegisteredWaitHandle RegisterWaitForSingleObject(System.Threading.WaitHandle waitObject, System.Threading.WaitOrTimerCallback callBack, object state, uint millisecondsTimeOutInterval, bool executeOnlyOnce) { throw null; } public static bool SetMaxThreads(int workerThreads, int completionPortThreads) { throw null; } public static bool SetMinThreads(int workerThreads, int completionPortThreads) { throw null; } + public static int ThreadCount { get { throw null; } } [System.CLSCompliantAttribute(false)] public unsafe static bool UnsafeQueueNativeOverlapped(System.Threading.NativeOverlapped* overlapped) { throw null; } public static bool UnsafeQueueUserWorkItem(System.Threading.IThreadPoolWorkItem callBack, bool preferLocal) { throw null; } diff --git a/src/System.Threading.ThreadPool/tests/Configurations.props b/src/System.Threading.ThreadPool/tests/Configurations.props index f5d3d45ffbf4..2ae79c79febf 100644 --- a/src/System.Threading.ThreadPool/tests/Configurations.props +++ b/src/System.Threading.ThreadPool/tests/Configurations.props @@ -3,6 +3,7 @@ netcoreapp; netstandard; + uap; \ No newline at end of file diff --git a/src/System.Threading.ThreadPool/tests/System.Threading.ThreadPool.Tests.csproj b/src/System.Threading.ThreadPool/tests/System.Threading.ThreadPool.Tests.csproj index fe7245c4389a..1762ff82ddc8 100644 --- a/src/System.Threading.ThreadPool/tests/System.Threading.ThreadPool.Tests.csproj +++ b/src/System.Threading.ThreadPool/tests/System.Threading.ThreadPool.Tests.csproj @@ -2,12 +2,12 @@ {403AD1B8-6F95-4A2E-92A2-727606ABD866} true - netcoreapp-Debug;netcoreapp-Release;netstandard-Debug;netstandard-Release + netcoreapp-Debug;netcoreapp-Release;netstandard-Debug;netstandard-Release;uap-Debug;uap-Release true - + diff --git a/src/System.Threading.ThreadPool/tests/ThreadPoolTests.netcoreapp.cs b/src/System.Threading.ThreadPool/tests/ThreadPoolTests.netcoreapp.cs index 2ddd1f422bcc..c0a405f7a644 100644 --- a/src/System.Threading.ThreadPool/tests/ThreadPoolTests.netcoreapp.cs +++ b/src/System.Threading.ThreadPool/tests/ThreadPoolTests.netcoreapp.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using System.Threading.Tests; using Xunit; namespace System.Threading.ThreadPools.Tests @@ -188,5 +189,109 @@ private sealed class InvalidWorkItemAndTask : Task, IThreadPoolWorkItem public InvalidWorkItemAndTask(Action action) : base(action) { } public void Execute() { } } + + [Fact] + public void MetricsTest() + { + int processorCount = Environment.ProcessorCount; + + var workStarted = new AutoResetEvent(false); + int completeWork = 0; + int simultaneousWorkCount = 0; + var allWorkCompleted = new ManualResetEvent(false); + Exception backgroundEx = null; + Action work = () => + { + workStarted.Set(); + try + { + // Blocking can affect thread pool thread injection heuristics, so don't block, pretend like a + // long-running CPU-bound work item + ThreadTestHelpers.WaitForConditionWithCustomDelay( + () => Interlocked.CompareExchange(ref completeWork, 0, 0) != 0, + () => Thread.SpinWait(1)); + } + catch (Exception ex) + { + Interlocked.CompareExchange(ref backgroundEx, ex, null); + } + finally + { + if (Interlocked.Decrement(ref simultaneousWorkCount) == 0) + { + allWorkCompleted.Set(); + } + } + }; + WaitCallback threadPoolWork = data => work(); + TimerCallback timerWork = data => work(); + WaitOrTimerCallback waitWork = (data, timedOut) => work(); + + var signaledEvent = new ManualResetEvent(true); + var timers = new List(); + int maxSimultaneousWorkCount = 0; + Action scheduleWork = () => + { + Assert.True(simultaneousWorkCount <= maxSimultaneousWorkCount); + + while (true) + { + if (simultaneousWorkCount >= maxSimultaneousWorkCount) + { + break; + } + ++simultaneousWorkCount; + ThreadPool.QueueUserWorkItem(threadPoolWork); + workStarted.CheckedWait(); + + if (simultaneousWorkCount >= maxSimultaneousWorkCount) + { + break; + } + ++simultaneousWorkCount; + timers.Add(new Timer(timerWork, null, 1, Timeout.Infinite)); + workStarted.CheckedWait(); + + if (simultaneousWorkCount >= maxSimultaneousWorkCount) + { + break; + } + ++simultaneousWorkCount; + ThreadPool.RegisterWaitForSingleObject( + signaledEvent, + waitWork, + null, + ThreadTestHelpers.UnexpectedTimeoutMilliseconds, + true); + workStarted.CheckedWait(); + } + + Assert.Equal(maxSimultaneousWorkCount, simultaneousWorkCount); + }; + + long initialCompletedWorkItemCount = ThreadPool.CompletedWorkItemCount; + + // Schedule some simultaneous work that would all be scheduled and verify the thread count + maxSimultaneousWorkCount = Math.Max(1, processorCount - 1); // minus one in case this thread is a thread pool thread + scheduleWork(); + Assert.True(ThreadPool.ThreadCount >= maxSimultaneousWorkCount); + + // Schedule more work that would not all be scheduled and roughly verify the pending work item count + maxSimultaneousWorkCount = processorCount * 8; + scheduleWork(); + Assert.True(ThreadPool.PendingWorkItemCount >= processorCount); + + // Complete the work and verify the completed work item count + Interlocked.Exchange(ref completeWork, 1); + allWorkCompleted.CheckedWait(); + backgroundEx = Interlocked.CompareExchange(ref backgroundEx, null, null); + if (backgroundEx != null) + { + throw new AggregateException(backgroundEx); + } + // Wait for work items to exit, for counting + ThreadTestHelpers.WaitForCondition(() => + ThreadPool.CompletedWorkItemCount - initialCompletedWorkItemCount >= maxSimultaneousWorkCount); + } } } diff --git a/src/System.Threading.Timer/tests/Configurations.props b/src/System.Threading.Timer/tests/Configurations.props index f5d3d45ffbf4..2ae79c79febf 100644 --- a/src/System.Threading.Timer/tests/Configurations.props +++ b/src/System.Threading.Timer/tests/Configurations.props @@ -3,6 +3,7 @@ netcoreapp; netstandard; + uap; \ No newline at end of file diff --git a/src/System.Threading.Timer/tests/System.Threading.Timer.Tests.csproj b/src/System.Threading.Timer/tests/System.Threading.Timer.Tests.csproj index d4b83860cfa4..8ffa44e4b6d0 100644 --- a/src/System.Threading.Timer/tests/System.Threading.Timer.Tests.csproj +++ b/src/System.Threading.Timer/tests/System.Threading.Timer.Tests.csproj @@ -1,7 +1,7 @@ {ac20a28f-fda8-45e8-8728-058ead16e44c} - netcoreapp-Debug;netcoreapp-Release;netstandard-Debug;netstandard-Release + netcoreapp-Debug;netcoreapp-Release;netstandard-Debug;netstandard-Release;uap-Debug;uap-Release true true @@ -10,7 +10,7 @@ - + diff --git a/src/System.Threading/ref/System.Threading.cs b/src/System.Threading/ref/System.Threading.cs index 66bb5f5f7b4f..b65709a4b442 100644 --- a/src/System.Threading/ref/System.Threading.cs +++ b/src/System.Threading/ref/System.Threading.cs @@ -228,6 +228,7 @@ public static void Enter(object obj) { } public static void Enter(object obj, ref bool lockTaken) { } public static void Exit(object obj) { } public static bool IsEntered(object obj) { throw null; } + public static long LockContentionCount { get { throw null; } } public static void Pulse(object obj) { } public static void PulseAll(object obj) { } public static bool TryEnter(object obj) { throw null; } diff --git a/src/System.Threading/tests/Configurations.props b/src/System.Threading/tests/Configurations.props index febffb03ac69..90185507ad77 100644 --- a/src/System.Threading/tests/Configurations.props +++ b/src/System.Threading/tests/Configurations.props @@ -3,6 +3,7 @@ netstandard; netcoreapp; + uap; \ No newline at end of file diff --git a/src/System.Threading/tests/MonitorTests.netcoreapp.cs b/src/System.Threading/tests/MonitorTests.netcoreapp.cs new file mode 100644 index 000000000000..2650596516c9 --- /dev/null +++ b/src/System.Threading/tests/MonitorTests.netcoreapp.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace System.Threading.Tests +{ + public static class MonitorTests + { + [Fact] + public static void Enter_HasToWait_LockContentionCountTest() + { + long initialLockContentionCount = Monitor.LockContentionCount; + Enter_HasToWait(); + Assert.True(Monitor.LockContentionCount - initialLockContentionCount >= 2); + } + } +} diff --git a/src/System.Threading/tests/System.Threading.Tests.csproj b/src/System.Threading/tests/System.Threading.Tests.csproj index 5054c23ddd63..d95b7b7599dc 100644 --- a/src/System.Threading/tests/System.Threading.Tests.csproj +++ b/src/System.Threading/tests/System.Threading.Tests.csproj @@ -3,7 +3,7 @@ {18EF66B3-51EE-46D8-B283-1CB6A1197813} true true - netcoreapp-Debug;netcoreapp-Release;netstandard-Debug;netstandard-Release + netcoreapp-Debug;netcoreapp-Release;netstandard-Debug;netstandard-Release;uap-Debug;uap-Release @@ -15,13 +15,14 @@ - + + @@ -30,7 +31,7 @@ - + From 9d067c6dd6a8f3b0e96fec0635219732686963eb Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Wed, 6 Mar 2019 12:17:52 -0800 Subject: [PATCH 02/11] Separate and expose pending local vs global work item count --- .../System/Threading/ThreadTestHelpers.cs | 5 +++ .../ref/System.Threading.ThreadPool.cs | 2 ++ .../tests/ThreadPoolTests.netcoreapp.cs | 31 ++++++++++++++++--- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/Common/tests/System/Threading/ThreadTestHelpers.cs b/src/Common/tests/System/Threading/ThreadTestHelpers.cs index ee33e2b9fc51..20c90a0e1fbd 100644 --- a/src/Common/tests/System/Threading/ThreadTestHelpers.cs +++ b/src/Common/tests/System/Threading/ThreadTestHelpers.cs @@ -123,6 +123,11 @@ public static void WaitForConditionWithoutBlocking(Func condition) WaitForConditionWithCustomDelay(condition, () => Thread.Yield()); } + public static void WaitForConditionWithoutRelinquishingTimeSlice(Func condition) + { + WaitForConditionWithCustomDelay(condition, () => Thread.SpinWait(1)); + } + public static void WaitForConditionWithCustomDelay(Func condition, Action delay) { if (condition()) diff --git a/src/System.Threading.ThreadPool/ref/System.Threading.ThreadPool.cs b/src/System.Threading.ThreadPool/ref/System.Threading.ThreadPool.cs index 56fe570dd5de..9a3815746e64 100644 --- a/src/System.Threading.ThreadPool/ref/System.Threading.ThreadPool.cs +++ b/src/System.Threading.ThreadPool/ref/System.Threading.ThreadPool.cs @@ -25,6 +25,8 @@ public static partial class ThreadPool public static void GetAvailableThreads(out int workerThreads, out int completionPortThreads) { throw null; } public static void GetMaxThreads(out int workerThreads, out int completionPortThreads) { throw null; } public static void GetMinThreads(out int workerThreads, out int completionPortThreads) { throw null; } + public static long PendingLocalWorkItemCount { get { throw null; } } + public static long PendingGlobalWorkItemCount { get { throw null; } } public static long PendingWorkItemCount { get { throw null; } } public static bool QueueUserWorkItem(System.Threading.WaitCallback callBack) { throw null; } public static bool QueueUserWorkItem(System.Threading.WaitCallback callBack, object state) { throw null; } diff --git a/src/System.Threading.ThreadPool/tests/ThreadPoolTests.netcoreapp.cs b/src/System.Threading.ThreadPool/tests/ThreadPoolTests.netcoreapp.cs index c0a405f7a644..d7fe0bda003b 100644 --- a/src/System.Threading.ThreadPool/tests/ThreadPoolTests.netcoreapp.cs +++ b/src/System.Threading.ThreadPool/tests/ThreadPoolTests.netcoreapp.cs @@ -198,6 +198,8 @@ public void MetricsTest() var workStarted = new AutoResetEvent(false); int completeWork = 0; int simultaneousWorkCount = 0; + int simultaneousLocalWorkCount = 0; + int simultaneousGlobalWorkCount = 0; var allWorkCompleted = new ManualResetEvent(false); Exception backgroundEx = null; Action work = () => @@ -207,9 +209,8 @@ public void MetricsTest() { // Blocking can affect thread pool thread injection heuristics, so don't block, pretend like a // long-running CPU-bound work item - ThreadTestHelpers.WaitForConditionWithCustomDelay( - () => Interlocked.CompareExchange(ref completeWork, 0, 0) != 0, - () => Thread.SpinWait(1)); + ThreadTestHelpers.WaitForConditionWithoutRelinquishingTimeSlice( + () => Interlocked.CompareExchange(ref completeWork, 0, 0) != 0); } catch (Exception ex) { @@ -224,6 +225,7 @@ public void MetricsTest() } }; WaitCallback threadPoolWork = data => work(); + Action threadPoolLocalWork = data => work(); TimerCallback timerWork = data => work(); WaitOrTimerCallback waitWork = (data, timedOut) => work(); @@ -241,6 +243,7 @@ public void MetricsTest() break; } ++simultaneousWorkCount; + ++simultaneousGlobalWorkCount; ThreadPool.QueueUserWorkItem(threadPoolWork); workStarted.CheckedWait(); @@ -249,6 +252,16 @@ public void MetricsTest() break; } ++simultaneousWorkCount; + ++simultaneousLocalWorkCount; + ThreadPool.QueueUserWorkItem(threadPoolLocalWork, null, preferLocal: true); + workStarted.CheckedWait(); + + if (simultaneousWorkCount >= maxSimultaneousWorkCount) + { + break; + } + ++simultaneousWorkCount; + ++simultaneousGlobalWorkCount; timers.Add(new Timer(timerWork, null, 1, Timeout.Infinite)); workStarted.CheckedWait(); @@ -257,6 +270,7 @@ public void MetricsTest() break; } ++simultaneousWorkCount; + ++simultaneousGlobalWorkCount; ThreadPool.RegisterWaitForSingleObject( signaledEvent, waitWork, @@ -277,9 +291,16 @@ public void MetricsTest() Assert.True(ThreadPool.ThreadCount >= maxSimultaneousWorkCount); // Schedule more work that would not all be scheduled and roughly verify the pending work item count - maxSimultaneousWorkCount = processorCount * 8; + maxSimultaneousWorkCount = processorCount * 64; scheduleWork(); - Assert.True(ThreadPool.PendingWorkItemCount >= processorCount); + // The following is assuming that no more than (processorCount * 8) work items will be scheduled to run + // simultaneously + int minExpectedPendingLocalWorkCount = Math.Max(1, simultaneousLocalWorkCount - processorCount * 8); + int minExpectedPendingGlobalWorkCount = Math.Max(1, simultaneousGlobalWorkCount - processorCount * 8); + int minExpectedPendingWorkCount = minExpectedPendingLocalWorkCount + minExpectedPendingGlobalWorkCount; + Assert.True(ThreadPool.PendingLocalWorkItemCount >= minExpectedPendingLocalWorkCount); + Assert.True(ThreadPool.PendingGlobalWorkItemCount >= minExpectedPendingGlobalWorkCount); + Assert.True(ThreadPool.PendingWorkItemCount >= minExpectedPendingWorkCount); // Complete the work and verify the completed work item count Interlocked.Exchange(ref completeWork, 1); From abc1e2caabcf27eae089ce9d7e2d45cf8f4423c3 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Tue, 2 Apr 2019 14:46:51 -0700 Subject: [PATCH 03/11] Remove local/global variants of PendingWorkItemCount --- .../ref/System.Threading.ThreadPool.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/System.Threading.ThreadPool/ref/System.Threading.ThreadPool.cs b/src/System.Threading.ThreadPool/ref/System.Threading.ThreadPool.cs index 9a3815746e64..56fe570dd5de 100644 --- a/src/System.Threading.ThreadPool/ref/System.Threading.ThreadPool.cs +++ b/src/System.Threading.ThreadPool/ref/System.Threading.ThreadPool.cs @@ -25,8 +25,6 @@ public static partial class ThreadPool public static void GetAvailableThreads(out int workerThreads, out int completionPortThreads) { throw null; } public static void GetMaxThreads(out int workerThreads, out int completionPortThreads) { throw null; } public static void GetMinThreads(out int workerThreads, out int completionPortThreads) { throw null; } - public static long PendingLocalWorkItemCount { get { throw null; } } - public static long PendingGlobalWorkItemCount { get { throw null; } } public static long PendingWorkItemCount { get { throw null; } } public static bool QueueUserWorkItem(System.Threading.WaitCallback callBack) { throw null; } public static bool QueueUserWorkItem(System.Threading.WaitCallback callBack, object state) { throw null; } From 14f4575138686aa54f42499e04c1b5bee8704ebf Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Tue, 2 Apr 2019 14:49:15 -0700 Subject: [PATCH 04/11] Remove unrelated test --- .../tests/System.Threading.Timer.Tests.csproj | 5 -- .../tests/TimerFiringTests.cs | 54 ------------------- 2 files changed, 59 deletions(-) diff --git a/src/System.Threading.Timer/tests/System.Threading.Timer.Tests.csproj b/src/System.Threading.Timer/tests/System.Threading.Timer.Tests.csproj index 8ffa44e4b6d0..6575f1bfa4a0 100644 --- a/src/System.Threading.Timer/tests/System.Threading.Timer.Tests.csproj +++ b/src/System.Threading.Timer/tests/System.Threading.Timer.Tests.csproj @@ -13,9 +13,4 @@ - - - CommonTest\System\Threading\ThreadTestHelpers.cs - - \ No newline at end of file diff --git a/src/System.Threading.Timer/tests/TimerFiringTests.cs b/src/System.Threading.Timer/tests/TimerFiringTests.cs index 375df334653b..00006f68f0df 100644 --- a/src/System.Threading.Timer/tests/TimerFiringTests.cs +++ b/src/System.Threading.Timer/tests/TimerFiringTests.cs @@ -10,7 +10,6 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -using System.Threading.Tests; using Xunit; using Xunit.Sdk; @@ -338,57 +337,4 @@ private static async Task PeriodAsync(int period, int iterations) await tcs.Task.ConfigureAwait(false); } } - - [Fact] - public void TimersCreatedConcurrentlyOnDifferentThreadsAllFire() - { - int processorCount = Environment.ProcessorCount; - - int timerTickCount = 0; - TimerCallback timerCallback = _ => Interlocked.Increment(ref timerTickCount); - - var threadStarted = new AutoResetEvent(false); - var createTimers = new ManualResetEvent(false); - var timers = new Timer[processorCount]; - Action createTimerThreadStart = data => - { - int i = (int)data; - var sw = new Stopwatch(); - threadStarted.Set(); - createTimers.WaitOne(); - - // Use the CPU a bit around creating the timer to try to have some of these threads run concurrently - sw.Restart(); - do - { - Thread.SpinWait(1000); - } while (sw.ElapsedMilliseconds < 10); - - timers[i] = new Timer(timerCallback, null, 1, Timeout.Infinite); - - // Use the CPU a bit around creating the timer to try to have some of these threads run concurrently - sw.Restart(); - do - { - Thread.SpinWait(1000); - } while (sw.ElapsedMilliseconds < 10); - }; - - var waitsForThread = new Action[timers.Length]; - for (int i = 0; i < timers.Length; ++i) - { - var t = ThreadTestHelpers.CreateGuardedThread(out waitsForThread[i], createTimerThreadStart); - t.IsBackground = true; - t.Start(i); - threadStarted.CheckedWait(); - } - - createTimers.Set(); - ThreadTestHelpers.WaitForCondition(() => timerTickCount == timers.Length); - - foreach (var waitForThread in waitsForThread) - { - waitForThread(); - } - } } From 4fa06e588262097cdab699efe0adaf8d42eb0b80 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Wed, 3 Apr 2019 06:00:01 -0700 Subject: [PATCH 05/11] Add test for a fix to ThreadLocal.Values property throwing NullReferenceException when disposed Fix is in https://github.com/dotnet/corert/pull/7066 --- .../tests/ThreadLocalTests.cs | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/src/System.Threading/tests/ThreadLocalTests.cs b/src/System.Threading/tests/ThreadLocalTests.cs index abf089a38003..f703ed341895 100644 --- a/src/System.Threading/tests/ThreadLocalTests.cs +++ b/src/System.Threading/tests/ThreadLocalTests.cs @@ -368,6 +368,75 @@ public static void RunThreadLocalTest9_Uninitialized() } } + [Fact] + [OuterLoop] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] + public static void ValuesGetterDoesNotThrowUnexpectedExceptionWhenDisposed() + { + var startTest = new ManualResetEvent(false); + var gotUnexpectedException = new ManualResetEvent(false); + ThreadLocal threadLocal = null; + bool stop = false; + + Action waitForCreatorDisposer; + Thread creatorDisposer = ThreadTestHelpers.CreateGuardedThread(out waitForCreatorDisposer, () => + { + startTest.CheckedWait(); + do + { + var tl = new ThreadLocal(trackAllValues: true); + Volatile.Write(ref threadLocal, tl); + tl.Value = 1; + tl.Dispose(); + } while (!Volatile.Read(ref stop)); + }); + creatorDisposer.IsBackground = true; + creatorDisposer.Start(); + + int readerCount = Math.Max(1, Environment.ProcessorCount - 1); + var waitsForReader = new Action[readerCount]; + for (int i = 0; i < readerCount; ++i) + { + Thread reader = ThreadTestHelpers.CreateGuardedThread(out waitsForReader[i], () => + { + startTest.CheckedWait(); + do + { + var tl = Volatile.Read(ref threadLocal); + if (tl == null) + { + continue; + } + + try + { + IList values = tl.Values; + } + catch (ObjectDisposedException) + { + } + catch + { + gotUnexpectedException.Set(); + throw; + } + } while (!Volatile.Read(ref stop)); + }); + reader.IsBackground = true; + reader.Start(); + } + + startTest.Set(); + bool failed = gotUnexpectedException.WaitOne(500); + Volatile.Write(ref stop, true); + foreach (Action waitForReader in waitsForReader) + { + waitForReader(); + } + waitForCreatorDisposer(); + Assert.False(failed); + } + private class SetMreOnFinalize { private ManualResetEventSlim _mres; From a5c1424337082174a33ea3302aca71c51e7437d6 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Fri, 26 Apr 2019 13:22:09 -0700 Subject: [PATCH 06/11] Fix build --- .../tests/System.Threading.Overlapped.Tests.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/System.Threading.Overlapped/tests/System.Threading.Overlapped.Tests.csproj b/src/System.Threading.Overlapped/tests/System.Threading.Overlapped.Tests.csproj index 7ac20c89c97f..f2cef71d179a 100644 --- a/src/System.Threading.Overlapped/tests/System.Threading.Overlapped.Tests.csproj +++ b/src/System.Threading.Overlapped/tests/System.Threading.Overlapped.Tests.csproj @@ -2,6 +2,7 @@ {861A3318-35AD-46ac-8257-8D5D2479BAD9} true + true netcoreapp-Debug;netcoreapp-Release;netstandard-Debug;netstandard-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release true From 816f425e2b498e7cad573d68b9a3cea5f645514c Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Mon, 29 Apr 2019 11:16:18 -0700 Subject: [PATCH 07/11] Fix test --- .../tests/ThreadPoolTests.netcoreapp.cs | 2 -- src/System.Threading/tests/MonitorTests.cs | 2 +- src/System.Threading/tests/MonitorTests.netcoreapp.cs | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/System.Threading.ThreadPool/tests/ThreadPoolTests.netcoreapp.cs b/src/System.Threading.ThreadPool/tests/ThreadPoolTests.netcoreapp.cs index d7fe0bda003b..bce7e83b5e07 100644 --- a/src/System.Threading.ThreadPool/tests/ThreadPoolTests.netcoreapp.cs +++ b/src/System.Threading.ThreadPool/tests/ThreadPoolTests.netcoreapp.cs @@ -298,8 +298,6 @@ public void MetricsTest() int minExpectedPendingLocalWorkCount = Math.Max(1, simultaneousLocalWorkCount - processorCount * 8); int minExpectedPendingGlobalWorkCount = Math.Max(1, simultaneousGlobalWorkCount - processorCount * 8); int minExpectedPendingWorkCount = minExpectedPendingLocalWorkCount + minExpectedPendingGlobalWorkCount; - Assert.True(ThreadPool.PendingLocalWorkItemCount >= minExpectedPendingLocalWorkCount); - Assert.True(ThreadPool.PendingGlobalWorkItemCount >= minExpectedPendingGlobalWorkCount); Assert.True(ThreadPool.PendingWorkItemCount >= minExpectedPendingWorkCount); // Complete the work and verify the completed work item count diff --git a/src/System.Threading/tests/MonitorTests.cs b/src/System.Threading/tests/MonitorTests.cs index 2d37c4b8c13b..f7fab6e40551 100644 --- a/src/System.Threading/tests/MonitorTests.cs +++ b/src/System.Threading/tests/MonitorTests.cs @@ -10,7 +10,7 @@ namespace System.Threading.Tests { - public static class MonitorTests + public static partial class MonitorTests { private const int FailTimeoutMilliseconds = 30000; diff --git a/src/System.Threading/tests/MonitorTests.netcoreapp.cs b/src/System.Threading/tests/MonitorTests.netcoreapp.cs index 2650596516c9..48eb758a3efc 100644 --- a/src/System.Threading/tests/MonitorTests.netcoreapp.cs +++ b/src/System.Threading/tests/MonitorTests.netcoreapp.cs @@ -10,7 +10,7 @@ namespace System.Threading.Tests { - public static class MonitorTests + public static partial class MonitorTests { [Fact] public static void Enter_HasToWait_LockContentionCountTest() From e96ff44cd2a76b6bfabad3fb08301f850850bf40 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Tue, 30 Apr 2019 09:35:53 -0700 Subject: [PATCH 08/11] Add API compat baselines for uapaot --- .../src/ApiCompatBaseline.uapaot.txt | 4 ++++ src/System.Threading/src/ApiCompatBaseline.uapaot.txt | 2 ++ 2 files changed, 6 insertions(+) create mode 100644 src/System.Threading.ThreadPool/src/ApiCompatBaseline.uapaot.txt create mode 100644 src/System.Threading/src/ApiCompatBaseline.uapaot.txt diff --git a/src/System.Threading.ThreadPool/src/ApiCompatBaseline.uapaot.txt b/src/System.Threading.ThreadPool/src/ApiCompatBaseline.uapaot.txt new file mode 100644 index 000000000000..2aae58bf2cfd --- /dev/null +++ b/src/System.Threading.ThreadPool/src/ApiCompatBaseline.uapaot.txt @@ -0,0 +1,4 @@ +Compat issues with assembly System.Threading.ThreadPool: +MembersMustExist : Member 'System.Threading.ThreadPool.CompletedWorkItemCount.get()' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.Threading.ThreadPool.PendingWorkItemCount.get()' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.Threading.ThreadPool.ThreadCount.get()' does not exist in the implementation but it does exist in the contract. diff --git a/src/System.Threading/src/ApiCompatBaseline.uapaot.txt b/src/System.Threading/src/ApiCompatBaseline.uapaot.txt new file mode 100644 index 000000000000..8db229d21609 --- /dev/null +++ b/src/System.Threading/src/ApiCompatBaseline.uapaot.txt @@ -0,0 +1,2 @@ +Compat issues with assembly System.Threading: +MembersMustExist : Member 'System.Threading.Monitor.LockContentionCount.get()' does not exist in the implementation but it does exist in the contract. From 18ccac47579a6c51e956e923cecea32a229873e2 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Fri, 3 May 2019 08:25:55 -0700 Subject: [PATCH 09/11] Fix test --- .../tests/ThreadPoolTests.netcoreapp.cs | 144 ++++++++++-------- 1 file changed, 78 insertions(+), 66 deletions(-) diff --git a/src/System.Threading.ThreadPool/tests/ThreadPoolTests.netcoreapp.cs b/src/System.Threading.ThreadPool/tests/ThreadPoolTests.netcoreapp.cs index bce7e83b5e07..4ad3c6b412a1 100644 --- a/src/System.Threading.ThreadPool/tests/ThreadPoolTests.netcoreapp.cs +++ b/src/System.Threading.ThreadPool/tests/ThreadPoolTests.netcoreapp.cs @@ -194,17 +194,24 @@ public void Execute() { } public void MetricsTest() { int processorCount = Environment.ProcessorCount; + if (processorCount <= 2) + { + return; + } + bool waitForWorkStart = false; var workStarted = new AutoResetEvent(false); + var localWorkScheduled = new AutoResetEvent(false); int completeWork = 0; - int simultaneousWorkCount = 0; - int simultaneousLocalWorkCount = 0; - int simultaneousGlobalWorkCount = 0; + int queuedWorkCount = 0; var allWorkCompleted = new ManualResetEvent(false); Exception backgroundEx = null; Action work = () => { - workStarted.Set(); + if (waitForWorkStart) + { + workStarted.Set(); + } try { // Blocking can affect thread pool thread injection heuristics, so don't block, pretend like a @@ -218,99 +225,104 @@ public void MetricsTest() } finally { - if (Interlocked.Decrement(ref simultaneousWorkCount) == 0) + if (Interlocked.Decrement(ref queuedWorkCount) == 0) { allWorkCompleted.Set(); } } }; - WaitCallback threadPoolWork = data => work(); + WaitCallback threadPoolGlobalWork = data => work(); Action threadPoolLocalWork = data => work(); - TimerCallback timerWork = data => work(); - WaitOrTimerCallback waitWork = (data, timedOut) => work(); + WaitCallback scheduleThreadPoolLocalWork = data => + { + try + { + int n = (int)data; + for (int i = 0; i < n; ++i) + { + ThreadPool.QueueUserWorkItem(threadPoolLocalWork, null, preferLocal: true); + if (waitForWorkStart) + { + workStarted.CheckedWait(); + } + } + } + catch (Exception ex) + { + Interlocked.CompareExchange(ref backgroundEx, ex, null); + } + finally + { + localWorkScheduled.Set(); + } + }; var signaledEvent = new ManualResetEvent(true); var timers = new List(); - int maxSimultaneousWorkCount = 0; + int totalWorkCountToQueue = 0; Action scheduleWork = () => { - Assert.True(simultaneousWorkCount <= maxSimultaneousWorkCount); + Assert.True(queuedWorkCount < totalWorkCountToQueue); - while (true) + int workCount = (totalWorkCountToQueue - queuedWorkCount) / 2; + if (workCount > 0) { - if (simultaneousWorkCount >= maxSimultaneousWorkCount) - { - break; - } - ++simultaneousWorkCount; - ++simultaneousGlobalWorkCount; - ThreadPool.QueueUserWorkItem(threadPoolWork); - workStarted.CheckedWait(); - - if (simultaneousWorkCount >= maxSimultaneousWorkCount) - { - break; - } - ++simultaneousWorkCount; - ++simultaneousLocalWorkCount; - ThreadPool.QueueUserWorkItem(threadPoolLocalWork, null, preferLocal: true); - workStarted.CheckedWait(); - - if (simultaneousWorkCount >= maxSimultaneousWorkCount) - { - break; - } - ++simultaneousWorkCount; - ++simultaneousGlobalWorkCount; - timers.Add(new Timer(timerWork, null, 1, Timeout.Infinite)); - workStarted.CheckedWait(); + queuedWorkCount += workCount; + ThreadPool.QueueUserWorkItem(scheduleThreadPoolLocalWork, workCount); + localWorkScheduled.CheckedWait(); + } - if (simultaneousWorkCount >= maxSimultaneousWorkCount) + for (; queuedWorkCount < totalWorkCountToQueue; ++queuedWorkCount) + { + ThreadPool.QueueUserWorkItem(threadPoolGlobalWork); + if (waitForWorkStart) { - break; + workStarted.CheckedWait(); } - ++simultaneousWorkCount; - ++simultaneousGlobalWorkCount; - ThreadPool.RegisterWaitForSingleObject( - signaledEvent, - waitWork, - null, - ThreadTestHelpers.UnexpectedTimeoutMilliseconds, - true); - workStarted.CheckedWait(); } - - Assert.Equal(maxSimultaneousWorkCount, simultaneousWorkCount); }; + Interlocked.MemoryBarrierProcessWide(); // get a reasonably accurate value for the following long initialCompletedWorkItemCount = ThreadPool.CompletedWorkItemCount; - // Schedule some simultaneous work that would all be scheduled and verify the thread count - maxSimultaneousWorkCount = Math.Max(1, processorCount - 1); // minus one in case this thread is a thread pool thread - scheduleWork(); - Assert.True(ThreadPool.ThreadCount >= maxSimultaneousWorkCount); + try + { + // Schedule some simultaneous work that would all be scheduled and verify the thread count + totalWorkCountToQueue = processorCount - 2; + Assert.True(totalWorkCountToQueue >= 1); + waitForWorkStart = true; + scheduleWork(); + Assert.True(ThreadPool.ThreadCount >= totalWorkCountToQueue); - // Schedule more work that would not all be scheduled and roughly verify the pending work item count - maxSimultaneousWorkCount = processorCount * 64; - scheduleWork(); - // The following is assuming that no more than (processorCount * 8) work items will be scheduled to run - // simultaneously - int minExpectedPendingLocalWorkCount = Math.Max(1, simultaneousLocalWorkCount - processorCount * 8); - int minExpectedPendingGlobalWorkCount = Math.Max(1, simultaneousGlobalWorkCount - processorCount * 8); - int minExpectedPendingWorkCount = minExpectedPendingLocalWorkCount + minExpectedPendingGlobalWorkCount; - Assert.True(ThreadPool.PendingWorkItemCount >= minExpectedPendingWorkCount); + int runningWorkItemCount = queuedWorkCount; - // Complete the work and verify the completed work item count - Interlocked.Exchange(ref completeWork, 1); + // Schedule more work that would not all be scheduled and roughly verify the pending work item count + totalWorkCountToQueue = processorCount * 64; + waitForWorkStart = false; + scheduleWork(); + int minExpectedPendingWorkCount = Math.Max(1, queuedWorkCount - runningWorkItemCount * 8); + ThreadTestHelpers.WaitForCondition(() => ThreadPool.PendingWorkItemCount >= minExpectedPendingWorkCount); + } + finally + { + // Complete the work + Interlocked.Exchange(ref completeWork, 1); + } + + // Verify the completed work item count allWorkCompleted.CheckedWait(); backgroundEx = Interlocked.CompareExchange(ref backgroundEx, null, null); if (backgroundEx != null) { throw new AggregateException(backgroundEx); } + // Wait for work items to exit, for counting ThreadTestHelpers.WaitForCondition(() => - ThreadPool.CompletedWorkItemCount - initialCompletedWorkItemCount >= maxSimultaneousWorkCount); + { + Interlocked.MemoryBarrierProcessWide(); // get a reasonably accurate value for the following + return ThreadPool.CompletedWorkItemCount - initialCompletedWorkItemCount >= totalWorkCountToQueue; + }); } } } From fc9429321edd214939ebc4de329f44416a3d98ed Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Mon, 6 May 2019 11:37:28 -0700 Subject: [PATCH 10/11] Use RemoteExecutor for MetricsTest --- .../tests/ThreadPoolTests.netcoreapp.cs | 216 +++++++++--------- 1 file changed, 110 insertions(+), 106 deletions(-) diff --git a/src/System.Threading.ThreadPool/tests/ThreadPoolTests.netcoreapp.cs b/src/System.Threading.ThreadPool/tests/ThreadPoolTests.netcoreapp.cs index 4ad3c6b412a1..45068730994a 100644 --- a/src/System.Threading.ThreadPool/tests/ThreadPoolTests.netcoreapp.cs +++ b/src/System.Threading.ThreadPool/tests/ThreadPoolTests.netcoreapp.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading.Tasks; using System.Threading.Tests; +using Microsoft.DotNet.RemoteExecutor; using Xunit; namespace System.Threading.ThreadPools.Tests @@ -193,136 +194,139 @@ public void Execute() { } [Fact] public void MetricsTest() { - int processorCount = Environment.ProcessorCount; - if (processorCount <= 2) + RemoteExecutor.Invoke(() => { - return; - } - - bool waitForWorkStart = false; - var workStarted = new AutoResetEvent(false); - var localWorkScheduled = new AutoResetEvent(false); - int completeWork = 0; - int queuedWorkCount = 0; - var allWorkCompleted = new ManualResetEvent(false); - Exception backgroundEx = null; - Action work = () => - { - if (waitForWorkStart) - { - workStarted.Set(); - } - try + int processorCount = Environment.ProcessorCount; + if (processorCount <= 2) { - // Blocking can affect thread pool thread injection heuristics, so don't block, pretend like a - // long-running CPU-bound work item - ThreadTestHelpers.WaitForConditionWithoutRelinquishingTimeSlice( - () => Interlocked.CompareExchange(ref completeWork, 0, 0) != 0); + return; } - catch (Exception ex) + + bool waitForWorkStart = false; + var workStarted = new AutoResetEvent(false); + var localWorkScheduled = new AutoResetEvent(false); + int completeWork = 0; + int queuedWorkCount = 0; + var allWorkCompleted = new ManualResetEvent(false); + Exception backgroundEx = null; + Action work = () => { - Interlocked.CompareExchange(ref backgroundEx, ex, null); - } - finally + if (waitForWorkStart) + { + workStarted.Set(); + } + try + { + // Blocking can affect thread pool thread injection heuristics, so don't block, pretend like a + // long-running CPU-bound work item + ThreadTestHelpers.WaitForConditionWithoutRelinquishingTimeSlice( + () => Interlocked.CompareExchange(ref completeWork, 0, 0) != 0); + } + catch (Exception ex) + { + Interlocked.CompareExchange(ref backgroundEx, ex, null); + } + finally + { + if (Interlocked.Decrement(ref queuedWorkCount) == 0) + { + allWorkCompleted.Set(); + } + } + }; + WaitCallback threadPoolGlobalWork = data => work(); + Action threadPoolLocalWork = data => work(); + WaitCallback scheduleThreadPoolLocalWork = data => { - if (Interlocked.Decrement(ref queuedWorkCount) == 0) + try { - allWorkCompleted.Set(); + int n = (int)data; + for (int i = 0; i < n; ++i) + { + ThreadPool.QueueUserWorkItem(threadPoolLocalWork, null, preferLocal: true); + if (waitForWorkStart) + { + workStarted.CheckedWait(); + } + } } - } - }; - WaitCallback threadPoolGlobalWork = data => work(); - Action threadPoolLocalWork = data => work(); - WaitCallback scheduleThreadPoolLocalWork = data => - { - try + catch (Exception ex) + { + Interlocked.CompareExchange(ref backgroundEx, ex, null); + } + finally + { + localWorkScheduled.Set(); + } + }; + + var signaledEvent = new ManualResetEvent(true); + var timers = new List(); + int totalWorkCountToQueue = 0; + Action scheduleWork = () => { - int n = (int)data; - for (int i = 0; i < n; ++i) + Assert.True(queuedWorkCount < totalWorkCountToQueue); + + int workCount = (totalWorkCountToQueue - queuedWorkCount) / 2; + if (workCount > 0) { - ThreadPool.QueueUserWorkItem(threadPoolLocalWork, null, preferLocal: true); + queuedWorkCount += workCount; + ThreadPool.QueueUserWorkItem(scheduleThreadPoolLocalWork, workCount); + localWorkScheduled.CheckedWait(); + } + + for (; queuedWorkCount < totalWorkCountToQueue; ++queuedWorkCount) + { + ThreadPool.QueueUserWorkItem(threadPoolGlobalWork); if (waitForWorkStart) { workStarted.CheckedWait(); } } - } - catch (Exception ex) + }; + + Interlocked.MemoryBarrierProcessWide(); // get a reasonably accurate value for the following + long initialCompletedWorkItemCount = ThreadPool.CompletedWorkItemCount; + + try { - Interlocked.CompareExchange(ref backgroundEx, ex, null); + // Schedule some simultaneous work that would all be scheduled and verify the thread count + totalWorkCountToQueue = processorCount - 2; + Assert.True(totalWorkCountToQueue >= 1); + waitForWorkStart = true; + scheduleWork(); + Assert.True(ThreadPool.ThreadCount >= totalWorkCountToQueue); + + int runningWorkItemCount = queuedWorkCount; + + // Schedule more work that would not all be scheduled and roughly verify the pending work item count + totalWorkCountToQueue = processorCount * 64; + waitForWorkStart = false; + scheduleWork(); + int minExpectedPendingWorkCount = Math.Max(1, queuedWorkCount - runningWorkItemCount * 8); + ThreadTestHelpers.WaitForCondition(() => ThreadPool.PendingWorkItemCount >= minExpectedPendingWorkCount); } finally { - localWorkScheduled.Set(); + // Complete the work + Interlocked.Exchange(ref completeWork, 1); } - }; - - var signaledEvent = new ManualResetEvent(true); - var timers = new List(); - int totalWorkCountToQueue = 0; - Action scheduleWork = () => - { - Assert.True(queuedWorkCount < totalWorkCountToQueue); - int workCount = (totalWorkCountToQueue - queuedWorkCount) / 2; - if (workCount > 0) + // Wait for work items to exit, for counting + allWorkCompleted.CheckedWait(); + backgroundEx = Interlocked.CompareExchange(ref backgroundEx, null, null); + if (backgroundEx != null) { - queuedWorkCount += workCount; - ThreadPool.QueueUserWorkItem(scheduleThreadPoolLocalWork, workCount); - localWorkScheduled.CheckedWait(); + throw new AggregateException(backgroundEx); } - for (; queuedWorkCount < totalWorkCountToQueue; ++queuedWorkCount) + // Verify the completed work item count + ThreadTestHelpers.WaitForCondition(() => { - ThreadPool.QueueUserWorkItem(threadPoolGlobalWork); - if (waitForWorkStart) - { - workStarted.CheckedWait(); - } - } - }; - - Interlocked.MemoryBarrierProcessWide(); // get a reasonably accurate value for the following - long initialCompletedWorkItemCount = ThreadPool.CompletedWorkItemCount; - - try - { - // Schedule some simultaneous work that would all be scheduled and verify the thread count - totalWorkCountToQueue = processorCount - 2; - Assert.True(totalWorkCountToQueue >= 1); - waitForWorkStart = true; - scheduleWork(); - Assert.True(ThreadPool.ThreadCount >= totalWorkCountToQueue); - - int runningWorkItemCount = queuedWorkCount; - - // Schedule more work that would not all be scheduled and roughly verify the pending work item count - totalWorkCountToQueue = processorCount * 64; - waitForWorkStart = false; - scheduleWork(); - int minExpectedPendingWorkCount = Math.Max(1, queuedWorkCount - runningWorkItemCount * 8); - ThreadTestHelpers.WaitForCondition(() => ThreadPool.PendingWorkItemCount >= minExpectedPendingWorkCount); - } - finally - { - // Complete the work - Interlocked.Exchange(ref completeWork, 1); - } - - // Verify the completed work item count - allWorkCompleted.CheckedWait(); - backgroundEx = Interlocked.CompareExchange(ref backgroundEx, null, null); - if (backgroundEx != null) - { - throw new AggregateException(backgroundEx); - } - - // Wait for work items to exit, for counting - ThreadTestHelpers.WaitForCondition(() => - { - Interlocked.MemoryBarrierProcessWide(); // get a reasonably accurate value for the following - return ThreadPool.CompletedWorkItemCount - initialCompletedWorkItemCount >= totalWorkCountToQueue; - }); + Interlocked.MemoryBarrierProcessWide(); // get a reasonably accurate value for the following + return ThreadPool.CompletedWorkItemCount - initialCompletedWorkItemCount >= totalWorkCountToQueue; + }); + }).Dispose(); } } } From 723ebc982db9545b17a0a2c56776cd92c759d699 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Wed, 8 May 2019 11:20:12 -0700 Subject: [PATCH 11/11] Address feedback --- src/Common/tests/System/Threading/ThreadTestHelpers.cs | 4 ++-- .../tests/ThreadPoolTests.netcoreapp.cs | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Common/tests/System/Threading/ThreadTestHelpers.cs b/src/Common/tests/System/Threading/ThreadTestHelpers.cs index 20c90a0e1fbd..4ebcdcb298b6 100644 --- a/src/Common/tests/System/Threading/ThreadTestHelpers.cs +++ b/src/Common/tests/System/Threading/ThreadTestHelpers.cs @@ -135,7 +135,7 @@ public static void WaitForConditionWithCustomDelay(Func condition, Action return; } - var startTime = Environment.TickCount; + int startTimeMs = Environment.TickCount; while (true) { delay(); @@ -145,7 +145,7 @@ public static void WaitForConditionWithCustomDelay(Func condition, Action return; } - Assert.True(Environment.TickCount - startTime < UnexpectedTimeoutMilliseconds); + Assert.InRange(Environment.TickCount - startTimeMs, 0, UnexpectedTimeoutMilliseconds); } } diff --git a/src/System.Threading.ThreadPool/tests/ThreadPoolTests.netcoreapp.cs b/src/System.Threading.ThreadPool/tests/ThreadPoolTests.netcoreapp.cs index 45068730994a..567f69e50b1b 100644 --- a/src/System.Threading.ThreadPool/tests/ThreadPoolTests.netcoreapp.cs +++ b/src/System.Threading.ThreadPool/tests/ThreadPoolTests.netcoreapp.cs @@ -191,7 +191,7 @@ public InvalidWorkItemAndTask(Action action) : base(action) { } public void Execute() { } } - [Fact] + [ConditionalFact(nameof(HasAtLeastThreeProcessors))] public void MetricsTest() { RemoteExecutor.Invoke(() => @@ -328,5 +328,7 @@ public void MetricsTest() }); }).Dispose(); } + + public static bool HasAtLeastThreeProcessors => Environment.ProcessorCount >= 3; } }