Skip to content

Commit

Permalink
Expose and test APIs for some threading metrics (CoreFX)
Browse files Browse the repository at this point in the history
  • Loading branch information
kouvel committed May 2, 2019
1 parent de0effd commit a8783fe
Show file tree
Hide file tree
Showing 16 changed files with 186 additions and 12 deletions.
17 changes: 14 additions & 3 deletions src/Common/tests/System/Threading/ThreadTestHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,22 @@ public static void WaitForConditionWithoutBlocking(Func<bool> condition)

public static void WaitForConditionWithCustomDelay(Func<bool> 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);
}
}

Expand Down
1 change: 1 addition & 0 deletions src/System.Threading.Overlapped/tests/Configurations.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<Project DefaultTargets="Build">
<PropertyGroup>
<BuildConfigurations>
netcoreapp;
netstandard;
uap-Windows_NT;
</BuildConfigurations>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
<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>
<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" />
Expand All @@ -21,4 +22,9 @@
<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>
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
</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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace System.Threading.Threads.Tests
{
public static partial class ThreadTests
{
[Fact]
public static void GetCurrentProcessorId()
{
Assert.True(Thread.GetCurrentProcessorId() >= 0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<TState>(System.Action<TState> callBack, TState state, bool preferLocal) { throw null; }
Expand All @@ -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; }
Expand Down
1 change: 1 addition & 0 deletions src/System.Threading.ThreadPool/tests/Configurations.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<BuildConfigurations>
netcoreapp;
netstandard;
uap;
</BuildConfigurations>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
<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">
Expand Down
105 changes: 105 additions & 0 deletions src/System.Threading.ThreadPool/tests/ThreadPoolTests.netcoreapp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<Timer>();
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);
}
}
}
1 change: 1 addition & 0 deletions src/System.Threading.Timer/tests/Configurations.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<BuildConfigurations>
netcoreapp;
netstandard;
uap;
</BuildConfigurations>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<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>
Expand All @@ -10,7 +10,7 @@
<Compile Include="TimerChangeTests.cs" />
<Compile Include="TimerFiringTests.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetGroup)' == 'netcoreapp'">
<ItemGroup Condition="'$(TargetGroup)' != 'netstandard'">
<Compile Include="TimerDisposeTests.cs" />
</ItemGroup>
<ItemGroup>
Expand Down
1 change: 1 addition & 0 deletions src/System.Threading/ref/System.Threading.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
1 change: 1 addition & 0 deletions src/System.Threading/tests/Configurations.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<BuildConfigurations>
netstandard;
netcoreapp;
uap;
</BuildConfigurations>
</PropertyGroup>
</Project>
23 changes: 23 additions & 0 deletions src/System.Threading/tests/MonitorTests.netcoreapp.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
7 changes: 4 additions & 3 deletions src/System.Threading/tests/System.Threading.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<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" />
Expand All @@ -15,13 +15,14 @@
<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" />
Expand All @@ -30,7 +31,7 @@
<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" />
Expand Down

0 comments on commit a8783fe

Please sign in to comment.