Skip to content

Commit b61a58e

Browse files
committed
Task.Run does not guarantee using new thread
1 parent df6c2a3 commit b61a58e

File tree

9 files changed

+77
-25
lines changed

9 files changed

+77
-25
lines changed

.ado/publish.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,6 @@ extends:
259259
- script: >
260260
dotnet test -f ${{ MatrixEntry.DotNetVersion }}
261261
--configuration Release
262-
--property:ParallelizeTestCollections=false
263262
--logger trx
264263
--results-directory "$(Build.StagingDirectory)/test/${{ MatrixEntry.DotNetVersion }}-Release"
265264
displayName: Run tests

.github/workflows/build.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ jobs:
7777
run: >
7878
dotnet test -f ${{ matrix.dotnet-version }}
7979
--configuration ${{ matrix.configuration }}
80-
--property:ParallelizeTestCollections=false
8180
--logger trx
8281
--results-directory "out/test/${{matrix.dotnet-version}}-node${{matrix.node-version}}-${{matrix.configuration}}"
8382
continue-on-error: true

Directory.Packages.props

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
<PackageVersion Include="System.Reflection.MetadataLoadContext" Version="6.0.0" />
1818
<PackageVersion Include="xunit" Version="2.4.2" />
1919
<PackageVersion Include="xunit.runner.visualstudio" Version="2.4.5" />
20-
<PackageVersion Include="Xunit.SkippableFact" Version="1.4.13" />
2120
<PackageVersion Include="XmlDocMarkdown.Core" Version="2.9.0" />
2221
</ItemGroup>
2322
</Project>

test/GCTests.cs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,9 @@ public class GCTests
1212
{
1313
private static string LibnodePath { get; } = GetLibnodePath();
1414

15-
[SkippableFact]
15+
[Fact]
1616
public void GCHandles()
1717
{
18-
Skip.If(
19-
NodejsEmbeddingTests.NodejsPlatform == null,
20-
"Node shared library not found at " + LibnodePath);
2118
using NodeEmbeddingThreadRuntime nodejs = NodejsEmbeddingTests.CreateNodejsEnvironment();
2219

2320
nodejs.Run(() =>
@@ -63,12 +60,9 @@ public void GCHandles()
6360
});
6461
}
6562

