Skip to content

[browser] further improve HTTP cancelation #100029

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 21, 2024
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 @@ -7,6 +7,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;

namespace NetCoreServer
{
Expand All @@ -30,6 +31,24 @@ public static async Task InvokeAsync(HttpContext context)

byte[] bytes = Encoding.UTF8.GetBytes(echoJson);

var delay = 0;
if (context.Request.QueryString.HasValue)
{
if (context.Request.QueryString.Value.Contains("delay1sec"))
{
delay = 1000;
}
else if (context.Request.QueryString.Value.Contains("delay10sec"))
{
delay = 10000;
}
}

if (delay > 0)
{
context.Features.Get<IHttpResponseBodyFeature>().DisableBuffering();
}

// Compute MD5 hash so that clients can verify the received data.
using (MD5 md5 = MD5.Create())
{
Expand All @@ -41,20 +60,19 @@ public static async Task InvokeAsync(HttpContext context)
context.Response.ContentLength = bytes.Length;
}

if (context.Request.QueryString.HasValue && context.Request.QueryString.Value.Contains("delay10sec"))
if (delay > 0)
{
await context.Response.StartAsync(CancellationToken.None);
await context.Response.Body.WriteAsync(bytes, 0, 10);
await context.Response.Body.FlushAsync();
await Task.Delay(delay);
await context.Response.Body.WriteAsync(bytes, 10, bytes.Length-10);
await context.Response.Body.FlushAsync();

await Task.Delay(10000);
}
else if (context.Request.QueryString.HasValue && context.Request.QueryString.Value.Contains("delay1sec"))
else
{
await context.Response.StartAsync(CancellationToken.None);
await Task.Delay(1000);
await context.Response.Body.WriteAsync(bytes, 0, bytes.Length);
}

await context.Response.Body.WriteAsync(bytes, 0, bytes.Length);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,13 @@ internal static unsafe partial class JavaScriptImports
[JSImport("INTERNAL.get_dotnet_instance")]
public static partial JSObject GetDotnetInstance();
[JSImport("INTERNAL.dynamic_import")]
// TODO: the continuation should be running on deputy or TP in MT
public static partial Task<JSObject> DynamicImport(string moduleName, string moduleUrl);

[JSImport("INTERNAL.mono_wasm_bind_cs_function")]
public static partial void BindCSFunction(IntPtr monoMethod, string assemblyName, string namespaceName, string shortClassName, string methodName, int signatureHash, IntPtr signature);

#if FEATURE_WASM_MANAGED_THREADS
[JSImport("INTERNAL.thread_available")]
// TODO: the continuation should be running on deputy or TP in MT
public static partial Task ThreadAvailable();
#endif

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,23 +81,11 @@ await HttpClient_ActionInDifferentThread(url, executor1, executor2, async (HttpR
await Assert.ThrowsAsync<TaskCanceledException>(async () =>
{
CancellationTokenSource cts = new CancellationTokenSource();
try
{
var promise = response.Content.ReadAsStringAsync(cts.Token);
Console.WriteLine("HttpClient_CancelInDifferentThread: ManagedThreadId: " + Environment.CurrentManagedThreadId + " NativeThreadId: " + WebWorkerTestHelper.NativeThreadId);
cts.Cancel();
await promise;
}
catch (TaskCanceledException ex)
{
Console.WriteLine("HttpClient_CancelInDifferentThread: TaskCanceledException is thrown with message: " + ex.ToString());
throw;
}
catch (OperationCanceledException ex)
{
Console.WriteLine("HttpClient_CancelInDifferentThread: OperationCanceledException is thrown with message: " + ex.ToString());
throw;
}
var promise = response.Content.ReadAsStringAsync(cts.Token);
Console.WriteLine("HttpClient_CancelInDifferentThread: ManagedThreadId: " + Environment.CurrentManagedThreadId + " NativeThreadId: " + WebWorkerTestHelper.NativeThreadId);
cts.Cancel();
var res = await promise;
throw new Exception("This should be unreachable: " + res);
});
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,12 +135,15 @@ async Task ActionsInDifferentThreads2()
{
throw;
}
Console.WriteLine("ActionsInDifferentThreads failed with: \n" + ex);
if (!e1Done || !e2Done)
{
Console.WriteLine("ActionsInDifferentThreads canceling!");
Console.WriteLine("ActionsInDifferentThreads canceling because of unexpected fail: \n" + ex);
cts.Cancel();
}
else
{
Console.WriteLine("ActionsInDifferentThreads failed with: \n" + ex);
}
throw;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public partial class WebWorkerTestHelper
public static NamedCall CurrentCallback;
public static CancellationToken CurrentCancellationToken = CancellationToken.None;
public static Exception? LastException = null;

[JSExport]
public static void CallCurrentCallback()
{
Expand Down Expand Up @@ -333,7 +333,7 @@ public static Task RunOnNewThread(Func<Task> job, CancellationToken cancellation
}
catch (Exception ex)
{
if(ex is AggregateException agg)
if (ex is AggregateException agg)
{
tcs.TrySetException(agg.InnerException);
}
Expand Down
2 changes: 1 addition & 1 deletion src/mono/browser/runtime/cancelable-promise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export class PromiseHolder extends ManagedObject {
public isPosted = false;
public isPostponed = false;
public data: any = null;
public reason: any = null;
public reason: any = undefined;
public constructor(public promise: Promise<any>,
private gc_handle: GCHandle,
private promiseHolderPtr: number, // could be null for GCV_handle
Expand Down
5 changes: 5 additions & 0 deletions src/mono/browser/runtime/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export function http_wasm_abort_request(controller: HttpController): void {
export function http_wasm_abort_response(controller: HttpController): void {
if (BuildConfiguration === "Debug") commonAsserts(controller);
try {
controller.isAborted = true;
if (controller.streamReader) {
controller.streamReader.cancel().catch((err) => {
if (err && err.name !== "AbortError") {
Expand Down Expand Up @@ -251,6 +252,9 @@ export function http_wasm_get_streamed_response_bytes(controller: HttpController
controller.currentBufferOffset = 0;
}
if (controller.currentStreamReaderChunk.done) {
if (controller.isAborted) {
throw new Error("OperationCanceledException");
}
return 0;
}

Expand All @@ -271,6 +275,7 @@ export function http_wasm_get_streamed_response_bytes(controller: HttpController

interface HttpController {
abortController: AbortController
isAborted?: boolean

// streaming request
streamReader?: ReadableStreamDefaultReader<Uint8Array>
Expand Down
1 change: 0 additions & 1 deletion src/mono/mono/utils/mono-threads-wasm.c
Original file line number Diff line number Diff line change
Expand Up @@ -629,7 +629,6 @@ mono_wasm_register_ui_thread (void)
MONO_ENTER_GC_SAFE_UNBALANCED;
}

// TODO ideally we should not need to have UI thread registered as managed
EMSCRIPTEN_KEEPALIVE void
mono_wasm_register_io_thread (void)
{
Expand Down
Loading