WaitForConditionWithCustomDelay(condition, () => Thread.Yield());
}
+ public static void WaitForConditionWithoutRelinquishingTimeSlice(Func<bool> condition)
+ {
+ WaitForConditionWithCustomDelay(condition, () => Thread.SpinWait(1));
+ }
+
public static void WaitForConditionWithCustomDelay(Func<bool> 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);
}
}
<Project DefaultTargets="Build">
<PropertyGroup>
<BuildConfigurations>
+ netcoreapp;
netstandard;
uap-Windows_NT;
</BuildConfigurations>
<PropertyGroup>
<ProjectGuid>{861A3318-35AD-46ac-8257-8D5D2479BAD9}</ProjectGuid>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
- <Configurations>netstandard-Debug;netstandard-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release</Configurations>
+ <IncludeRemoteExecutor>true</IncludeRemoteExecutor>
+ <Configurations>netcoreapp-Debug;netcoreapp-Release;netstandard-Debug;netstandard-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release</Configurations>
<TestRuntime>true</TestRuntime>
</PropertyGroup>
<ItemGroup>
<Compile Include="DllImport.cs" />
<Compile Include="ThreadPoolBoundHandle_PreAllocatedOverlappedTests.cs" />
<Compile Include="ThreadPoolBoundHandle_IntegrationTests.cs" />
+ <Compile Include="ThreadPoolBoundHandle_IntegrationTests.netcoreapp.cs" Condition="'$(TargetGroup)' != 'netstandard'" />
<Compile Include="ThreadPoolBoundHandle_Helpers.cs" />
<Compile Include="AsyncResult.cs" />
<Compile Include="Win32Handle.cs" />
<Compile Include="ThreadPoolBoundHandle_BindHandleTests.cs" />
<Compile Include="OverlappedTests.cs" />
</ItemGroup>
+ <ItemGroup>
+ <Compile Include="$(CommonTestPath)\System\Threading\ThreadTestHelpers.cs">
+ <Link>CommonTest\System\Threading\ThreadTestHelpers.cs</Link>
+ </Compile>
+ </ItemGroup>
</Project>
\ No newline at end of file
--- /dev/null
+// 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);
+ }
+}
</ItemGroup>
<ItemGroup>
<Compile Include="$(CommonTestPath)\System\Threading\ThreadTestHelpers.cs">
- <Link>CommonTest\System\Threading\ThreadPoolHelpers.cs</Link>
+ <Link>CommonTest\System\Threading\ThreadTestHelpers.cs</Link>
</Compile>
<ProjectReference Include="STAMain\STAMain.csproj">
<Name>STAMain</Name>
{
public static partial class ThreadTests
{
+ [Fact]
public static void GetCurrentProcessorId()
{
Assert.True(Thread.GetCurrentProcessorId() >= 0);
[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<TState>(System.Action<TState> callBack, TState state, bool preferLocal) { throw null; }
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; }
--- /dev/null
+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.
<BuildConfigurations>
netcoreapp;
netstandard;
+ uap;
</BuildConfigurations>
</PropertyGroup>
</Project>
\ No newline at end of file
<PropertyGroup>
<ProjectGuid>{403AD1B8-6F95-4A2E-92A2-727606ABD866}</ProjectGuid>
<IncludeRemoteExecutor>true</IncludeRemoteExecutor>
- <Configurations>netcoreapp-Debug;netcoreapp-Release;netstandard-Debug;netstandard-Release</Configurations>
+ <Configurations>netcoreapp-Debug;netcoreapp-Release;netstandard-Debug;netstandard-Release;uap-Debug;uap-Release</Configurations>
<TestRuntime>true</TestRuntime>
</PropertyGroup>
<ItemGroup>
<Compile Include="ThreadPoolTests.cs" />
- <Compile Include="ThreadPoolTests.netcoreapp.cs" Condition="'$(TargetGroup)' == 'netcoreapp'" />
+ <Compile Include="ThreadPoolTests.netcoreapp.cs" Condition="'$(TargetGroup)' != 'netstandard'" />
</ItemGroup>
<ItemGroup>
<Compile Include="$(CommonTestPath)\System\Threading\ThreadTestHelpers.cs">
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
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<object> 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<Timer>();
+ 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;
}
}
<BuildConfigurations>
netcoreapp;
netstandard;
+ uap;
</BuildConfigurations>
</PropertyGroup>
</Project>
\ No newline at end of file
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ProjectGuid>{ac20a28f-fda8-45e8-8728-058ead16e44c}</ProjectGuid>
- <Configurations>netcoreapp-Debug;netcoreapp-Release;netstandard-Debug;netstandard-Release</Configurations>
+ <Configurations>netcoreapp-Debug;netcoreapp-Release;netstandard-Debug;netstandard-Release;uap-Debug;uap-Release</Configurations>
<TestRuntime>true</TestRuntime>
<IncludeRemoteExecutor>true</IncludeRemoteExecutor>
</PropertyGroup>
<Compile Include="TimerChangeTests.cs" />
<Compile Include="TimerFiringTests.cs" />
</ItemGroup>
- <ItemGroup Condition="'$(TargetGroup)' == 'netcoreapp'">
+ <ItemGroup Condition="'$(TargetGroup)' != 'netstandard'">
<Compile Include="TimerDisposeTests.cs" />
</ItemGroup>
- <ItemGroup>
- <Compile Include="$(CommonTestPath)\System\Threading\ThreadTestHelpers.cs">
- <Link>CommonTest\System\Threading\ThreadTestHelpers.cs</Link>
- </Compile>
- </ItemGroup>
</Project>
\ No newline at end of file
using System.Text;
using System.Threading;
using System.Threading.Tasks;
-using System.Threading.Tests;
using Xunit;
using Xunit.Sdk;
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<object> 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();
- }
- }
}
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; }
--- /dev/null
+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.
<BuildConfigurations>
netstandard;
netcoreapp;
+ uap;
</BuildConfigurations>
</PropertyGroup>
</Project>
\ No newline at end of file
namespace System.Threading.Tests
{
- public static class MonitorTests
+ public static partial class MonitorTests
{
private const int FailTimeoutMilliseconds = 30000;
--- /dev/null
+// 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);
+ }
+ }
+}
<ProjectGuid>{18EF66B3-51EE-46D8-B283-1CB6A1197813}</ProjectGuid>
<TestRuntime>true</TestRuntime>
<IncludeRemoteExecutor>true</IncludeRemoteExecutor>
- <Configurations>netcoreapp-Debug;netcoreapp-Release;netstandard-Debug;netstandard-Release</Configurations>
+ <Configurations>netcoreapp-Debug;netcoreapp-Release;netstandard-Debug;netstandard-Release;uap-Debug;uap-Release</Configurations>
</PropertyGroup>
<ItemGroup>
<Compile Include="AsyncLocalTests.cs" />
<Compile Include="EtwTests.cs" />
<Compile Include="EventWaitHandleTests.cs" />
<Compile Include="InterlockedTests.cs" />
- <Compile Include="InterlockedTests.netcoreapp.cs" Condition="'$(TargetGroup)'=='netcoreapp'" />
+ <Compile Include="InterlockedTests.netcoreapp.cs" Condition="'$(TargetGroup)' != 'netstandard'" />
<Compile Include="HostExecutionContextTests.cs" />
<Compile Include="HostExecutionContextManagerTests.cs" />
<Compile Include="ManualResetEventTests.cs" />
<Compile Include="ManualResetEventSlimCancellationTests.cs" />
<Compile Include="ManualResetEventSlimTests.cs" />
<Compile Include="MonitorTests.cs" />
+ <Compile Include="MonitorTests.netcoreapp.cs" Condition="'$(TargetGroup)' != 'netstandard'" />
<Compile Include="MutexTests.cs" />
<Compile Include="SemaphoreSlimCancellationTests.cs" />
<Compile Include="SemaphoreSlimTests.cs" />
<Compile Include="ReaderWriterLockTests.cs" />
<Compile Include="ReaderWriterLockSlimTests.cs" />
<Compile Include="SpinWaitTests.cs" />
- <Compile Include="SpinWaitTests.netcoreapp.cs" Condition="'$(TargetGroup)'=='netcoreapp'" />
+ <Compile Include="SpinWaitTests.netcoreapp.cs" Condition="'$(TargetGroup)' != 'netstandard'" />
<Compile Include="ThreadLocalTests.cs" />
<Compile Include="XunitAssemblyAttributes.cs" />
<Compile Include="ExecutionContextTests.cs" />
}
}
+ [Fact]
+ [OuterLoop]
+ [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)]
+ public static void ValuesGetterDoesNotThrowUnexpectedExceptionWhenDisposed()
+ {
+ var startTest = new ManualResetEvent(false);
+ var gotUnexpectedException = new ManualResetEvent(false);
+ ThreadLocal<int> threadLocal = null;
+ bool stop = false;
+
+ Action waitForCreatorDisposer;
+ Thread creatorDisposer = ThreadTestHelpers.CreateGuardedThread(out waitForCreatorDisposer, () =>
+ {
+ startTest.CheckedWait();
+ do
+ {
+ var tl = new ThreadLocal<int>(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<int> 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;