Fix TestServer hang with duplex streaming requests (#17158)
This commit is contained in:
parent
ad8ecf96a1
commit
34294a3e7a
|
|
@ -110,8 +110,10 @@ namespace Microsoft.AspNetCore.TestHost
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _application.ProcessRequestAsync(_testContext);
|
await _application.ProcessRequestAsync(_testContext);
|
||||||
await CompleteRequestAsync();
|
|
||||||
|
// Matches Kestrel server: response is completed before request is drained
|
||||||
await CompleteResponseAsync();
|
await CompleteResponseAsync();
|
||||||
|
await CompleteRequestAsync();
|
||||||
_application.DisposeContext(_testContext, exception: null);
|
_application.DisposeContext(_testContext, exception: null);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
@ -171,18 +173,9 @@ namespace Microsoft.AspNetCore.TestHost
|
||||||
await _requestPipe.Reader.CompleteAsync();
|
await _requestPipe.Reader.CompleteAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_sendRequestStreamTask != null)
|
// Don't wait for request to drain. It could block indefinitely. In a real server
|
||||||
{
|
// we would wait for a timeout and then kill the socket.
|
||||||
try
|
// Potential future improvement: add logging that the request timed out
|
||||||
{
|
|
||||||
// Ensure duplex request is either completely read or has been aborted.
|
|
||||||
await _sendRequestStreamTask;
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
// Request was canceled, likely because it wasn't read before the request ended.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task CompleteResponseAsync()
|
internal async Task CompleteResponseAsync()
|
||||||
|
|
|
||||||
|
|
@ -384,6 +384,53 @@ namespace Microsoft.AspNetCore.TestHost
|
||||||
await writeTask;
|
await writeTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ClientStreaming_ResponseCompletesWithoutResponseBodyWrite()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var requestStreamTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
|
||||||
|
RequestDelegate appDelegate = ctx =>
|
||||||
|
{
|
||||||
|
ctx.Response.Headers["test-header"] = "true";
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
Stream requestStream = null;
|
||||||
|
|
||||||
|
var builder = new WebHostBuilder().Configure(app => app.Run(appDelegate));
|
||||||
|
var server = new TestServer(builder);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
var httpRequest = new HttpRequestMessage(HttpMethod.Post, "http://localhost:12345");
|
||||||
|
httpRequest.Version = new Version(2, 0);
|
||||||
|
httpRequest.Content = new PushContent(async stream =>
|
||||||
|
{
|
||||||
|
requestStream = stream;
|
||||||
|
await requestStreamTcs.Task;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.SendAsync(httpRequest, HttpCompletionOption.ResponseHeadersRead).WithTimeout();
|
||||||
|
|
||||||
|
var responseContent = await response.Content.ReadAsStreamAsync().WithTimeout();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
Assert.Equal("true", response.Headers.GetValues("test-header").Single());
|
||||||
|
|
||||||
|
// Read response
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
var length = await responseContent.ReadAsync(buffer).AsTask().WithTimeout();
|
||||||
|
Assert.Equal(0, length);
|
||||||
|
|
||||||
|
// Writing to request stream will fail because server is complete
|
||||||
|
await Assert.ThrowsAnyAsync<Exception>(() => requestStream.WriteAsync(buffer).AsTask());
|
||||||
|
|
||||||
|
// Unblock request
|
||||||
|
requestStreamTcs.TrySetResult(null);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ClientStreaming_ServerAbort()
|
public async Task ClientStreaming_ServerAbort()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue