Skip to content

Commit

Permalink
[browser][MT] Use auto thread dispatch in HTTP (#95370)
Browse files Browse the repository at this point in the history
Co-authored-by: campersau <[email protected]>
Co-authored-by: Marek Fišera <[email protected]>
  • Loading branch information
3 people authored Jan 21, 2024
1 parent ff1eeff commit 756a138
Show file tree
Hide file tree
Showing 20 changed files with 592 additions and 738 deletions.
2 changes: 1 addition & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,4 @@ src/tests/JIT/Performance/CodeQuality/BenchmarksGame/reverse-complement/revcomp-
src/tests/JIT/Performance/CodeQuality/BenchmarksGame/reverse-complement/revcomp-input25000.txt text eol=lf
src/tests/JIT/Performance/CodeQuality/BenchmarksGame/k-nucleotide/knucleotide-input.txt text eol=lf
src/tests/JIT/Performance/CodeQuality/BenchmarksGame/k-nucleotide/knucleotide-input-big.txt text eol=lf
src/mono/wasm/runtime/dotnet.d.ts text eol=lf
src/mono/browser/runtime/dotnet.d.ts text eol=lf
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;

Expand All @@ -27,18 +28,33 @@ public static async Task InvokeAsync(HttpContext context)
RequestInformation info = await RequestInformation.CreateAsync(context.Request);
string echoJson = info.SerializeToJson();

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

// Compute MD5 hash so that clients can verify the received data.
using (MD5 md5 = MD5.Create())
{
byte[] bytes = Encoding.UTF8.GetBytes(echoJson);
byte[] hash = md5.ComputeHash(bytes);
string encodedHash = Convert.ToBase64String(hash);

context.Response.Headers["Content-MD5"] = encodedHash;
context.Response.ContentType = "application/json";
context.Response.ContentLength = bytes.Length;
await context.Response.Body.WriteAsync(bytes, 0, bytes.Length);
}

if (context.Request.QueryString.HasValue && context.Request.QueryString.Value.Contains("delay10sec"))
{
await context.Response.StartAsync(CancellationToken.None);
await context.Response.Body.FlushAsync();

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

await context.Response.Body.WriteAsync(bytes, 0, bytes.Length);
}
}
}

Large diffs are not rendered by default.

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

using System.IO;
using System.Buffers;
using System.Net.Http.Headers;
using System.Runtime.InteropServices.JavaScript;
using System.Threading;
Expand All @@ -17,47 +17,53 @@ internal static partial class BrowserHttpInterop
[JSImport("INTERNAL.http_wasm_supports_streaming_response")]
public static partial bool SupportsStreamingResponse();

[JSImport("INTERNAL.http_wasm_create_abort_controler")]
public static partial JSObject CreateAbortController();
[JSImport("INTERNAL.http_wasm_create_controller")]
public static partial JSObject CreateController();

[JSImport("INTERNAL.http_wasm_abort_request")]
public static partial void AbortRequest(
JSObject abortController);
JSObject httpController);

[JSImport("INTERNAL.http_wasm_abort_response")]
public static partial void AbortResponse(
JSObject fetchResponse);

[JSImport("INTERNAL.http_wasm_create_transform_stream")]
public static partial JSObject CreateTransformStream();
JSObject httpController);

[JSImport("INTERNAL.http_wasm_transform_stream_write")]
public static partial Task TransformStreamWrite(
JSObject transformStream,
JSObject httpController,
IntPtr bufferPtr,
int bufferLength);

public static unsafe Task TransformStreamWriteUnsafe(JSObject httpController, ReadOnlyMemory<byte> buffer, Buffers.MemoryHandle handle)
=> TransformStreamWrite(httpController, (nint)handle.Pointer, buffer.Length);

[JSImport("INTERNAL.http_wasm_transform_stream_close")]
public static partial Task TransformStreamClose(
JSObject transformStream);

[JSImport("INTERNAL.http_wasm_transform_stream_abort")]
public static partial void TransformStreamAbort(
JSObject transformStream);
JSObject httpController);

[JSImport("INTERNAL.http_wasm_get_response_header_names")]
private static partial string[] _GetResponseHeaderNames(
JSObject fetchResponse);
JSObject httpController);

[JSImport("INTERNAL.http_wasm_get_response_header_values")]
private static partial string[] _GetResponseHeaderValues(
JSObject fetchResponse);
JSObject httpController);

[JSImport("INTERNAL.http_wasm_get_response_status")]
public static partial int GetResponseStatus(
JSObject httpController);

[JSImport("INTERNAL.http_wasm_get_response_type")]
public static partial string GetResponseType(
JSObject httpController);

