From 2607a608b03595eb457b796d0bb7f8b1b2d23281 Mon Sep 17 00:00:00 2001 From: Ahmet Ibrahim Aksoy Date: Wed, 12 Mar 2025 08:42:26 +0300 Subject: [PATCH 1/4] Fix Hang on GetRequestStreamAsync in case underlying HttpClient.SendAsync throws for Non-Buffering Write case --- .../src/System/Net/HttpWebRequest.cs | 15 ++++++++++++++- .../tests/HttpWebRequestTest.cs | 10 ++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs index ee2466d285a142..04bacd7ac499af 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs @@ -1099,7 +1099,20 @@ private async Task InternalGetRequestStream() TaskCompletionSource getStreamTcs = new(); TaskCompletionSource completeTcs = new(); _sendRequestTask = SendRequest(async: true, new RequestStreamContent(getStreamTcs, completeTcs)); - _requestStream = new RequestStream(await getStreamTcs.Task.ConfigureAwait(false), completeTcs); + Task getStreamTask = getStreamTcs.Task; + try + { + Task result = await Task.WhenAny((Task)getStreamTask, (Task)_sendRequestTask).WaitAsync(TimeSpan.FromMilliseconds(Timeout)).ConfigureAwait(false); + if (result == _sendRequestTask && !_sendRequestTask.IsCompletedSuccessfully) + { + await _sendRequestTask.ConfigureAwait(false); // Propagate the exception + } + _requestStream = new RequestStream(await getStreamTask.ConfigureAwait(false), completeTcs); + } + catch (OperationCanceledException) + { + throw new WebException(SR.net_timeout, WebExceptionStatus.Timeout); + } } else { diff --git a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs index 2dcbc910bff4bc..68771d03fd3226 100644 --- a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs +++ b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs @@ -2094,6 +2094,16 @@ await server.AcceptConnectionAsync(async connection => }); } + [Fact] + public async Task SendHttpPostRequest_BufferingDisabledWithInvalidHost_ShouldThrow() + { + HttpWebRequest request = WebRequest.CreateHttp("http://anything-unusable-blabla"); + request.Method = "POST"; + request.AllowWriteStreamBuffering = false; + WebException webException = await Assert.ThrowsAnyAsync(() => request.GetRequestStreamAsync()); + Assert.Equal(WebExceptionStatus.NameResolutionFailure, webException.Status); + } + [Fact] public async Task SendHttpPostRequest_BufferingDisabled_ConnectionShouldStartWithRequestStream() { From 169cc5b4257ab071ea0d30d274fd7037740bf07c Mon Sep 17 00:00:00 2001 From: Ahmet Ibrahim Aksoy Date: Wed, 12 Mar 2025 08:52:38 +0300 Subject: [PATCH 2/4] Update src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs --- .../System.Net.Requests/src/System/Net/HttpWebRequest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs index 04bacd7ac499af..5ed13ab7345a23 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs @@ -1109,7 +1109,7 @@ private async Task InternalGetRequestStream() } _requestStream = new RequestStream(await getStreamTask.ConfigureAwait(false), completeTcs); } - catch (OperationCanceledException) + catch (TimeoutException) { throw new WebException(SR.net_timeout, WebExceptionStatus.Timeout); } From 63b5bcb114dcf7891cbf6b05f318481c46d2e026 Mon Sep 17 00:00:00 2001 From: Ahmet Ibrahim Aksoy Date: Wed, 12 Mar 2025 10:42:30 +0300 Subject: [PATCH 3/4] Linux returns UnknownError for exception --- src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs index 68771d03fd3226..f68866018a7e46 100644 --- a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs +++ b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs @@ -2101,7 +2101,7 @@ public async Task SendHttpPostRequest_BufferingDisabledWithInvalidHost_ShouldThr request.Method = "POST"; request.AllowWriteStreamBuffering = false; WebException webException = await Assert.ThrowsAnyAsync(() => request.GetRequestStreamAsync()); - Assert.Equal(WebExceptionStatus.NameResolutionFailure, webException.Status); + Assert.Equal(PlatformDetection.IsLinux ? WebExceptionStatus.UnknownError : WebExceptionStatus.NameResolutionFailure, webException.Status); } [Fact] From f0686c63465f0b5d7ca59df64d1870de8e25eebb Mon Sep 17 00:00:00 2001 From: Ahmet Ibrahim Aksoy Date: Fri, 28 Mar 2025 21:11:46 +0300 Subject: [PATCH 4/4] Fix edge case --- .../src/System/Net/HttpWebRequest.cs | 12 ++++++++++-- .../System.Net.Requests/tests/HttpWebRequestTest.cs | 3 +-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs index 5ed13ab7345a23..251e8f3a19aefa 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs @@ -1096,16 +1096,23 @@ private async Task InternalGetRequestStream() { // We're calling SendRequest with async, because we need to open the connection and send the request // Otherwise, sync path will block the current thread until the request is sent. + CancellationTokenSource cts = new(); + cts.CancelAfter(Timeout); TaskCompletionSource getStreamTcs = new(); TaskCompletionSource completeTcs = new(); + cts.Token.Register(() => { + + }); _sendRequestTask = SendRequest(async: true, new RequestStreamContent(getStreamTcs, completeTcs)); Task getStreamTask = getStreamTcs.Task; try { - Task result = await Task.WhenAny((Task)getStreamTask, (Task)_sendRequestTask).WaitAsync(TimeSpan.FromMilliseconds(Timeout)).ConfigureAwait(false); - if (result == _sendRequestTask && !_sendRequestTask.IsCompletedSuccessfully) + Task result = await Task.WhenAny((Task)getStreamTask, (Task)_sendRequestTask).ConfigureAwait(false); + if (result == _sendRequestTask) { await _sendRequestTask.ConfigureAwait(false); // Propagate the exception + return Stream.Null; // If we successfully completed the request without getting the stream + // We just need to return a null stream, to avoid blocking. } _requestStream = new RequestStream(await getStreamTask.ConfigureAwait(false), completeTcs); } @@ -1187,6 +1194,7 @@ private Task SendRequest(bool async, HttpContent? content = _sendRequestMessage = new HttpRequestMessage(HttpMethod.Parse(_originVerb), _requestUri); _sendRequestCts = new CancellationTokenSource(); + _sendRequestCts.CancelAfter(Timeout); _httpClient = GetCachedOrCreateHttpClient(async, out _disposeRequired); if (content is not null) diff --git a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs index f68866018a7e46..704cf36b837bdf 100644 --- a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs +++ b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs @@ -2100,8 +2100,7 @@ public async Task SendHttpPostRequest_BufferingDisabledWithInvalidHost_ShouldThr HttpWebRequest request = WebRequest.CreateHttp("http://anything-unusable-blabla"); request.Method = "POST"; request.AllowWriteStreamBuffering = false; - WebException webException = await Assert.ThrowsAnyAsync(() => request.GetRequestStreamAsync()); - Assert.Equal(PlatformDetection.IsLinux ? WebExceptionStatus.UnknownError : WebExceptionStatus.NameResolutionFailure, webException.Status); + await Assert.ThrowsAnyAsync(() => request.GetRequestStreamAsync()); } [Fact]