Skip to content

Commit fb953cc

Browse files
pavelsavaramaraf
andauthored
[browser][MT] send cancel to abandoned threads with event loop during mono_exit (#97441)
Co-authored-by: Marek Fišera <[email protected]>
1 parent 742039f commit fb953cc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1026
-801
lines changed

src/libraries/Common/tests/WasmTestRunner/WasmTestRunner.cs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public static async Task<int> Main(string[] args)
2424
var includedClasses = new List<string>();
2525
var includedMethods = new List<string>();
2626
var backgroundExec = false;
27+
var untilFailed = false;
2728

2829
for (int i = 1; i < args.Length; i++)
2930
{
@@ -53,6 +54,9 @@ public static async Task<int> Main(string[] args)
5354
case "-backgroundExec":
5455
backgroundExec = true;
5556
break;
57+
case "-untilFailed":
58+
untilFailed = true;
59+
break;
5660
default:
5761
throw new ArgumentException($"Invalid argument '{option}'.");
5862
}
@@ -72,10 +76,21 @@ public static async Task<int> Main(string[] args)
7276
{
7377
await Task.Yield();
7478
}
75-
if (backgroundExec)
79+
80+
var res = 0;
81+
do
7682
{
77-
return await Task.Run(() => runner.Run());
83+
if (backgroundExec)
84+
{
85+
res = await Task.Run(() => runner.Run());
86+
}
87+
else
88+
{
89+
res = await runner.Run();
90+
}
7891
}
79-
return await runner.Run();
92+
while(res == 0 && untilFailed);
93+
94+
return res;
8095
}
8196
}

src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSWebWorker.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ private void Dispose(bool disposing)
194194
{
195195
_cancellationRegistration?.Dispose();
196196
_cancellationRegistration = null;
197+
_thread?.Join(50);
197198
}
198199

199200
if (_jsSynchronizationContext != null)

src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System.Runtime.InteropServices.JavaScript.Tests.csproj

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,33 +13,39 @@
1313
<!-- Use following lines to write the generated files to disk. -->
1414
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
1515
<!-- to see timing and which test aborted the runtime -->
16-
<WasmXHarnessMonoArgs>$(WasmXHarnessMonoArgs) --setenv=XHARNESS_LOG_TEST_START=true</WasmXHarnessMonoArgs>
16+
<WasmXHarnessMonoArgs>$(WasmXHarnessMonoArgs) --setenv=XHARNESS_LOG_TEST_START=true --no-memory-snapshot</WasmXHarnessMonoArgs>
1717
</PropertyGroup>
1818
<!-- Make debugging easier -->
1919
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
2020
<WasmNativeStrip>false</WasmNativeStrip>
21-
<WasmXHarnessMonoArgs>$(WasmXHarnessMonoArgs) --setenv=XHARNESS_LOG_TEST_START=true</WasmXHarnessMonoArgs>
2221
</PropertyGroup>
2322
<ItemGroup>
2423
<Compile Include="System\Runtime\InteropServices\JavaScript\JavaScriptTestHelper.cs" />
2524
<Compile Include="System\Runtime\InteropServices\JavaScript\JSImportExportTest.cs" />
26-
<Compile Include="System\Runtime\InteropServices\JavaScript\SecondRuntimeTest.cs" />
27-
<Compile Include="System\Runtime\InteropServices\JavaScript\HttpRequestMessageTest.cs" />
28-
<Compile Include="System\Runtime\InteropServices\JavaScript\TimerTests.cs" />
2925
<Compile Include="System\Runtime\InteropServices\JavaScript\Utils.cs" />
3026
<None Include="System\Runtime\InteropServices\JavaScript\JavaScriptTestHelper.mjs" />
3127
<None Include="$(CompilerGeneratedFilesOutputPath)\..\browser-wasm\generated\Microsoft.Interop.JavaScript.JSImportGenerator\Microsoft.Interop.JavaScript.JSImportGenerator\JSImports.g.cs" />
3228
<None Include="$(CompilerGeneratedFilesOutputPath)\..\browser-wasm\generated\Microsoft.Interop.JavaScript.JSImportGenerator\Microsoft.Interop.JavaScript.JsExportGenerator\JSExports.g.cs" />
3329
<WasmExtraFilesToDeploy Include="System\Runtime\InteropServices\JavaScript\JavaScriptTestHelper.mjs" />
3430
<WasmExtraFilesToDeploy Include="System\Runtime\InteropServices\JavaScript\SecondRuntimeTest.js" />
3531
<WasmExtraFilesToDeploy Include="System\Runtime\InteropServices\JavaScript\timers.mjs" />
36-
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\src\System.Runtime.InteropServices.JavaScript.csproj" SkipUseReferenceAssembly="true"/>
32+
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\src\System.Runtime.InteropServices.JavaScript.csproj" SkipUseReferenceAssembly="true" />
33+
</ItemGroup>
34+
<ItemGroup Condition="'$(FeatureWasmThreads)' != 'true'">
35+
<Compile Include="System\Runtime\InteropServices\JavaScript\SecondRuntimeTest.cs" />
36+
<Compile Include="System\Runtime\InteropServices\JavaScript\HttpRequestMessageTest.cs" />
37+
<Compile Include="System\Runtime\InteropServices\JavaScript\TimerTests.cs" />
3738
</ItemGroup>
38-
<ItemGroup Condition="'$(FeatureWasmThreads)' == 'true'" >
39+
<ItemGroup Condition="'$(FeatureWasmThreads)' == 'true'">
40+
<Compile Include="System\Runtime\InteropServices\JavaScript\WebWorkerTestBase.cs" />
3941
<Compile Include="System\Runtime\InteropServices\JavaScript\WebWorkerTest.cs" />
42+
<Compile Include="System\Runtime\InteropServices\JavaScript\WebWorkerTest.Http.cs" />
43+
<Compile Include="System\Runtime\InteropServices\JavaScript\WebWorkerTest.WebSocket.cs" />
4044
<Compile Include="System\Runtime\InteropServices\JavaScript\WebWorkerTestHelper.cs" />
4145
<None Include="System\Runtime\InteropServices\JavaScript\WebWorkerTestHelper.mjs" />
46+
<None Include="System\Runtime\InteropServices\JavaScript\test.json" />
4247
<WasmExtraFilesToDeploy Include="System\Runtime\InteropServices\JavaScript\WebWorkerTestHelper.mjs" />
43-
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\src\System.Runtime.InteropServices.JavaScript.csproj" SkipUseReferenceAssembly="true"/>
48+
<WasmExtraFilesToDeploy Include="System\Runtime\InteropServices\JavaScript\test.json" />
49+
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\src\System.Runtime.InteropServices.JavaScript.csproj" SkipUseReferenceAssembly="true" />
4450
</ItemGroup>
4551
</Project>

