Skip to content

Commit

Permalink
[browser][MT] send cancel to abandoned threads with event loop during…
Browse files Browse the repository at this point in the history
… mono_exit (#97441)

Co-authored-by: Marek Fišera <[email protected]>
  • Loading branch information
pavelsavara and maraf authored Jan 29, 2024
1 parent 742039f commit fb953cc
Show file tree
Hide file tree
Showing 45 changed files with 1,026 additions and 801 deletions.
21 changes: 18 additions & 3 deletions src/libraries/Common/tests/WasmTestRunner/WasmTestRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public static async Task<int> Main(string[] args)
var includedClasses = new List<string>();
var includedMethods = new List<string>();
var backgroundExec = false;
var untilFailed = false;

for (int i = 1; i < args.Length; i++)
{
Expand Down Expand Up @@ -53,6 +54,9 @@ public static async Task<int> Main(string[] args)
case "-backgroundExec":
backgroundExec = true;
break;
case "-untilFailed":
untilFailed = true;
break;
default:
throw new ArgumentException($"Invalid argument '{option}'.");
}
Expand All @@ -72,10 +76,21 @@ public static async Task<int> Main(string[] args)
{
await Task.Yield();
}
if (backgroundExec)

var res = 0;
do
{
return await Task.Run(() => runner.Run());
if (backgroundExec)
{
res = await Task.Run(() => runner.Run());
}
else
{
res = await runner.Run();
}
}
return await runner.Run();
while(res == 0 && untilFailed);

return res;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ private void Dispose(bool disposing)
{
_cancellationRegistration?.Dispose();
_cancellationRegistration = null;
_thread?.Join(50);
}

if (_jsSynchronizationContext != null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,33 +13,39 @@
<!-- Use following lines to write the generated files to disk. -->
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<!-- to see timing and which test aborted the runtime -->
<WasmXHarnessMonoArgs>$(WasmXHarnessMonoArgs) --setenv=XHARNESS_LOG_TEST_START=true</WasmXHarnessMonoArgs>
<WasmXHarnessMonoArgs>$(WasmXHarnessMonoArgs) --setenv=XHARNESS_LOG_TEST_START=true --no-memory-snapshot</WasmXHarnessMonoArgs>
</PropertyGroup>
<!-- Make debugging easier -->
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<WasmNativeStrip>false</WasmNativeStrip>
<WasmXHarnessMonoArgs>$(WasmXHarnessMonoArgs) --setenv=XHARNESS_LOG_TEST_START=true</WasmXHarnessMonoArgs>
</PropertyGroup>
<ItemGroup>
<Compile Include="System\Runtime\InteropServices\JavaScript\JavaScriptTestHelper.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\JSImportExportTest.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\SecondRuntimeTest.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\HttpRequestMessageTest.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\TimerTests.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\Utils.cs" />
<None Include="System\Runtime\InteropServices\JavaScript\JavaScriptTestHelper.mjs" />
<None Include="$(CompilerGeneratedFilesOutputPath)\..\browser-wasm\generated\Microsoft.Interop.JavaScript.JSImportGenerator\Microsoft.Interop.JavaScript.JSImportGenerator\JSImports.g.cs" />
<None Include="$(CompilerGeneratedFilesOutputPath)\..\browser-wasm\generated\Microsoft.Interop.JavaScript.JSImportGenerator\Microsoft.Interop.JavaScript.JsExportGenerator\JSExports.g.cs" />
<WasmExtraFilesToDeploy Include="System\Runtime\InteropServices\JavaScript\JavaScriptTestHelper.mjs" />
<WasmExtraFilesToDeploy Include="System\Runtime\InteropServices\JavaScript\SecondRuntimeTest.js" />
<WasmExtraFilesToDeploy Include="System\Runtime\InteropServices\JavaScript\timers.mjs" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\src\System.Runtime.InteropServices.JavaScript.csproj" SkipUseReferenceAssembly="true"/>
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\src\System.Runtime.InteropServices.JavaScript.csproj" SkipUseReferenceAssembly="true" />
</ItemGroup>
<ItemGroup Condition="'$(FeatureWasmThreads)' != 'true'">
<Compile Include="System\Runtime\InteropServices\JavaScript\SecondRuntimeTest.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\HttpRequestMessageTest.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\TimerTests.cs" />
</ItemGroup>
<ItemGroup Condition="'$(FeatureWasmThreads)' == 'true'" >
<ItemGroup Condition="'$(FeatureWasmThreads)' == 'true'">
<Compile Include="System\Runtime\InteropServices\JavaScript\WebWorkerTestBase.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\WebWorkerTest.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\WebWorkerTest.Http.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\WebWorkerTest.WebSocket.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\WebWorkerTestHelper.cs" />
<None Include="System\Runtime\InteropServices\JavaScript\WebWorkerTestHelper.mjs" />
<None Include="System\Runtime\InteropServices\JavaScript\test.json" />
<WasmExtraFilesToDeploy Include="System\Runtime\InteropServices\JavaScript\WebWorkerTestHelper.mjs" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\src\System.Runtime.InteropServices.JavaScript.csproj" SkipUseReferenceAssembly="true"/>
<WasmExtraFilesToDeploy Include="System\Runtime\InteropServices\JavaScript\test.json" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\src\System.Runtime.InteropServices.JavaScript.csproj" SkipUseReferenceAssembly="true" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ public partial class SecondRuntimeTest
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsBrowserDomSupportedOrNodeJS))]
public static async Task RunSecondRuntimeAndTestStaticState()
{
await JSHost.ImportAsync("SecondRuntimeTest", "../SecondRuntimeTest.js");
var runId = Guid.NewGuid().ToString();
await JSHost.ImportAsync("SecondRuntimeTest", "../SecondRuntimeTest.js?run=" + runId);

Interop.State = 42;
var state2 = await Interop.RunSecondRuntimeAndTestStaticState();
var state2 = await Interop.RunSecondRuntimeAndTestStaticState(runId);
Assert.Equal(44, Interop.State);
Assert.Equal(3, state2);
}
Expand All @@ -31,7 +32,7 @@ public static partial class Interop

