Skip to content

Commit 756a138

Browse files
pavelsavaracampersaumaraf
authored
[browser][MT] Use auto thread dispatch in HTTP (#95370)
Co-authored-by: campersau <[email protected]> Co-authored-by: Marek Fišera <[email protected]>
1 parent ff1eeff commit 756a138

File tree

20 files changed

+592
-738
lines changed

20 files changed

+592
-738
lines changed

.gitattributes

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,4 @@ src/tests/JIT/Performance/CodeQuality/BenchmarksGame/reverse-complement/revcomp-
7777
src/tests/JIT/Performance/CodeQuality/BenchmarksGame/reverse-complement/revcomp-input25000.txt text eol=lf
7878
src/tests/JIT/Performance/CodeQuality/BenchmarksGame/k-nucleotide/knucleotide-input.txt text eol=lf
7979
src/tests/JIT/Performance/CodeQuality/BenchmarksGame/k-nucleotide/knucleotide-input-big.txt text eol=lf
80-
src/mono/wasm/runtime/dotnet.d.ts text eol=lf
80+
src/mono/browser/runtime/dotnet.d.ts text eol=lf

src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/EchoHandler.cs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Security.Cryptography;
66
using System.Text;
7+
using System.Threading;
78
using System.Threading.Tasks;
89
using Microsoft.AspNetCore.Http;
910

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

31+
byte[] bytes = Encoding.UTF8.GetBytes(echoJson);
32+
3033
// Compute MD5 hash so that clients can verify the received data.
3134
using (MD5 md5 = MD5.Create())
3235
{
33-
byte[] bytes = Encoding.UTF8.GetBytes(echoJson);
3436
byte[] hash = md5.ComputeHash(bytes);
3537
string encodedHash = Convert.ToBase64String(hash);
3638

3739
context.Response.Headers["Content-MD5"] = encodedHash;
3840
context.Response.ContentType = "application/json";
3941
context.Response.ContentLength = bytes.Length;
40-
await context.Response.Body.WriteAsync(bytes, 0, bytes.Length);
4142
}
43+
44+
if (context.Request.QueryString.HasValue && context.Request.QueryString.Value.Contains("delay10sec"))
45+
{
46+
await context.Response.StartAsync(CancellationToken.None);
47+
await context.Response.Body.FlushAsync();
48+
49+
await Task.Delay(10000);
50+
}
51+
else if (context.Request.QueryString.HasValue && context.Request.QueryString.Value.Contains("delay1sec"))
52+
{
53+
await context.Response.StartAsync(CancellationToken.None);
54+
await Task.Delay(1000);
55+
}
56+
57+
await context.Response.Body.WriteAsync(bytes, 0, bytes.Length);
4258
}
4359
}
4460
}

src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/BrowserHttpHandler.cs

Lines changed: 200 additions & 322 deletions
Large diffs are not rendered by default.
Lines changed: 55 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

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

20-
[JSImport("INTERNAL.http_wasm_create_abort_controler")]
21-
public static partial JSObject CreateAbortController();
20+
[JSImport("INTERNAL.http_wasm_create_controller")]
21+
public static partial JSObject CreateController();
2222

2323
[JSImport("INTERNAL.http_wasm_abort_request")]
2424
public static partial void AbortRequest(
25-
JSObject abortController);
25+
JSObject httpController);
2626

2727
[JSImport("INTERNAL.http_wasm_abort_response")]
2828
public static partial void AbortResponse(
29-
JSObject fetchResponse);
30-
31-
[JSImport("INTERNAL.http_wasm_create_transform_stream")]
32-
public static partial JSObject CreateTransformStream();
29+
JSObject httpController);
3330

3431
[JSImport("INTERNAL.http_wasm_transform_stream_write")]
3532
public static partial Task TransformStreamWrite(
36-
JSObject transformStream,
33+
JSObject httpController,
3734
IntPtr bufferPtr,
3835
int bufferLength);
3936

37+
public static unsafe Task TransformStreamWriteUnsafe(JSObject httpController, ReadOnlyMemory<byte> buffer, Buffers.MemoryHandle handle)
38+
=> TransformStreamWrite(httpController, (nint)handle.Pointer, buffer.Length);
39+
4040
[JSImport("INTERNAL.http_wasm_transform_stream_close")]
4141
public static partial Task TransformStreamClose(
42-
JSObject transformStream);
43-
44-
[JSImport("INTERNAL.http_wasm_transform_stream_abort")]
45-
public static partial void TransformStreamAbort(
46-
JSObject transformStream);
42+
JSObject httpController);
4743

