Skip to content
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

[browser][MT] Use auto thread dispatch in HTTP #95370

Merged
merged 25 commits into from
Jan 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
737189d
rebase
pavelsavara Jan 16, 2024
85d6a0f
75123
pavelsavara Jan 17, 2024
08da5aa
more
pavelsavara Jan 17, 2024
c1bef65
Merge branch 'main' into browser_jsimport_sc_dispatch
pavelsavara Jan 17, 2024
f07acd5
more
pavelsavara Jan 17, 2024
ff63f69
add HTTP to MT smoke test
pavelsavara Jan 17, 2024
6697711
Merge branch 'main' into browser_jsimport_sc_dispatch
pavelsavara Jan 18, 2024
f3363db
wip
pavelsavara Jan 19, 2024
288b1c4
Merge branch 'main' into browser_jsimport_sc_dispatch
pavelsavara Jan 19, 2024
6de377c
feedback
pavelsavara Jan 19, 2024
91c748a
feedback
pavelsavara Jan 19, 2024
69ddae1
Update src/mono/browser/runtime/http.ts
pavelsavara Jan 19, 2024
4e67c24
feedback
pavelsavara Jan 19, 2024
a5f6aed
test cleanup
pavelsavara Jan 19, 2024
699a0d6
Merge branch 'main' into browser_jsimport_sc_dispatch
pavelsavara Jan 19, 2024
bef203e
fix
pavelsavara Jan 19, 2024
e6c8298
Merge branch 'main' into browser_jsimport_sc_dispatch
pavelsavara Jan 19, 2024
3bbefbd
fix
pavelsavara Jan 19, 2024
758243b
postpone RunContinuationsAsynchronously
pavelsavara Jan 19, 2024
a4136a4
Merge branch 'main' into browser_jsimport_sc_dispatch
pavelsavara Jan 19, 2024
7a95729
Merge branch 'main' into browser_jsimport_sc_dispatch
pavelsavara Jan 20, 2024
b924f1c
fix
pavelsavara Jan 20, 2024
3e989ca
ActiveIssue https://github.com/dotnet/runtime/issues/96628
pavelsavara Jan 20, 2024
440ee61
Merge branch 'main' into browser_jsimport_sc_dispatch
pavelsavara Jan 20, 2024
1056ab5
use TaskCompletionSource.TrySetFromTask
pavelsavara Jan 20, 2024
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
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);
pavelsavara marked this conversation as resolved.
Show resolved Hide resolved
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
Loading