diff --git a/src/Common/tests/System/Threading/ThreadTestHelpers.cs b/src/Common/tests/System/Threading/ThreadTestHelpers.cs index d5735754ff1b..20c90a0e1fbd 100644 --- a/src/Common/tests/System/Threading/ThreadTestHelpers.cs +++ b/src/Common/tests/System/Threading/ThreadTestHelpers.cs @@ -123,13 +123,29 @@ 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) { - 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..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,13 +2,15 @@ {861A3318-35AD-46ac-8257-8D5D2479BAD9} true - netstandard-Debug;netstandard-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release + true + netcoreapp-Debug;netcoreapp-Release;netstandard-Debug;netstandard-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release true + @@ -21,4 +23,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/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.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..bce7e83b5e07 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,128 @@ 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; + int simultaneousLocalWorkCount = 0; + int simultaneousGlobalWorkCount = 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.WaitForConditionWithoutRelinquishingTimeSlice( + () => Interlocked.CompareExchange(ref completeWork, 0, 0) != 0); + } + catch (Exception ex) + { + Interlocked.CompareExchange(ref backgroundEx, ex, null); + } + finally + { + if (Interlocked.Decrement(ref simultaneousWorkCount) == 0) + { + allWorkCompleted.Set(); + } + } + }; + WaitCallback threadPoolWork = data => work(); + Action threadPoolLocalWork = 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; + ++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(); + + if (simultaneousWorkCount >= maxSimultaneousWorkCount) + { + break; + } + ++simultaneousWorkCount; + ++simultaneousGlobalWorkCount; + 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 * 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); + + // 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..6575f1bfa4a0 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,12 +10,7 @@ - + - - - 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(); - } - } } 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/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. 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.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 new file mode 100644 index 000000000000..48eb758a3efc --- /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 partial 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 @@ - + 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;