4844
[JSImport("INTERNAL.http_wasm_get_response_header_names")]
4945
private static partial string[] _GetResponseHeaderNames(
50-
JSObject fetchResponse);
46+
JSObject httpController);
5147

5248
[JSImport("INTERNAL.http_wasm_get_response_header_values")]
5349
private static partial string[] _GetResponseHeaderValues(
54-
JSObject fetchResponse);
50+
JSObject httpController);
51+
52+
[JSImport("INTERNAL.http_wasm_get_response_status")]
53+
public static partial int GetResponseStatus(
54+
JSObject httpController);
55+
56+
[JSImport("INTERNAL.http_wasm_get_response_type")]
57+
public static partial string GetResponseType(
58+
JSObject httpController);
5559

56-
public static void GetResponseHeaders(JSObject fetchResponse, HttpHeaders resposeHeaders, HttpHeaders contentHeaders)
60+
public static void GetResponseHeaders(JSObject httpController, HttpHeaders resposeHeaders, HttpHeaders contentHeaders)
5761
{
58-
string[] headerNames = _GetResponseHeaderNames(fetchResponse);
59-
string[] headerValues = _GetResponseHeaderValues(fetchResponse);
62+
string[] headerNames = _GetResponseHeaderNames(httpController);
63+
string[] headerValues = _GetResponseHeaderValues(httpController);
6064

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

70-
7176
[JSImport("INTERNAL.http_wasm_fetch")]
72-
public static partial Task<JSObject> Fetch(
77+
public static partial Task Fetch(
78+
JSObject httpController,
7379
string uri,
7480
string[] headerNames,
7581
string[] headerValues,
7682
string[] optionNames,
77-
[JSMarshalAs<JSType.Array<JSType.Any>>] object?[] optionValues,
78-
JSObject abortControler);
83+
[JSMarshalAs<JSType.Array<JSType.Any>>] object?[] optionValues);
7984

8085
[JSImport("INTERNAL.http_wasm_fetch_stream")]
81-
public static partial Task<JSObject> Fetch(
86+
public static partial Task FetchStream(
87+
JSObject httpController,
8288
string uri,
8389
string[] headerNames,
8490
string[] headerValues,
8591
string[] optionNames,
86-
[JSMarshalAs<JSType.Array<JSType.Any>>] object?[] optionValues,
87-
JSObject abortControler,
88-
JSObject transformStream);
92+
[JSMarshalAs<JSType.Array<JSType.Any>>] object?[] optionValues);
8993

9094
[JSImport("INTERNAL.http_wasm_fetch_bytes")]
91-
private static partial Task<JSObject> FetchBytes(
95+
private static partial Task FetchBytes(
96+
JSObject httpController,
9297
string uri,
9398
string[] headerNames,
9499
string[] headerValues,
95100
string[] optionNames,
96101
[JSMarshalAs<JSType.Array<JSType.Any>>] object?[] optionValues,
97-
JSObject abortControler,
98102
IntPtr bodyPtr,
99103
int bodyLength);
100104

101-
public static unsafe Task<JSObject> Fetch(string uri, string[] headerNames, string[] headerValues, string[] optionNames, object?[] optionValues, JSObject abortControler, byte[] body)
105+
public static unsafe Task FetchBytes(JSObject httpController, string uri, string[] headerNames, string[] headerValues, string[] optionNames, object?[] optionValues, MemoryHandle pinBuffer, int bodyLength)
102106
{
103-
fixed (byte* ptr = body)
104-
{
105-
return FetchBytes(uri, headerNames, headerValues, optionNames, optionValues, abortControler, (IntPtr)ptr, body.Length);
106-
}
107+
return FetchBytes(httpController, uri, headerNames, headerValues, optionNames, optionValues, (IntPtr)pinBuffer.Pointer, bodyLength);
107108
}
108109

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

116+
public static unsafe Task<int> GetStreamedResponseBytesUnsafe(JSObject jsController, Memory<byte> buffer, MemoryHandle handle)
117+
=> GetStreamedResponseBytes(jsController, (IntPtr)handle.Pointer, buffer.Length);
118+
119+
115120
[JSImport("INTERNAL.http_wasm_get_response_length")]
116121
public static partial Task<int> GetResponseLength(
117122
JSObject fetchResponse);
@@ -122,8 +127,10 @@ public static partial int GetResponseBytes(
122127
[JSMarshalAs<JSType.MemoryView>] Span<byte> buffer);
123128

124129

125-
public static async ValueTask CancelationHelper(Task promise, CancellationToken cancellationToken, JSObject? fetchResponse = null)
130+
public static async Task CancellationHelper(Task promise, CancellationToken cancellationToken, JSObject jsController)
126131
{
132+
Http.CancellationHelper.ThrowIfCancellationRequested(cancellationToken);
133+
127134
if (promise.IsCompletedSuccessfully)
128135
{
129136
return;
@@ -132,46 +139,43 @@ public static async ValueTask CancelationHelper(Task promise, CancellationToken
132139
{
133140
using (var operationRegistration = cancellationToken.Register(static s =>
134141
{
135-
(Task _promise, JSObject? _fetchResponse) = ((Task, JSObject?))s!;
136-
CancelablePromise.CancelPromise(_promise, static (JSObject? __fetchResponse) =>
142+
(Task _promise, JSObject _jsController) = ((Task, JSObject))s!;
143+
CancelablePromise.CancelPromise(_promise, static (JSObject __jsController) =>
137144
{
138-
if (__fetchResponse != null)
145+
if (!__jsController.IsDisposed)
139146
{
140-
AbortResponse(__fetchResponse);
147+
AbortResponse(__jsController);
141148
}
142-
}, _fetchResponse);
143-
}, (promise, fetchResponse)))
149+
}, _jsController);
150+
}, (promise, jsController)))
144151
{
145152
await promise.ConfigureAwait(true);
146153
}
147154
}
148155
catch (OperationCanceledException oce) when (cancellationToken.IsCancellationRequested)
149156
{
150-
throw CancellationHelper.CreateOperationCanceledException(oce, cancellationToken);
157+
Http.CancellationHelper.ThrowIfCancellationRequested(oce, cancellationToken);
151158
}
152159
catch (JSException jse)
153160
{
154161
if (jse.Message.StartsWith("AbortError", StringComparison.Ordinal))
155162
{
156-
throw CancellationHelper.CreateOperationCanceledException(jse, CancellationToken.None);
157-
}
158-
if (cancellationToken.IsCancellationRequested)
159-
{
160-
throw CancellationHelper.CreateOperationCanceledException(jse, cancellationToken);
163+
throw Http.CancellationHelper.CreateOperationCanceledException(jse, CancellationToken.None);
161164
}
165+
Http.CancellationHelper.ThrowIfCancellationRequested(jse, cancellationToken);
162166
throw new HttpRequestException(jse.Message, jse);
163167
}
164168
}
165169

