diff --git a/src/Common/tests/System/Threading/ThreadTestHelpers.cs b/src/Common/tests/System/Threading/ThreadTestHelpers.cs index d5735754ff1b..4ebcdcb298b6 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; + } + + int startTimeMs = Environment.TickCount; + while (true) { - Assert.True((DateTime.Now - startTime).TotalMilliseconds < UnexpectedTimeoutMilliseconds); delay(); + + if (condition()) + { + return; + } + + Assert.InRange(Environment.TickCount - startTimeMs, 0, 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..567f69e50b1b 100644 --- a/src/System.Threading.ThreadPool/tests/ThreadPoolTests.netcoreapp.cs +++ b/src/System.Threading.ThreadPool/tests/ThreadPoolTests.netcoreapp.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using System.Threading.Tests; +using Microsoft.DotNet.RemoteExecutor; using Xunit; namespace System.Threading.ThreadPools.Tests @@ -188,5 +190,145 @@ private sealed class InvalidWorkItemAndTask : Task, IThreadPoolWorkItem public InvalidWorkItemAndTask(Action action) : base(action) { } public void Execute() { } } + + [ConditionalFact(nameof(HasAtLeastThreeProcessors))] + public void MetricsTest() + { + RemoteExecutor.Invoke(() => + { + 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 queuedWorkCount = 0; + var allWorkCompleted = new ManualResetEvent(false); + Exception backgroundEx = null; + Action work = () => + { + 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 => + { + 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 totalWorkCountToQueue = 0; + Action scheduleWork = () => + { + Assert.True(queuedWorkCount < totalWorkCountToQueue); + + int workCount = (totalWorkCountToQueue - queuedWorkCount) / 2; + if (workCount > 0) + { + queuedWorkCount += workCount; + ThreadPool.QueueUserWorkItem(scheduleThreadPoolLocalWork, workCount); + localWorkScheduled.CheckedWait(); + } + + for (; queuedWorkCount < totalWorkCountToQueue; ++queuedWorkCount) + { + 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); + } + + // Wait for work items to exit, for counting + allWorkCompleted.CheckedWait(); + backgroundEx = Interlocked.CompareExchange(ref backgroundEx, null, null); + if (backgroundEx != null) + { + throw new AggregateException(backgroundEx); + } + + // Verify the completed work item count + ThreadTestHelpers.WaitForCondition(() => + { + Interlocked.MemoryBarrierProcessWide(); // get a reasonably accurate value for the following + return ThreadPool.CompletedWorkItemCount - initialCompletedWorkItemCount >= totalWorkCountToQueue; + }); + }).Dispose(); + } + + public static bool HasAtLeastThreeProcessors => Environment.ProcessorCount >= 3; } } 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;