66-
[SkippableFact]
63+
[Fact]
6764
public void GCObjects()
6865
{
69-
Skip.If(
70-
NodejsEmbeddingTests.NodejsPlatform == null,
71-
"Node shared library not found at " + LibnodePath);
7266
using NodeEmbeddingThreadRuntime nodejs = NodejsEmbeddingTests.CreateNodejsEnvironment();
7367

7468
nodejs.Run(() =>

test/JSReferenceTests.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Licensed under the MIT License.
33

44
using System;
5-
using System.Threading.Tasks;
65
using Xunit;
76
using static Microsoft.JavaScript.NodeApi.Runtime.JSRuntime;
87

@@ -52,7 +51,7 @@ public void GetReferenceFromDifferentThread()
5251
JSReference reference = new(value);
5352

5453
// Run in a new thread which will not have any current scope.
55-
Task.Run(() =>
54+
TestUtils.RunInThread(() =>
5655
{
5756
Assert.Throws<JSInvalidThreadAccessException>(() => reference.GetValue());
5857
}).Wait();
@@ -67,7 +66,7 @@ public void GetReferenceFromDifferentRootScope()
6766
JSReference reference = new(value);
6867

6968
// Run in a new thread and establish another root scope there.
70-
Task.Run(() =>
69+
TestUtils.RunInThread(() =>
7170
{
7271
using JSValueScope rootScope2 = TestScope(JSValueScopeType.Root);
7372
Assert.Throws<JSInvalidThreadAccessException>(() => reference.GetValue());

test/JSValueScopeTests.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Licensed under the MIT License.
33

44
using System;
5-
using System.Threading.Tasks;
65
using Xunit;
76
using static Microsoft.JavaScript.NodeApi.Runtime.JSRuntime;
87

@@ -325,7 +324,7 @@ public void CreateValueFromDifferentThread()
325324
using JSValueScope rootScope = TestScope(JSValueScopeType.Root);
326325

327326
// Run in a new thread which will not have any current scope.
328-
Task.Run(() =>
327+
TestUtils.RunInThread(() =>
329328
{
330329
Assert.Throws<JSInvalidThreadAccessException>(() => JSValueScope.Current);
331330
JSInvalidThreadAccessException ex = Assert.Throws<JSInvalidThreadAccessException>(
@@ -342,7 +341,7 @@ public void AccessValueFromDifferentThread()
342341
JSValue objectValue = JSValue.CreateObject();
343342

344343
// Run in a new thread which will not have any current scope.
345-
Task.Run(() =>
344+
TestUtils.RunInThread(() =>
346345
{
347346
Assert.Throws<JSInvalidThreadAccessException>(() => JSValueScope.Current);
348347
JSInvalidThreadAccessException ex = Assert.Throws<JSInvalidThreadAccessException>(
@@ -359,7 +358,7 @@ public void AccessValueFromDifferentRootScope()
359358
JSValue objectValue = JSValue.CreateObject();
360359

361360
// Run in a new thread and establish another root scope there.
362-
Task.Run(() =>
361+
TestUtils.RunInThread(() =>
363362
{
364363
using JSValueScope rootScope2 = TestScope(JSValueScopeType.Root);
365364
Assert.Equal(JSValueScopeType.Root, JSValueScope.Current.ScopeType);

test/NodeApi.Test.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
<PackageReference Include="Microsoft.NET.Test.Sdk" />
2525
<PackageReference Include="xunit" />
2626
<PackageReference Include="xunit.runner.visualstudio" />
27-
<PackageReference Include="Xunit.SkippableFact" />
2827
</ItemGroup>
2928

3029
<ItemGroup>

test/TestCases/napi-dotnet/ThreadSafety.cs

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
// Copyright (c) Microsoft Corporation.
1+
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Threading;
67
using System.Threading.Tasks;
78

89
namespace Microsoft.JavaScript.NodeApi.TestCases;
@@ -36,7 +37,7 @@ private static void ValidateNotOnJSThread()
3637

3738
public static async Task CallDelegateFromOtherThread(Action action)
3839
{
39-
await Task.Run(() =>
40+
await RunInThread(() =>
4041
{
4142
ValidateNotOnJSThread();
4243

@@ -48,7 +49,7 @@ public static async Task<string> CallInterfaceMethodFromOtherThread(
4849
ISmpleInterface interfaceObj,
4950
string value)
5051
{
51-
return await Task.Run(() =>
52+
return await RunInThread(() =>
5253
{
5354
ValidateNotOnJSThread();
5455

@@ -59,7 +60,7 @@ public static async Task<string> CallInterfaceMethodFromOtherThread(
5960
public static async Task<int> EnumerateCollectionFromOtherThread(
6061
IReadOnlyCollection<int> collection)
6162
{
62-
return await Task.Run(() =>
63+
return await RunInThread(() =>
6364
{
6465
ValidateNotOnJSThread();
6566

@@ -76,7 +77,7 @@ public static async Task<int> EnumerateCollectionFromOtherThread(
7677
public static async Task<int> EnumerateDictionaryFromOtherThread(
7778
IReadOnlyDictionary<string, string> dictionary)
7879
{
79-
return await Task.Run(() =>
80+
return await RunInThread(() =>
8081
{
8182
ValidateNotOnJSThread();
8283

@@ -93,11 +94,52 @@ public static async Task<int> EnumerateDictionaryFromOtherThread(
9394
public static async Task<bool> ModifyDictionaryFromOtherThread(
9495
IDictionary<string, string> dictionary, string keyToRemove)
9596
{
96-
return await Task.Run(() =>
97+
return await RunInThread(() =>
9798
{
9899
ValidateNotOnJSThread();
99100

100101
return dictionary.Remove(keyToRemove);
101102
});
102103
}
104+
105+
private static Task RunInThread(Action action)
106+
{
107+
TaskCompletionSource<bool> threadCompletion = new TaskCompletionSource<bool>();
108+
109+
Thread thread = new Thread(() =>
110+
{
111+
try
112+
{
113+
action();
114+
threadCompletion.TrySetResult(true);
115+
}
116+
catch (Exception e)
117+
{
118+
threadCompletion.TrySetException(e);
119+
}
120+
});
121+
thread.Start();
122+
123+
return threadCompletion.Task;
124+
}
125+
126+
private static Task<T> RunInThread<T>(Func<T> func)
127+
{
128+
TaskCompletionSource<T> threadCompletion = new TaskCompletionSource<T>();
129+
130+
Thread thread = new Thread(() =>
131+
{
132+
try
133+
{
134+
threadCompletion.TrySetResult(func());
135+
}
136+
catch (Exception e)
137+
{
138+
threadCompletion.TrySetException(e);
139+
}
140+
});
141+
thread.Start();
142+
143+
return threadCompletion.Task;
144+
}
103145
}

test/TestUtils.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Runtime.InteropServices;
88
using System.Text;
99
using System.Threading;
10+
using System.Threading.Tasks;
1011

1112
namespace Microsoft.JavaScript.NodeApi.Test;
1213

@@ -145,4 +146,25 @@ public static void CopyIfNewer(string sourceFilePath, string targetFilePath)
145146
File.Copy(sourceFilePath, targetFilePath, overwrite: true);
146147
}
147148
}
149+
150+
public static Task RunInThread(Action action)
151+
{
152+
TaskCompletionSource<bool> threadCompletion = new TaskCompletionSource<bool>();
153+
154+
Thread thread = new Thread(() =>
155+
{
156+
try
157+
{
158+
action();
159+
threadCompletion.TrySetResult(true);
160+
}
161+
catch (Exception e)
162+
{
163+
threadCompletion.TrySetException(e);
164+
}
165+
});
166+
thread.Start();
167+
168+
return threadCompletion.Task;
169+
}
148170
}

0 commit comments

Comments
 (0)