Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

Expose\test generic UnsafeQueueUserWorkItem overload #33637

Merged
merged 2 commits into from
Nov 27, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public static partial class ThreadPool
public static unsafe bool UnsafeQueueNativeOverlapped(System.Threading.NativeOverlapped* overlapped) { throw null; }
public static bool UnsafeQueueUserWorkItem(System.Threading.IThreadPoolWorkItem callBack, bool preferLocal) { throw null; }
public static bool UnsafeQueueUserWorkItem(System.Threading.WaitCallback callBack, object state) { throw null; }
public static bool UnsafeQueueUserWorkItem<TState>(System.Action<TState> callBack, TState state, bool preferLocal) { throw null; }
public static System.Threading.RegisteredWaitHandle UnsafeRegisterWaitForSingleObject(System.Threading.WaitHandle waitObject, System.Threading.WaitOrTimerCallback callBack, object state, int millisecondsTimeOutInterval, bool executeOnlyOnce) { throw null; }
public static System.Threading.RegisteredWaitHandle UnsafeRegisterWaitForSingleObject(System.Threading.WaitHandle waitObject, System.Threading.WaitOrTimerCallback callBack, object state, long millisecondsTimeOutInterval, bool executeOnlyOnce) { throw null; }
public static System.Threading.RegisteredWaitHandle UnsafeRegisterWaitForSingleObject(System.Threading.WaitHandle waitObject, System.Threading.WaitOrTimerCallback callBack, object state, System.TimeSpan timeout, bool executeOnlyOnce) { throw null; }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Compat issues with assembly System.Threading.ThreadPool:
MembersMustExist : Member 'System.Threading.ThreadPool.UnsafeQueueUserWorkItem<TState>(System.Action<TState>, TState, System.Boolean)' does not exist in the implementation but it does exist in the contract.
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
MembersMustExist : Member 'System.Threading.ThreadPool.QueueUserWorkItem<TState>(System.Action<TState>, TState, System.Boolean)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'System.Threading.ThreadPool.QueueUserWorkItem<TState>(System.Action<TState>, TState, System.Boolean)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'System.Threading.ThreadPool.UnsafeQueueUserWorkItem<TState>(System.Action<TState>, TState, System.Boolean)' does not exist in the implementation but it does exist in the contract.
103 changes: 70 additions & 33 deletions src/System.Threading.ThreadPool/tests/ThreadPoolTests.netcoreapp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// 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.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
Expand All @@ -10,82 +11,120 @@ namespace System.Threading.ThreadPools.Tests
{
public partial class ThreadPoolTests
{
public static IEnumerable<object[]> OneBool() =>
from b in new[] { true, false }
select new object[] { b };

public static IEnumerable<object[]> TwoBools() =>
from b1 in new[] { true, false }
from b2 in new[] { true, false }
select new object[] { b1, b2 };

[Theory]
[InlineData(false)]
[InlineData(true)]
public void QueueUserWorkItem_PreferLocal_InvalidArguments_Throws(bool preferLocal)
[MemberData(nameof(TwoBools))]
public void QueueUserWorkItem_PreferLocal_InvalidArguments_Throws(bool preferLocal, bool useUnsafe)
{
AssertExtensions.Throws<ArgumentNullException>("callBack", () => ThreadPool.QueueUserWorkItem(null, new object(), preferLocal));
AssertExtensions.Throws<ArgumentNullException>("callBack", () => useUnsafe ?
ThreadPool.UnsafeQueueUserWorkItem(null, new object(), preferLocal) :
ThreadPool.QueueUserWorkItem(null, new object(), preferLocal));
}

[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task QueueUserWorkItem_PreferLocal_NullValidForState(bool preferLocal)
[MemberData(nameof(TwoBools))]
public async Task QueueUserWorkItem_PreferLocal_NullValidForState(bool preferLocal, bool useUnsafe)
{
var tcs = new TaskCompletionSource<int>();
ThreadPool.QueueUserWorkItem(s => tcs.SetResult(84), (object)null, preferLocal);
if (useUnsafe)
{
ThreadPool.UnsafeQueueUserWorkItem(s => tcs.SetResult(84), (object)null, preferLocal);
}
else
{
ThreadPool.QueueUserWorkItem(s => tcs.SetResult(84), (object)null, preferLocal);
}
Assert.Equal(84, await tcs.Task);
}

[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task QueueUserWorkItem_PreferLocal_ReferenceTypeStateObjectPassedThrough(bool preferLocal)
[MemberData(nameof(TwoBools))]
public async Task QueueUserWorkItem_PreferLocal_ReferenceTypeStateObjectPassedThrough(bool preferLocal, bool useUnsafe)
{
var tcs = new TaskCompletionSource<int>();
ThreadPool.QueueUserWorkItem(s => s.SetResult(84), tcs, preferLocal);
if (useUnsafe)
{
ThreadPool.UnsafeQueueUserWorkItem(s => s.SetResult(84), tcs, preferLocal);
}
else
{
ThreadPool.QueueUserWorkItem(s => s.SetResult(84), tcs, preferLocal);
}
Assert.Equal(84, await tcs.Task);
}

[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task QueueUserWorkItem_PreferLocal_ValueTypeStateObjectPassedThrough(bool preferLocal)
[MemberData(nameof(TwoBools))]
public async Task QueueUserWorkItem_PreferLocal_ValueTypeStateObjectPassedThrough(bool preferLocal, bool useUnsafe)
{
var tcs = new TaskCompletionSource<int>();
ThreadPool.QueueUserWorkItem(s => s.tcs.SetResult(s.value), (tcs, value: 42), preferLocal);
if (useUnsafe)
{
ThreadPool.UnsafeQueueUserWorkItem(s => s.tcs.SetResult(s.value), (tcs, value: 42), preferLocal);
}
else
{
ThreadPool.QueueUserWorkItem(s => s.tcs.SetResult(s.value), (tcs, value: 42), preferLocal);
}
Assert.Equal(42, await tcs.Task);
}

[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task QueueUserWorkItem_PreferLocal_RunsAsynchronously(bool preferLocal)
[MemberData(nameof(TwoBools))]
public async Task QueueUserWorkItem_PreferLocal_RunsAsynchronously(bool preferLocal, bool useUnsafe)
{
await Task.Factory.StartNew(() =>
{
int origThread = Environment.CurrentManagedThreadId;
var tcs = new TaskCompletionSource<int>();
ThreadPool.QueueUserWorkItem(s => s.SetResult(Environment.CurrentManagedThreadId), tcs, preferLocal);
if (useUnsafe)
{
ThreadPool.UnsafeQueueUserWorkItem(s => s.SetResult(Environment.CurrentManagedThreadId), tcs, preferLocal);
}
else
{
ThreadPool.QueueUserWorkItem(s => s.SetResult(Environment.CurrentManagedThreadId), tcs, preferLocal);
}
Assert.NotEqual(origThread, tcs.Task.GetAwaiter().GetResult());
}, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default);
}

[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task QueueUserWorkItem_PreferLocal_ExecutionContextFlowed(bool preferLocal)
[MemberData(nameof(TwoBools))]
public async Task QueueUserWorkItem_PreferLocal_ExecutionContextFlowedIfSafe(bool preferLocal, bool useUnsafe)
{
var tcs = new TaskCompletionSource<int>();
var asyncLocal = new AsyncLocal<int>() { Value = 42 };
ThreadPool.QueueUserWorkItem(s => s.SetResult(asyncLocal.Value), tcs, preferLocal);
if (useUnsafe)
{
ThreadPool.UnsafeQueueUserWorkItem(s => s.SetResult(asyncLocal.Value), tcs, preferLocal);
}
else
{
ThreadPool.QueueUserWorkItem(s => s.SetResult(asyncLocal.Value), tcs, preferLocal);
}
asyncLocal.Value = 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

asyncLocal.Value = 0; [](start = 12, length = 21)

Is it possible the queued work item can finish executing before executing this line? if so the following check can fail at that time in case of useunsafe

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My guess is that the unsafe version would run in the default execution context, where the AsyncLocal would not exist, defaulting to a value of 0.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. There should not be a race condition here.

Assert.Equal(42, await tcs.Task);
Assert.Equal(useUnsafe ? 0 : 42, await tcs.Task);
}

[Theory]
[InlineData(false)]
[InlineData(true)]
[MemberData(nameof(OneBool))]
public void UnsafeQueueUserWorkItem_IThreadPoolWorkItem_Invalid_Throws(bool preferLocal)
{
AssertExtensions.Throws<ArgumentNullException>("callBack", () => ThreadPool.UnsafeQueueUserWorkItem(null, preferLocal));
AssertExtensions.Throws<ArgumentOutOfRangeException>("callBack", () => ThreadPool.UnsafeQueueUserWorkItem(new InvalidWorkItemAndTask(() => { }), preferLocal));
}

[Theory]
[InlineData(false)]
[InlineData(true)]
[MemberData(nameof(OneBool))]
public async Task UnsafeQueueUserWorkItem_IThreadPoolWorkItem_ManyIndividualItems_AllInvoked(bool preferLocal)
{
TaskCompletionSource<bool>[] tasks = Enumerable.Range(0, 100).Select(_ => new TaskCompletionSource<bool>()).ToArray();
Expand All @@ -101,8 +140,7 @@ public async Task UnsafeQueueUserWorkItem_IThreadPoolWorkItem_ManyIndividualItem
}

[Theory]
[InlineData(false)]
[InlineData(true)]
[MemberData(nameof(OneBool))]
public async Task UnsafeQueueUserWorkItem_IThreadPoolWorkItem_SameObjectReused_AllInvoked(bool preferLocal)
{
const int Iters = 100;
Expand All @@ -124,8 +162,7 @@ public async Task UnsafeQueueUserWorkItem_IThreadPoolWorkItem_SameObjectReused_A
}

[Theory]
[InlineData(false)]
[InlineData(true)]
[MemberData(nameof(OneBool))]
public async Task UnsafeQueueUserWorkItem_IThreadPoolWorkItem_ExecutionContextNotFlowed(bool preferLocal)
{
var al = new AsyncLocal<int> { Value = 42 };
Expand Down