src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/SecondRuntimeTest.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@ public partial class SecondRuntimeTest
1414
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsBrowserDomSupportedOrNodeJS))]
1515
public static async Task RunSecondRuntimeAndTestStaticState()
1616
{
17-
await JSHost.ImportAsync("SecondRuntimeTest", "../SecondRuntimeTest.js");
17+
var runId = Guid.NewGuid().ToString();
18+
await JSHost.ImportAsync("SecondRuntimeTest", "../SecondRuntimeTest.js?run=" + runId);
1819

1920
Interop.State = 42;
20-
var state2 = await Interop.RunSecondRuntimeAndTestStaticState();
21+
var state2 = await Interop.RunSecondRuntimeAndTestStaticState(runId);
2122
Assert.Equal(44, Interop.State);
2223
Assert.Equal(3, state2);
2324
}
@@ -31,7 +32,7 @@ public static partial class Interop
3132

3233
[JSImport("runSecondRuntimeAndTestStaticState", "SecondRuntimeTest")]
3334
[return: JSMarshalAs<JSType.Promise<JSType.Number>>]
34-
internal static partial Task<int> RunSecondRuntimeAndTestStaticState();
35+
internal static partial Task<int> RunSecondRuntimeAndTestStaticState(string guid);
3536
}
3637
}
3738
}

