From e74312fb6c131aa7b31b2e1052402be9703860d2 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Mon, 16 Jan 2023 14:21:20 +0100 Subject: [PATCH 1/9] - fix unhandled error in reader.cancel() promise - return bytes of streaming response as soon as available --- src/mono/wasm/runtime/http.ts | 50 ++++++++++++++++------------------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/src/mono/wasm/runtime/http.ts b/src/mono/wasm/runtime/http.ts index 7404cefcb304ea..bff55fcfe6ece1 100644 --- a/src/mono/wasm/runtime/http.ts +++ b/src/mono/wasm/runtime/http.ts @@ -21,7 +21,9 @@ export function http_wasm_abort_request(abort_controller: AbortController): void export function http_wasm_abort_response(res: ResponseExtension): void { res.__abort_controller.abort(); if (res.__reader) { - res.__reader.cancel(); + res.__reader.cancel().catch(() => { + //we ignore the error + }); } } @@ -104,38 +106,32 @@ export async function http_wasm_get_streamed_response_bytes(res: ResponseExtensi // the bufferPtr is pinned by the caller const view = new Span(bufferPtr, bufferLength, MemoryViewType.Byte); return wrap_as_cancelable_promise(async () => { - if (!res.__chunk && res.body) { + if (!res.body) { + return 0; + } + if (!res.__reader) { res.__reader = res.body.getReader(); + } + if (!res.__chunk) { res.__chunk = await res.__reader.read(); res.__source_offset = 0; } + if (res.__chunk.done) { + return 0; + } - let target_offset = 0; - let bytes_read = 0; - // loop until end of browser stream or end of C# buffer - while (res.__reader && res.__chunk && !res.__chunk.done) { - const remaining_source = res.__chunk.value.byteLength - res.__source_offset; - if (remaining_source === 0) { - res.__chunk = await res.__reader.read(); - res.__source_offset = 0; - continue;// are we done yet - } - - const remaining_target = view.byteLength - target_offset; - const bytes_copied = Math.min(remaining_source, remaining_target); - const source_view = res.__chunk.value.subarray(res.__source_offset, res.__source_offset + bytes_copied); - - // copy available bytes - view.set(source_view, target_offset); - target_offset += bytes_copied; - bytes_read += bytes_copied; - res.__source_offset += bytes_copied; - - if (target_offset == view.byteLength) { - return bytes_read; - } + const remaining_source = res.__chunk.value.byteLength - res.__source_offset; + mono_assert(remaining_source > 0, "expected remaining_source to be greater than 0"); + + const bytes_copied = Math.min(remaining_source, view.byteLength); + const source_view = res.__chunk.value.subarray(res.__source_offset, bytes_copied); + view.set(source_view, 0); + res.__source_offset += bytes_copied; + if (remaining_source == bytes_copied) { + res.__chunk = undefined; } - return bytes_read; + + return bytes_copied; }); } From 9bf6badcc62e49b9b5a55d4c5fc5d3894d769939 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Mon, 16 Jan 2023 14:26:05 +0100 Subject: [PATCH 2/9] free reader --- src/mono/wasm/runtime/http.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mono/wasm/runtime/http.ts b/src/mono/wasm/runtime/http.ts index bff55fcfe6ece1..a539cf47c01c6f 100644 --- a/src/mono/wasm/runtime/http.ts +++ b/src/mono/wasm/runtime/http.ts @@ -117,6 +117,7 @@ export async function http_wasm_get_streamed_response_bytes(res: ResponseExtensi res.__source_offset = 0; } if (res.__chunk.done) { + res.__reader = undefined; return 0; } From 0778466958d4a938b69051333243252d0f532798 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Mon, 16 Jan 2023 17:54:12 +0100 Subject: [PATCH 3/9] fix + test --- .../System/Net/Http/HttpClientHandlerTest.cs | 25 +++++++++++++------ src/mono/wasm/runtime/http.ts | 8 ++---- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs index 91ea0ae621bffe..4f4f7ad00c589d 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs @@ -984,13 +984,14 @@ await connection.WriteStringAsync( } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - [InlineData(null, false)] + [InlineData(true, true, true)] + [InlineData(true, true, false)] + [InlineData(true, false, false)] + [InlineData(false, true, false)] + [InlineData(false, false, false)] + [InlineData(null, false, false)] [ActiveIssue("https://github.com/dotnet/runtime/issues/65429", typeof(PlatformDetection), nameof(PlatformDetection.IsNodeJS))] - public async Task ReadAsStreamAsync_HandlerProducesWellBehavedResponseStream(bool? chunked, bool enableWasmStreaming) + public async Task ReadAsStreamAsync_HandlerProducesWellBehavedResponseStream(bool? chunked, bool enableWasmStreaming, bool slowChunks) { if (IsWinHttpHandler && UseVersion >= HttpVersion20.Value) { @@ -1184,7 +1185,17 @@ await server.AcceptConnectionAsync(async connection => { case true: await connection.SendResponseAsync(HttpStatusCode.OK, headers: new HttpHeaderData[] { new HttpHeaderData("Transfer-Encoding", "chunked") }, isFinal: false); - await connection.SendResponseBodyAsync("3\r\nhel\r\n8\r\nlo world\r\n0\r\n\r\n"); + if(slowChunks) + { + await connection.SendResponseBodyAsync("1\r\nh\r\n", false); + await connection.SendResponseBodyAsync("2\r\nel\r\n", false); + await connection.SendResponseBodyAsync("8\r\nlo world\r\n", false); + await connection.SendResponseBodyAsync("0\r\n\r\n", true); + } + else + { + await connection.SendResponseBodyAsync("3\r\nhel\r\n8\r\nlo world\r\n0\r\n\r\n"); + } break; case false: diff --git a/src/mono/wasm/runtime/http.ts b/src/mono/wasm/runtime/http.ts index a539cf47c01c6f..9e2713647c5da1 100644 --- a/src/mono/wasm/runtime/http.ts +++ b/src/mono/wasm/runtime/http.ts @@ -106,18 +106,14 @@ export async function http_wasm_get_streamed_response_bytes(res: ResponseExtensi // the bufferPtr is pinned by the caller const view = new Span(bufferPtr, bufferLength, MemoryViewType.Byte); return wrap_as_cancelable_promise(async () => { - if (!res.body) { - return 0; - } if (!res.__reader) { - res.__reader = res.body.getReader(); + res.__reader = res.body!.getReader(); } if (!res.__chunk) { res.__chunk = await res.__reader.read(); res.__source_offset = 0; } if (res.__chunk.done) { - res.__reader = undefined; return 0; } @@ -125,7 +121,7 @@ export async function http_wasm_get_streamed_response_bytes(res: ResponseExtensi mono_assert(remaining_source > 0, "expected remaining_source to be greater than 0"); const bytes_copied = Math.min(remaining_source, view.byteLength); - const source_view = res.__chunk.value.subarray(res.__source_offset, bytes_copied); + const source_view = res.__chunk.value.subarray(res.__source_offset, res.__source_offset + bytes_copied); view.set(source_view, 0); res.__source_offset += bytes_copied; if (remaining_source == bytes_copied) { From 494d2fa62a29189fed55835d837ea885a69a7aa8 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Mon, 16 Jan 2023 17:57:15 +0100 Subject: [PATCH 4/9] test --- .../Common/tests/System/Net/Http/HttpClientHandlerTest.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs index 4f4f7ad00c589d..20ede36fdc876e 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs @@ -1188,8 +1188,11 @@ await server.AcceptConnectionAsync(async connection => if(slowChunks) { await connection.SendResponseBodyAsync("1\r\nh\r\n", false); + await Task.Delay(100); await connection.SendResponseBodyAsync("2\r\nel\r\n", false); + await Task.Delay(100); await connection.SendResponseBodyAsync("8\r\nlo world\r\n", false); + await Task.Delay(100); await connection.SendResponseBodyAsync("0\r\n\r\n", true); } else From 0516f2cd7d1051a8c9db05dcd35e2536e0fb5dd0 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Mon, 16 Jan 2023 18:48:27 +0100 Subject: [PATCH 5/9] test --- .../tests/System/Net/Http/HttpClientHandlerTest.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs index 20ede36fdc876e..f9474d30b7d0a0 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs @@ -1080,11 +1080,20 @@ await LoopbackServerFactory.CreateClientAndServerAsync(async uri => // Various forms of reading var buffer = new byte[1]; + var buffer2 = new byte[2]; if (PlatformDetection.IsBrowser) { #if !NETFRAMEWORK - Assert.Equal('h', await responseStream.ReadByteAsync()); + if(slowChunks) + { + Assert.Equal(1, await responseStream.ReadAsync(new Memory(buffer2))); + Assert.Equal((byte)'h', buffer2[0]); + } + else + { + Assert.Equal('h', await responseStream.ReadByteAsync()); + } Assert.Equal('e', await responseStream.ReadByteAsync()); Assert.Equal(1, await responseStream.ReadAsync(new Memory(buffer))); Assert.Equal((byte)'l', buffer[0]); @@ -1190,9 +1199,7 @@ await server.AcceptConnectionAsync(async connection => await connection.SendResponseBodyAsync("1\r\nh\r\n", false); await Task.Delay(100); await connection.SendResponseBodyAsync("2\r\nel\r\n", false); - await Task.Delay(100); await connection.SendResponseBodyAsync("8\r\nlo world\r\n", false); - await Task.Delay(100); await connection.SendResponseBodyAsync("0\r\n\r\n", true); } else From ea02a5f75aae941a5560874170ee1de9e1fc1ac4 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Tue, 17 Jan 2023 12:19:21 +0100 Subject: [PATCH 6/9] feedback --- .../Common/tests/System/Net/Http/HttpClientHandlerTest.cs | 8 +++++++- src/mono/wasm/runtime/http.ts | 8 ++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs index f9474d30b7d0a0..6d7c1f818bb2f2 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs @@ -1004,6 +1004,12 @@ public async Task ReadAsStreamAsync_HandlerProducesWellBehavedResponseStream(boo return; } + if (enableWasmStreaming && !PlatformDetection.IsBrowser) + { + // enableWasmStreaming makes only sense on Browser platform + return; + } + await LoopbackServerFactory.CreateClientAndServerAsync(async uri => { var request = new HttpRequestMessage(HttpMethod.Get, uri) { Version = UseVersion }; @@ -1194,7 +1200,7 @@ await server.AcceptConnectionAsync(async connection => { case true: await connection.SendResponseAsync(HttpStatusCode.OK, headers: new HttpHeaderData[] { new HttpHeaderData("Transfer-Encoding", "chunked") }, isFinal: false); - if(slowChunks) + if(PlatformDetection.IsBrowser && slowChunks) { await connection.SendResponseBodyAsync("1\r\nh\r\n", false); await Task.Delay(100); diff --git a/src/mono/wasm/runtime/http.ts b/src/mono/wasm/runtime/http.ts index 9e2713647c5da1..981171822e55be 100644 --- a/src/mono/wasm/runtime/http.ts +++ b/src/mono/wasm/runtime/http.ts @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. import { wrap_as_cancelable_promise } from "./cancelable-promise"; +import { Module } from "./imports"; import { MemoryViewType, Span } from "./marshal"; import { mono_assert } from "./types"; import { VoidPtr } from "./types/emscripten"; @@ -21,8 +22,11 @@ export function http_wasm_abort_request(abort_controller: AbortController): void export function http_wasm_abort_response(res: ResponseExtension): void { res.__abort_controller.abort(); if (res.__reader) { - res.__reader.cancel().catch(() => { - //we ignore the error + res.__reader.cancel().catch((err) => { + if (err && err.name !== "AbortError") { + Module.printErr("MONO_WASM: Error in http_wasm_abort_response: " + err); + } + // otherwise, it's expected }); } } From ef66bd617c8c7228896158c53cc5701f44f3e953 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Tue, 17 Jan 2023 22:14:51 +0100 Subject: [PATCH 7/9] new test for streaming abort --- .../System/Net/Http/HttpClientHandlerTest.cs | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs index 6d7c1f818bb2f2..cfa4d900f395d4 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs @@ -1322,6 +1322,49 @@ await LoopbackServerFactory.CreateClientAndServerAsync(async uri => server => server.AcceptConnectionSendResponseAndCloseAsync()); } + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser))] + public async Task ReadAsStreamAsync_StreamingCancellation() + { + await LoopbackServerFactory.CreateClientAndServerAsync(async uri => + { + var request = new HttpRequestMessage(HttpMethod.Get, uri) { Version = UseVersion }; +#if !NETFRAMEWORK + request.Options.Set(new HttpRequestOptionsKey("WebAssemblyEnableStreamingResponse"), true); +#endif + + var cts = new CancellationTokenSource(); + + using (var client = new HttpMessageInvoker(CreateHttpClientHandler())) + using (HttpResponseMessage response = await client.SendAsync(TestAsync, request, CancellationToken.None)) + { + using (Stream responseStream = await response.Content.ReadAsStreamAsync(TestAsync)) + { + // Various forms of reading + var buffer = new byte[1]; +#if !NETFRAMEWORK + Assert.Equal(1, await responseStream.ReadAsync(new Memory(buffer))); + Assert.Equal((byte)'h', buffer[0]); + var sizePromise = responseStream.ReadAsync(new Memory(buffer), cts.Token); + cts.Cancel(); + await Assert.ThrowsAsync(async () => await sizePromise); +#endif + } + } + }, async server => + { + await server.AcceptConnectionAsync(async connection => + { + await connection.ReadRequestDataAsync(); + await connection.SendResponseAsync(HttpStatusCode.OK, headers: new HttpHeaderData[] { new HttpHeaderData("Transfer-Encoding", "chunked") }, isFinal: false); + await connection.SendResponseBodyAsync("1\r\nh\r\n", false); + await Task.Delay(100); + await connection.SendResponseBodyAsync("2\r\nel\r\n", false); + await connection.SendResponseBodyAsync("8\r\nlo world\r\n", false); + await connection.SendResponseBodyAsync("0\r\n\r\n", true); + }); + }); + } + [Fact] public async Task Dispose_DisposingHandlerCancelsActiveOperationsWithoutResponses() { From faa430036c414e31f584d2d08d68a05d17dbcf15 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Wed, 18 Jan 2023 11:58:13 +0100 Subject: [PATCH 8/9] test + fix streaming cancellation --- .../System/Net/Http/HttpClientHandlerTest.cs | 38 +++++++++++++++---- src/mono/wasm/runtime/http.ts | 2 +- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs index cfa4d900f395d4..f795b82ffbab54 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs @@ -1325,6 +1325,7 @@ await LoopbackServerFactory.CreateClientAndServerAsync(async uri => [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser))] public async Task ReadAsStreamAsync_StreamingCancellation() { + var tcs = new TaskCompletionSource(); await LoopbackServerFactory.CreateClientAndServerAsync(async uri => { var request = new HttpRequestMessage(HttpMethod.Get, uri) { Version = UseVersion }; @@ -1333,20 +1334,19 @@ await LoopbackServerFactory.CreateClientAndServerAsync(async uri => #endif var cts = new CancellationTokenSource(); - using (var client = new HttpMessageInvoker(CreateHttpClientHandler())) using (HttpResponseMessage response = await client.SendAsync(TestAsync, request, CancellationToken.None)) { using (Stream responseStream = await response.Content.ReadAsStreamAsync(TestAsync)) { - // Various forms of reading var buffer = new byte[1]; #if !NETFRAMEWORK Assert.Equal(1, await responseStream.ReadAsync(new Memory(buffer))); Assert.Equal((byte)'h', buffer[0]); var sizePromise = responseStream.ReadAsync(new Memory(buffer), cts.Token); cts.Cancel(); - await Assert.ThrowsAsync(async () => await sizePromise); + await Assert.ThrowsAsync(async () => await sizePromise); + tcs.SetResult(true); #endif } } @@ -1357,10 +1357,34 @@ await server.AcceptConnectionAsync(async connection => await connection.ReadRequestDataAsync(); await connection.SendResponseAsync(HttpStatusCode.OK, headers: new HttpHeaderData[] { new HttpHeaderData("Transfer-Encoding", "chunked") }, isFinal: false); await connection.SendResponseBodyAsync("1\r\nh\r\n", false); - await Task.Delay(100); - await connection.SendResponseBodyAsync("2\r\nel\r\n", false); - await connection.SendResponseBodyAsync("8\r\nlo world\r\n", false); - await connection.SendResponseBodyAsync("0\r\n\r\n", true); + await tcs.Task; + }); + }); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser))] + public async Task ReadAsStreamAsync_Cancellation() + { + var tcs = new TaskCompletionSource(); + await LoopbackServerFactory.CreateClientAndServerAsync(async uri => + { + var request = new HttpRequestMessage(HttpMethod.Get, uri) { Version = UseVersion }; + var cts = new CancellationTokenSource(); + using (var client = new HttpMessageInvoker(CreateHttpClientHandler())) + { + var responsePromise = client.SendAsync(TestAsync, request, cts.Token); + cts.Cancel(); + await Assert.ThrowsAsync(async () => await responsePromise); + tcs.SetResult(true); + } + }, async server => + { + await server.AcceptConnectionAsync(async connection => + { + await connection.ReadRequestDataAsync(); + await connection.SendResponseAsync(HttpStatusCode.OK, headers: new HttpHeaderData[] { new HttpHeaderData("Transfer-Encoding", "chunked") }, isFinal: false); + await connection.SendResponseBodyAsync("1\r\nh\r\n", false); + await tcs.Task; }); }); } diff --git a/src/mono/wasm/runtime/http.ts b/src/mono/wasm/runtime/http.ts index 981171822e55be..73365d340704db 100644 --- a/src/mono/wasm/runtime/http.ts +++ b/src/mono/wasm/runtime/http.ts @@ -106,7 +106,7 @@ export function http_wasm_get_response_bytes(res: ResponseExtension, view: Span) return bytes_read; } -export async function http_wasm_get_streamed_response_bytes(res: ResponseExtension, bufferPtr: VoidPtr, bufferLength: number): Promise { +export function http_wasm_get_streamed_response_bytes(res: ResponseExtension, bufferPtr: VoidPtr, bufferLength: number): Promise { // the bufferPtr is pinned by the caller const view = new Span(bufferPtr, bufferLength, MemoryViewType.Byte); return wrap_as_cancelable_promise(async () => { From 5021035c500f7fb7712a1d88ac7ea7712b4aca01 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Thu, 19 Jan 2023 12:25:59 +0100 Subject: [PATCH 9/9] improve the test for NodeJS --- .../tests/System/Net/Http/HttpClientHandlerTest.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs index f795b82ffbab54..1e586af99977ce 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs @@ -1010,6 +1010,7 @@ public async Task ReadAsStreamAsync_HandlerProducesWellBehavedResponseStream(boo return; } + var tcs = new TaskCompletionSource(); await LoopbackServerFactory.CreateClientAndServerAsync(async uri => { var request = new HttpRequestMessage(HttpMethod.Get, uri) { Version = UseVersion }; @@ -1095,6 +1096,7 @@ await LoopbackServerFactory.CreateClientAndServerAsync(async uri => { Assert.Equal(1, await responseStream.ReadAsync(new Memory(buffer2))); Assert.Equal((byte)'h', buffer2[0]); + tcs.SetResult(true); } else { @@ -1203,7 +1205,7 @@ await server.AcceptConnectionAsync(async connection => if(PlatformDetection.IsBrowser && slowChunks) { await connection.SendResponseBodyAsync("1\r\nh\r\n", false); - await Task.Delay(100); + await tcs.Task; await connection.SendResponseBodyAsync("2\r\nel\r\n", false); await connection.SendResponseBodyAsync("8\r\nlo world\r\n", false); await connection.SendResponseBodyAsync("0\r\n\r\n", true); @@ -1323,9 +1325,11 @@ await LoopbackServerFactory.CreateClientAndServerAsync(async uri => } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/65429", typeof(PlatformDetection), nameof(PlatformDetection.IsNodeJS))] public async Task ReadAsStreamAsync_StreamingCancellation() { var tcs = new TaskCompletionSource(); + var tcs2 = new TaskCompletionSource(); await LoopbackServerFactory.CreateClientAndServerAsync(async uri => { var request = new HttpRequestMessage(HttpMethod.Get, uri) { Version = UseVersion }; @@ -1344,6 +1348,7 @@ await LoopbackServerFactory.CreateClientAndServerAsync(async uri => Assert.Equal(1, await responseStream.ReadAsync(new Memory(buffer))); Assert.Equal((byte)'h', buffer[0]); var sizePromise = responseStream.ReadAsync(new Memory(buffer), cts.Token); + await tcs2.Task; // wait for the request and response header to be sent cts.Cancel(); await Assert.ThrowsAsync(async () => await sizePromise); tcs.SetResult(true); @@ -1357,6 +1362,7 @@ await server.AcceptConnectionAsync(async connection => await connection.ReadRequestDataAsync(); await connection.SendResponseAsync(HttpStatusCode.OK, headers: new HttpHeaderData[] { new HttpHeaderData("Transfer-Encoding", "chunked") }, isFinal: false); await connection.SendResponseBodyAsync("1\r\nh\r\n", false); + tcs2.SetResult(true); await tcs.Task; }); }); @@ -1366,6 +1372,7 @@ await server.AcceptConnectionAsync(async connection => public async Task ReadAsStreamAsync_Cancellation() { var tcs = new TaskCompletionSource(); + var tcs2 = new TaskCompletionSource(); await LoopbackServerFactory.CreateClientAndServerAsync(async uri => { var request = new HttpRequestMessage(HttpMethod.Get, uri) { Version = UseVersion }; @@ -1373,6 +1380,7 @@ await LoopbackServerFactory.CreateClientAndServerAsync(async uri => using (var client = new HttpMessageInvoker(CreateHttpClientHandler())) { var responsePromise = client.SendAsync(TestAsync, request, cts.Token); + await tcs2.Task; // wait for the request to be sent cts.Cancel(); await Assert.ThrowsAsync(async () => await responsePromise); tcs.SetResult(true); @@ -1382,6 +1390,7 @@ await LoopbackServerFactory.CreateClientAndServerAsync(async uri => await server.AcceptConnectionAsync(async connection => { await connection.ReadRequestDataAsync(); + tcs2.SetResult(true); await connection.SendResponseAsync(HttpStatusCode.OK, headers: new HttpHeaderData[] { new HttpHeaderData("Transfer-Encoding", "chunked") }, isFinal: false); await connection.SendResponseBodyAsync("1\r\nh\r\n", false); await tcs.Task;