public static void GetResponseHeaders(JSObject fetchResponse, HttpHeaders resposeHeaders, HttpHeaders contentHeaders)
public static void GetResponseHeaders(JSObject httpController, HttpHeaders resposeHeaders, HttpHeaders contentHeaders)
{
string[] headerNames = _GetResponseHeaderNames(fetchResponse);
string[] headerValues = _GetResponseHeaderValues(fetchResponse);
string[] headerNames = _GetResponseHeaderNames(httpController);
string[] headerValues = _GetResponseHeaderValues(httpController);

// Some of the headers may not even be valid header types in .NET thus we use TryAddWithoutValidation
// CORS will only allow access to certain headers on browser.
for (int i = 0; i < headerNames.Length; i++)
{
if (!resposeHeaders.TryAddWithoutValidation(headerNames[i], headerValues[i]))
Expand All @@ -67,43 +73,38 @@ public static void GetResponseHeaders(JSObject fetchResponse, HttpHeaders respos
}
}


[JSImport("INTERNAL.http_wasm_fetch")]
public static partial Task<JSObject> Fetch(
public static partial Task Fetch(
JSObject httpController,
string uri,
string[] headerNames,
string[] headerValues,
string[] optionNames,
[JSMarshalAs<JSType.Array<JSType.Any>>] object?[] optionValues,
JSObject abortControler);
[JSMarshalAs<JSType.Array<JSType.Any>>] object?[] optionValues);

[JSImport("INTERNAL.http_wasm_fetch_stream")]
public static partial Task<JSObject> Fetch(
public static partial Task FetchStream(
JSObject httpController,
string uri,
string[] headerNames,
string[] headerValues,
string[] optionNames,
[JSMarshalAs<JSType.Array<JSType.Any>>] object?[] optionValues,
JSObject abortControler,
JSObject transformStream);
[JSMarshalAs<JSType.Array<JSType.Any>>] object?[] optionValues);

[JSImport("INTERNAL.http_wasm_fetch_bytes")]
private static partial Task<JSObject> FetchBytes(
private static partial Task FetchBytes(
JSObject httpController,
string uri,
string[] headerNames,
string[] headerValues,
string[] optionNames,
[JSMarshalAs<JSType.Array<JSType.Any>>] object?[] optionValues,
JSObject abortControler,
IntPtr bodyPtr,
int bodyLength);

public static unsafe Task<JSObject> Fetch(string uri, string[] headerNames, string[] headerValues, string[] optionNames, object?[] optionValues, JSObject abortControler, byte[] body)
public static unsafe Task FetchBytes(JSObject httpController, string uri, string[] headerNames, string[] headerValues, string[] optionNames, object?[] optionValues, MemoryHandle pinBuffer, int bodyLength)
{
fixed (byte* ptr = body)
{
return FetchBytes(uri, headerNames, headerValues, optionNames, optionValues, abortControler, (IntPtr)ptr, body.Length);
}
return FetchBytes(httpController, uri, headerNames, headerValues, optionNames, optionValues, (IntPtr)pinBuffer.Pointer, bodyLength);
}

[JSImport("INTERNAL.http_wasm_get_streamed_response_bytes")]
Expand All @@ -112,6 +113,10 @@ public static partial Task<int> GetStreamedResponseBytes(
IntPtr bufferPtr,
int bufferLength);

public static unsafe Task<int> GetStreamedResponseBytesUnsafe(JSObject jsController, Memory<byte> buffer, MemoryHandle handle)
=> GetStreamedResponseBytes(jsController, (IntPtr)handle.Pointer, buffer.Length);


[JSImport("INTERNAL.http_wasm_get_response_length")]
public static partial Task<int> GetResponseLength(
JSObject fetchResponse);
Expand All @@ -122,8 +127,10 @@ public static partial int GetResponseBytes(
[JSMarshalAs<JSType.MemoryView>] Span<byte> buffer);


public static async ValueTask CancelationHelper(Task promise, CancellationToken cancellationToken, JSObject? fetchResponse = null)
public static async Task CancellationHelper(Task promise, CancellationToken cancellationToken, JSObject jsController)
{
Http.CancellationHelper.ThrowIfCancellationRequested(cancellationToken);

if (promise.IsCompletedSuccessfully)
{
return;
Expand All @@ -132,46 +139,43 @@ public static async ValueTask CancelationHelper(Task promise, CancellationToken
{
using (var operationRegistration = cancellationToken.Register(static s =>
{
(Task _promise, JSObject? _fetchResponse) = ((Task, JSObject?))s!;
CancelablePromise.CancelPromise(_promise, static (JSObject? __fetchResponse) =>
(Task _promise, JSObject _jsController) = ((Task, JSObject))s!;
CancelablePromise.CancelPromise(_promise, static (JSObject __jsController) =>
{
if (__fetchResponse != null)
if (!__jsController.IsDisposed)
{
AbortResponse(__fetchResponse);
AbortResponse(__jsController);
}
}, _fetchResponse);
}, (promise, fetchResponse)))
}, _jsController);
}, (promise, jsController)))
{
await promise.ConfigureAwait(true);
}
}
catch (OperationCanceledException oce) when (cancellationToken.IsCancellationRequested)
{
throw CancellationHelper.CreateOperationCanceledException(oce, cancellationToken);
Http.CancellationHelper.ThrowIfCancellationRequested(oce, cancellationToken);
}
catch (JSException jse)
{
if (jse.Message.StartsWith("AbortError", StringComparison.Ordinal))
{
throw CancellationHelper.CreateOperationCanceledException(jse, CancellationToken.None);
}
if (cancellationToken.IsCancellationRequested)
{
throw CancellationHelper.CreateOperationCanceledException(jse, cancellationToken);
throw Http.CancellationHelper.CreateOperationCanceledException(jse, CancellationToken.None);
}
Http.CancellationHelper.ThrowIfCancellationRequested(jse, cancellationToken);
throw new HttpRequestException(jse.Message, jse);
}
}