[JSImport("runSecondRuntimeAndTestStaticState", "SecondRuntimeTest")]
[return: JSMarshalAs<JSType.Promise<JSType.Number>>]
internal static partial Task<int> RunSecondRuntimeAndTestStaticState();
internal static partial Task<int> RunSecondRuntimeAndTestStaticState(string guid);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export async function runSecondRuntimeAndTestStaticState() {
const { dotnet: dotnet2 } = await import('./_framework/dotnet.js?instance=2');
export async function runSecondRuntimeAndTestStaticState(guid) {
const { dotnet: dotnet2 } = await import('./_framework/dotnet.js?instance=2-' + guid);
const runtime2 = await dotnet2
.withStartupMemoryCache(false)
.withConfig({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Threading.Tasks;
using System.Threading;
using System.Net.Http;
using Xunit;
using System.IO;
using System.Text;

namespace System.Runtime.InteropServices.JavaScript.Tests
{
public class WebWorkerHttpTest : WebWorkerTestBase
{
#region HTTP

[Theory, MemberData(nameof(GetTargetThreads))]
public async Task HttpClient_ContentInSameThread(Executor executor)
{
using var cts = CreateTestCaseTimeoutSource();
var uri = WebWorkerTestHelper.GetOriginUrl() + "/test.json";

await executor.Execute(async () =>
{
using var client = new HttpClient();
using var response = await client.GetAsync(uri);
response.EnsureSuccessStatusCode();
var body = await response.Content.ReadAsStringAsync();
Assert.Contains("hello", body);
Assert.Contains("world", body);
}, cts.Token);
}

private static HttpRequestOptionsKey<bool> WebAssemblyEnableStreamingRequestKey = new("WebAssemblyEnableStreamingRequest");
private static HttpRequestOptionsKey<bool> WebAssemblyEnableStreamingResponseKey = new("WebAssemblyEnableStreamingResponse");
private static string HelloJson = "{'hello':'world'}".Replace('\'', '"');
private static string EchoStart = "{\"Method\":\"POST\",\"Url\":\"/Echo.ashx";

private async Task HttpClient_ActionInDifferentThread(string url, Executor executor1, Executor executor2, Func<HttpResponseMessage, Task> e2Job)
{
using var cts = CreateTestCaseTimeoutSource();

var e1Job = async (Task e2done, TaskCompletionSource<HttpResponseMessage> e1State) =>
{
using var ms = new MemoryStream();
await ms.WriteAsync(Encoding.UTF8.GetBytes(HelloJson));

using var req = new HttpRequestMessage(HttpMethod.Post, url);
req.Options.Set(WebAssemblyEnableStreamingResponseKey, true);
req.Content = new StreamContent(ms);
using var client = new HttpClient();
var pr = client.SendAsync(req, HttpCompletionOption.ResponseHeadersRead);
using var response = await pr;

// share the state with the E2 continuation
e1State.SetResult(response);

await e2done;
};
await ActionsInDifferentThreads<HttpResponseMessage>(executor1, executor2, e1Job, e2Job, cts);
}

[Theory, MemberData(nameof(GetTargetThreads2x))]
public async Task HttpClient_ContentInDifferentThread(Executor executor1, Executor executor2)
{
var url = WebWorkerTestHelper.LocalHttpEcho + "?guid=" + Guid.NewGuid();
await HttpClient_ActionInDifferentThread(url, executor1, executor2, async (HttpResponseMessage response) =>
{
response.EnsureSuccessStatusCode();
var body = await response.Content.ReadAsStringAsync();
Assert.StartsWith(EchoStart, body);
});
}

[Theory, MemberData(nameof(GetTargetThreads2x))]
public async Task HttpClient_CancelInDifferentThread(Executor executor1, Executor executor2)
{
var url = WebWorkerTestHelper.LocalHttpEcho + "?delay10sec=true&guid=" + Guid.NewGuid();
await HttpClient_ActionInDifferentThread(url, executor1, executor2, async (HttpResponseMessage response) =>
{
await Assert.ThrowsAsync<TaskCanceledException>(async () =>
{
CancellationTokenSource cts = new CancellationTokenSource();
var promise = response.Content.ReadAsStringAsync(cts.Token);
cts.Cancel();
await promise;
});
});
}

#endregion
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Threading.Tasks;
using System.Threading;
using Xunit;
using System.Net.WebSockets;
using System.Text;

namespace System.Runtime.InteropServices.JavaScript.Tests
{
public class WebWorkerWebSocketTest : WebWorkerTestBase
{
#region WebSocket

[Theory, MemberData(nameof(GetTargetThreads))]
public async Task WebSocketClient_ContentInSameThread(Executor executor)
{
using var cts = CreateTestCaseTimeoutSource();

var uri = new Uri(WebWorkerTestHelper.LocalWsEcho + "?guid=" + Guid.NewGuid());
var message = "hello";
var send = Encoding.UTF8.GetBytes(message);
var receive = new byte[100];

await executor.Execute(async () =>
{
using var client = new ClientWebSocket();
await client.ConnectAsync(uri, CancellationToken.None);
await client.SendAsync(send, WebSocketMessageType.Text, true, CancellationToken.None);

var res = await client.ReceiveAsync(receive, CancellationToken.None);
Assert.Equal(WebSocketMessageType.Text, res.MessageType);
Assert.True(res.EndOfMessage);
Assert.Equal(send.Length, res.Count);
Assert.Equal(message, Encoding.UTF8.GetString(receive, 0, res.Count));
}, cts.Token);
}


[Theory, MemberData(nameof(GetTargetThreads2x))]
public async Task WebSocketClient_ResponseCloseInDifferentThread(Executor executor1, Executor executor2)
{
using var cts = CreateTestCaseTimeoutSource();

var uri = new Uri(WebWorkerTestHelper.LocalWsEcho + "?guid=" + Guid.NewGuid());
var message = "hello";
var send = Encoding.UTF8.GetBytes(message);
var receive = new byte[100];

var e1Job = async (Task e2done, TaskCompletionSource<ClientWebSocket> e1State) =>
{
using var client = new ClientWebSocket();
await client.ConnectAsync(uri, CancellationToken.None);
await client.SendAsync(send, WebSocketMessageType.Text, true, CancellationToken.None);

// share the state with the E2 continuation
e1State.SetResult(client);
await e2done;
};

var e2Job = async (ClientWebSocket client) =>
{
var res = await client.ReceiveAsync(receive, CancellationToken.None);
Assert.Equal(WebSocketMessageType.Text, res.MessageType);
Assert.True(res.EndOfMessage);
Assert.Equal(send.Length, res.Count);
Assert.Equal(message, Encoding.UTF8.GetString(receive, 0, res.Count));

await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "bye", CancellationToken.None);
};

await ActionsInDifferentThreads<ClientWebSocket>(executor1, executor2, e1Job, e2Job, cts);
}

[Theory, MemberData(nameof(GetTargetThreads2x))]
public async Task WebSocketClient_CancelInDifferentThread(Executor executor1, Executor executor2)
{
using var cts = CreateTestCaseTimeoutSource();

var uri = new Uri(WebWorkerTestHelper.LocalWsEcho + "?guid=" + Guid.NewGuid());
var message = ".delay5sec"; // this will make the loopback server slower
var send = Encoding.UTF8.GetBytes(message);
var receive = new byte[100];

var e1Job = async (Task e2done, TaskCompletionSource<ClientWebSocket> e1State) =>
{
using var client = new ClientWebSocket();
await client.ConnectAsync(uri, CancellationToken.None);
await client.SendAsync(send, WebSocketMessageType.Text, true, CancellationToken.None);

// share the state with the E2 continuation
e1State.SetResult(client);
await e2done;
};

var e2Job = async (ClientWebSocket client) =>
{
CancellationTokenSource cts2 = new CancellationTokenSource();
var resTask = client.ReceiveAsync(receive, cts2.Token);
cts2.Cancel();
var ex = await Assert.ThrowsAnyAsync<OperationCanceledException>(() => resTask);
Assert.Equal(cts2.Token, ex.CancellationToken);
};

await ActionsInDifferentThreads<ClientWebSocket>(executor1, executor2, e1Job, e2Job, cts);
}

#endregion
}
}
Loading

0 comments on commit fb953cc

Please sign in to comment.