src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/SecondRuntimeTest.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
export async function runSecondRuntimeAndTestStaticState() {
2-
const { dotnet: dotnet2 } = await import('./_framework/dotnet.js?instance=2');
1+
export async function runSecondRuntimeAndTestStaticState(guid) {
2+
const { dotnet: dotnet2 } = await import('./_framework/dotnet.js?instance=2-' + guid);
33
const runtime2 = await dotnet2
44
.withStartupMemoryCache(false)
55
.withConfig({
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Threading.Tasks;
5+
using System.Threading;
6+
using System.Net.Http;
7+
using Xunit;
8+
using System.IO;
9+
using System.Text;
10+
11+
namespace System.Runtime.InteropServices.JavaScript.Tests
12+
{
13+
public class WebWorkerHttpTest : WebWorkerTestBase
14+
{
15+
#region HTTP
16+
17+
[Theory, MemberData(nameof(GetTargetThreads))]
18+
public async Task HttpClient_ContentInSameThread(Executor executor)
19+
{
20+
using var cts = CreateTestCaseTimeoutSource();
21+
var uri = WebWorkerTestHelper.GetOriginUrl() + "/test.json";
22+
23+
await executor.Execute(async () =>
24+
{
25+
using var client = new HttpClient();
26+
using var response = await client.GetAsync(uri);
27+
response.EnsureSuccessStatusCode();
28+
var body = await response.Content.ReadAsStringAsync();
29+
Assert.Contains("hello", body);
30+
Assert.Contains("world", body);
31+
}, cts.Token);
32+
}
33+
34+
private static HttpRequestOptionsKey<bool> WebAssemblyEnableStreamingRequestKey = new("WebAssemblyEnableStreamingRequest");
35+
private static HttpRequestOptionsKey<bool> WebAssemblyEnableStreamingResponseKey = new("WebAssemblyEnableStreamingResponse");
36+
private static string HelloJson = "{'hello':'world'}".Replace('\'', '"');
37+
private static string EchoStart = "{\"Method\":\"POST\",\"Url\":\"/Echo.ashx";
38+
39+
private async Task HttpClient_ActionInDifferentThread(string url, Executor executor1, Executor executor2, Func<HttpResponseMessage, Task> e2Job)
40+
{
41+
using var cts = CreateTestCaseTimeoutSource();
42+
43+
var e1Job = async (Task e2done, TaskCompletionSource<HttpResponseMessage> e1State) =>
44+
{
45+
using var ms = new MemoryStream();
46+
await ms.WriteAsync(Encoding.UTF8.GetBytes(HelloJson));
47+
48+
using var req = new HttpRequestMessage(HttpMethod.Post, url);
49+
req.Options.Set(WebAssemblyEnableStreamingResponseKey, true);
50+
req.Content = new StreamContent(ms);
51+
using var client = new HttpClient();
52+
var pr = client.SendAsync(req, HttpCompletionOption.ResponseHeadersRead);
53+
using var response = await pr;
54+
55+
// share the state with the E2 continuation
56+
e1State.SetResult(response);
57+
58+
await e2done;
59+
};
60+
await ActionsInDifferentThreads<HttpResponseMessage>(executor1, executor2, e1Job, e2Job, cts);
61+
}
62+
63+
[Theory, MemberData(nameof(GetTargetThreads2x))]
64+
public async Task HttpClient_ContentInDifferentThread(Executor executor1, Executor executor2)
65+
{
66+
var url = WebWorkerTestHelper.LocalHttpEcho + "?guid=" + Guid.NewGuid();
67+
await HttpClient_ActionInDifferentThread(url, executor1, executor2, async (HttpResponseMessage response) =>
68+
{
69+
response.EnsureSuccessStatusCode();
70+
var body = await response.Content.ReadAsStringAsync();
71+
Assert.StartsWith(EchoStart, body);
72+
});
73+
}
74+
75+
[Theory, MemberData(nameof(GetTargetThreads2x))]
76+
public async Task HttpClient_CancelInDifferentThread(Executor executor1, Executor executor2)
77+
{
78+
var url = WebWorkerTestHelper.LocalHttpEcho + "?delay10sec=true&guid=" + Guid.NewGuid();
79+
await HttpClient_ActionInDifferentThread(url, executor1, executor2, async (HttpResponseMessage response) =>
80+
{
81+
await Assert.ThrowsAsync<TaskCanceledException>(async () =>
82+
{
83+
CancellationTokenSource cts = new CancellationTokenSource();
84+
var promise = response.Content.ReadAsStringAsync(cts.Token);
85+
cts.Cancel();
86+
await promise;
87+
});
88+
});
89+
}
90+
91+
#endregion
92+
}
93+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Threading.Tasks;
5+
using System.Threading;
6+
using Xunit;
7+
using System.Net.WebSockets;
8+
using System.Text;
9+
10+
namespace System.Runtime.InteropServices.JavaScript.Tests
11+
{
12+
public class WebWorkerWebSocketTest : WebWorkerTestBase
13+
{
14+
#region WebSocket
15+
16+
[Theory, MemberData(nameof(GetTargetThreads))]
17+
public async Task WebSocketClient_ContentInSameThread(Executor executor)
18+
{
19+
using var cts = CreateTestCaseTimeoutSource();
20+
21+
var uri = new Uri(WebWorkerTestHelper.LocalWsEcho + "?guid=" + Guid.NewGuid());
22+
var message = "hello";
23+
var send = Encoding.UTF8.GetBytes(message);
24+
var receive = new byte[100];
25+
26+
await executor.Execute(async () =>
27+
{
28+
using var client = new ClientWebSocket();
29+
await client.ConnectAsync(uri, CancellationToken.None);
30+
await client.SendAsync(send, WebSocketMessageType.Text, true, CancellationToken.None);
31+
32+
var res = await client.ReceiveAsync(receive, CancellationToken.None);
33+
Assert.Equal(WebSocketMessageType.Text, res.MessageType);
34+
Assert.True(res.EndOfMessage);
35+
Assert.Equal(send.Length, res.Count);
36+
Assert.Equal(message, Encoding.UTF8.GetString(receive, 0, res.Count));
37+
}, cts.Token);
38+
}
39+
40+
41+
[Theory, MemberData(nameof(GetTargetThreads2x))]
42+
public async Task WebSocketClient_ResponseCloseInDifferentThread(Executor executor1, Executor executor2)
43+
{
44+
using var cts = CreateTestCaseTimeoutSource();
45+
46+
var uri = new Uri(WebWorkerTestHelper.LocalWsEcho + "?guid=" + Guid.NewGuid());
47+
var message = "hello";
48+
var send = Encoding.UTF8.GetBytes(message);
49+
var receive = new byte[100];
50+
51+
var e1Job = async (Task e2done, TaskCompletionSource<ClientWebSocket> e1State) =>
52+
{
53+
using var client = new ClientWebSocket();
54+
await client.ConnectAsync(uri, CancellationToken.None);
55+
await client.SendAsync(send, WebSocketMessageType.Text, true, CancellationToken.None);
56+
57+
// share the state with the E2 continuation
58+
e1State.SetResult(client);
59+
await e2done;
60+
};
61+
62+
var e2Job = async (ClientWebSocket client) =>
63+
{
64+
var res = await client.ReceiveAsync(receive, CancellationToken.None);
65+
Assert.Equal(WebSocketMessageType.Text, res.MessageType);
66+
Assert.True(res.EndOfMessage);
67+
Assert.Equal(send.Length, res.Count);
68+
Assert.Equal(message, Encoding.UTF8.GetString(receive, 0, res.Count));
69+
70+
await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "bye", CancellationToken.None);
71+
};
72+
73+
await ActionsInDifferentThreads<ClientWebSocket>(executor1, executor2, e1Job, e2Job, cts);
74+
}
75+
76+
[Theory, MemberData(nameof(GetTargetThreads2x))]
77+
public async Task WebSocketClient_CancelInDifferentThread(Executor executor1, Executor executor2)
78+
{
79+
using var cts = CreateTestCaseTimeoutSource();
80+
81+
var uri = new Uri(WebWorkerTestHelper.LocalWsEcho + "?guid=" + Guid.NewGuid());
82+
var message = ".delay5sec"; // this will make the loopback server slower
83+
var send = Encoding.UTF8.GetBytes(message);
84+
var receive = new byte[100];
85+
86+
var e1Job = async (Task e2done, TaskCompletionSource<ClientWebSocket> e1State) =>
87+
{
88+
using var client = new ClientWebSocket();
89+
await client.ConnectAsync(uri, CancellationToken.None);
90+
await client.SendAsync(send, WebSocketMessageType.Text, true, CancellationToken.None);
91+
92+
// share the state with the E2 continuation
93+
e1State.SetResult(client);
94+
await e2done;
95+
};
96+
97+
var e2Job = async (ClientWebSocket client) =>
98+
{
99+
CancellationTokenSource cts2 = new CancellationTokenSource();
100+
var resTask = client.ReceiveAsync(receive, cts2.Token);
101+
cts2.Cancel();
102+
var ex = await Assert.ThrowsAnyAsync<OperationCanceledException>(() => resTask);
103+
Assert.Equal(cts2.Token, ex.CancellationToken);
104+
};
105+
106+
await ActionsInDifferentThreads<ClientWebSocket>(executor1, executor2, e1Job, e2Job, cts);
107+
}
108+
109+
#endregion
110+
}
111+
}

0 commit comments

Comments
 (0)