166-
public static async ValueTask<T> CancelationHelper<T>(Task<T> promise, CancellationToken cancellationToken, JSObject? fetchResponse = null)
170+
public static async Task<T> CancellationHelper<T>(Task<T> promise, CancellationToken cancellationToken, JSObject jsController)
167171
{
172+
Http.CancellationHelper.ThrowIfCancellationRequested(cancellationToken);
168173
if (promise.IsCompletedSuccessfully)
169174
{
170175
return promise.Result;
171176
}
172-
await CancelationHelper((Task)promise, cancellationToken, fetchResponse).ConfigureAwait(true);
173-
return await promise.ConfigureAwait(true);
177+
await CancellationHelper((Task)promise, cancellationToken, jsController).ConfigureAwait(false);
178+
return promise.Result;
174179
}
175180
}
176-
177181
}

src/libraries/System.Net.Http/src/System/Net/Http/CancellationHelper.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,18 @@ private static void ThrowOperationCanceledException(Exception? innerException, C
3535
/// <summary>Throws a cancellation exception if cancellation has been requested via <paramref name="cancellationToken"/>.</summary>
3636
/// <param name="cancellationToken">The token to check for a cancellation request.</param>
3737
internal static void ThrowIfCancellationRequested(CancellationToken cancellationToken)
38+
{
39+
ThrowIfCancellationRequested(innerException: null, cancellationToken);
40+
}
41+
42+
/// <summary>Throws a cancellation exception if cancellation has been requested via <paramref name="cancellationToken"/>.</summary>
43+
/// <param name="innerException">The inner exception to wrap. May be null.</param>
44+
/// <param name="cancellationToken">The token to check for a cancellation request.</param>
45+
internal static void ThrowIfCancellationRequested(Exception? innerException, CancellationToken cancellationToken)
3846
{
3947
if (cancellationToken.IsCancellationRequested)
4048
{
41-
ThrowOperationCanceledException(innerException: null, cancellationToken);
49+
ThrowOperationCanceledException(innerException, cancellationToken);
4250
}
4351
}
4452
}