public static async ValueTask<T> CancelationHelper<T>(Task<T> promise, CancellationToken cancellationToken, JSObject? fetchResponse = null)
public static async Task<T> CancellationHelper<T>(Task<T> promise, CancellationToken cancellationToken, JSObject jsController)
{
Http.CancellationHelper.ThrowIfCancellationRequested(cancellationToken);
if (promise.IsCompletedSuccessfully)
{
return promise.Result;
}
await CancelationHelper((Task)promise, cancellationToken, fetchResponse).ConfigureAwait(true);
return await promise.ConfigureAwait(true);
await CancellationHelper((Task)promise, cancellationToken, jsController).ConfigureAwait(false);
return promise.Result;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,18 @@ private static void ThrowOperationCanceledException(Exception? innerException, C
/// <summary>Throws a cancellation exception if cancellation has been requested via <paramref name="cancellationToken"/>.</summary>
/// <param name="cancellationToken">The token to check for a cancellation request.</param>
internal static void ThrowIfCancellationRequested(CancellationToken cancellationToken)
{
ThrowIfCancellationRequested(innerException: null, cancellationToken);
}

/// <summary>Throws a cancellation exception if cancellation has been requested via <paramref name="cancellationToken"/>.</summary>
/// <param name="innerException">The inner exception to wrap. May be null.</param>
/// <param name="cancellationToken">The token to check for a cancellation request.</param>
internal static void ThrowIfCancellationRequested(Exception? innerException, CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
ThrowOperationCanceledException(innerException: null, cancellationToken);
ThrowOperationCanceledException(innerException, cancellationToken);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@
<TargetPlatformIdentifier>$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))</TargetPlatformIdentifier>
<DefineConstants Condition="'$(TargetPlatformIdentifier)' == 'windows'">$(DefineConstants);TargetsWindows</DefineConstants>
<DefineConstants Condition="'$(TargetPlatformIdentifier)' == 'browser'">$(DefineConstants);TARGETS_BROWSER</DefineConstants>
<!-- Active issue https://github.com/dotnet/runtime/issues/96173 -->
<_XUnitBackgroundExec>false</_XUnitBackgroundExec>
</PropertyGroup>

<PropertyGroup Condition="'$(TargetOS)' == 'browser'">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ public void v3()

//-----------------------------------------------------------------------------------
[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/75123", typeof(PlatformDetection), nameof(PlatformDetection.IsWasmThreadingSupported))]
//[Variation(Desc = "v4 - ns = valid, URL = invalid")]
public void v4()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:System.Runtime.InteropServices.JavaScript.SynchronizationContextExtension</Target>
<Left>ref/net9.0/System.Runtime.InteropServices.JavaScript.dll</Left>
<Right>runtimes/browser/lib/net9.0/System.Runtime.InteropServices.JavaScript.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:System.Runtime.InteropServices.JavaScript.CancelablePromise</Target>
Expand All @@ -18,10 +12,4 @@
<Left>ref/net9.0/System.Runtime.InteropServices.JavaScript.dll</Left>
<Right>runtimes/browser/lib/net9.0/System.Runtime.InteropServices.JavaScript.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:System.Runtime.InteropServices.JavaScript.JSHost.get_CurrentOrMainJSSynchronizationContext</Target>
<Left>ref/net9.0/System.Runtime.InteropServices.JavaScript.dll</Left>
<Right>runtimes/browser/lib/net9.0/System.Runtime.InteropServices.JavaScript.dll</Right>
</Suppression>
</Suppressions>
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
<Compile Include="System\Runtime\InteropServices\JavaScript\JSExportAttribute.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\JSImportAttribute.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\CancelablePromise.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\SynchronizationContextExtensions.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\JSProxyContext.cs" />

<Compile Include="System\Runtime\InteropServices\JavaScript\MarshalerType.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,17 +50,5 @@ public static Task<JSObject> ImportAsync(string moduleName, string moduleUrl, Ca
return JSHostImplementation.ImportAsync(moduleName, moduleUrl, cancellationToken);
}

public static SynchronizationContext CurrentOrMainJSSynchronizationContext
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
#if FEATURE_WASM_THREADS
return (JSProxyContext.ExecutionContext ?? JSProxyContext.MainThreadContext).SynchronizationContext;
#else
return null!;
#endif
}
}
}
}
Loading

0 comments on commit 756a138

Please sign in to comment.