src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616
<TargetPlatformIdentifier>$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))</TargetPlatformIdentifier>
1717
<DefineConstants Condition="'$(TargetPlatformIdentifier)' == 'windows'">$(DefineConstants);TargetsWindows</DefineConstants>
1818
<DefineConstants Condition="'$(TargetPlatformIdentifier)' == 'browser'">$(DefineConstants);TARGETS_BROWSER</DefineConstants>
19-
<!-- Active issue https://github.com/dotnet/runtime/issues/96173 -->
20-
<_XUnitBackgroundExec>false</_XUnitBackgroundExec>
2119
</PropertyGroup>
2220

2321
<PropertyGroup Condition="'$(TargetOS)' == 'browser'">

src/libraries/System.Private.Xml/tests/XmlSchema/XmlSchemaSet/TC_SchemaSet_Add_URL.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ public void v3()
6464

6565
//-----------------------------------------------------------------------------------
6666
[Fact]
67-
[ActiveIssue("https://github.com/dotnet/runtime/issues/75123", typeof(PlatformDetection), nameof(PlatformDetection.IsWasmThreadingSupported))]
6867
//[Variation(Desc = "v4 - ns = valid, URL = invalid")]
6968
public void v4()
7069
{
Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
3-
<Suppression>
4-
<DiagnosticId>CP0001</DiagnosticId>
5-
<Target>T:System.Runtime.InteropServices.JavaScript.SynchronizationContextExtension</Target>
6-
<Left>ref/net9.0/System.Runtime.InteropServices.JavaScript.dll</Left>
7-
<Right>runtimes/browser/lib/net9.0/System.Runtime.InteropServices.JavaScript.dll</Right>
8-
</Suppression>
93
<Suppression>
104
<DiagnosticId>CP0001</DiagnosticId>
115
<Target>T:System.Runtime.InteropServices.JavaScript.CancelablePromise</Target>
@@ -18,10 +12,4 @@
1812
<Left>ref/net9.0/System.Runtime.InteropServices.JavaScript.dll</Left>
1913
<Right>runtimes/browser/lib/net9.0/System.Runtime.InteropServices.JavaScript.dll</Right>
2014
</Suppression>
21-
<Suppression>
22-
<DiagnosticId>CP0002</DiagnosticId>
23-
<Target>M:System.Runtime.InteropServices.JavaScript.JSHost.get_CurrentOrMainJSSynchronizationContext</Target>
24-
<Left>ref/net9.0/System.Runtime.InteropServices.JavaScript.dll</Left>
25-
<Right>runtimes/browser/lib/net9.0/System.Runtime.InteropServices.JavaScript.dll</Right>
26-
</Suppression>
2715
</Suppressions>

src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@
4242
<Compile Include="System\Runtime\InteropServices\JavaScript\JSExportAttribute.cs" />
4343
<Compile Include="System\Runtime\InteropServices\JavaScript\JSImportAttribute.cs" />
4444
<Compile Include="System\Runtime\InteropServices\JavaScript\CancelablePromise.cs" />
45-
<Compile Include="System\Runtime\InteropServices\JavaScript\SynchronizationContextExtensions.cs" />
4645
<Compile Include="System\Runtime\InteropServices\JavaScript\JSProxyContext.cs" />
4746

4847
<Compile Include="System\Runtime\InteropServices\JavaScript\MarshalerType.cs" />

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

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -50,17 +50,5 @@ public static Task<JSObject> ImportAsync(string moduleName, string moduleUrl, Ca
5050
return JSHostImplementation.ImportAsync(moduleName, moduleUrl, cancellationToken);
5151
}
5252

53-
public static SynchronizationContext CurrentOrMainJSSynchronizationContext
54-
{
55-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
56-
get
57-
{
58-
#if FEATURE_WASM_THREADS
59-
return (JSProxyContext.ExecutionContext ?? JSProxyContext.MainThreadContext).SynchronizationContext;
60-
#else
61-
return null!;
62-
#endif
63-
}
64-
}
6553
}
6654
}

0 commit comments

Comments
 (0)