未验证 提交 38ecfc39 编写于 作者: G Geoff Kizer 提交者: GitHub

ensure Http2Connection is disposed if SetupAsync throws (#51889)

* ensure Http2Connection is disposed if SetupAsync throws

* add relevant test, improve existing test, and improve exception handling
Co-authored-by: NGeoffrey Kizer <geoffrek@windows.microsoft.com>
上级 779e1e58
......@@ -168,32 +168,40 @@ public Http2Connection(HttpConnectionPool pool, Stream stream)
public async ValueTask SetupAsync()
{
_outgoingBuffer.EnsureAvailableSpace(s_http2ConnectionPreface.Length +
FrameHeader.Size + FrameHeader.SettingLength +
FrameHeader.Size + FrameHeader.WindowUpdateLength);
// Send connection preface
s_http2ConnectionPreface.AsSpan().CopyTo(_outgoingBuffer.AvailableSpan);
_outgoingBuffer.Commit(s_http2ConnectionPreface.Length);
// Send SETTINGS frame. Disable push promise.
FrameHeader.WriteTo(_outgoingBuffer.AvailableSpan, FrameHeader.SettingLength, FrameType.Settings, FrameFlags.None, streamId: 0);
_outgoingBuffer.Commit(FrameHeader.Size);
BinaryPrimitives.WriteUInt16BigEndian(_outgoingBuffer.AvailableSpan, (ushort)SettingId.EnablePush);
_outgoingBuffer.Commit(2);
BinaryPrimitives.WriteUInt32BigEndian(_outgoingBuffer.AvailableSpan, 0);
_outgoingBuffer.Commit(4);
// Send initial connection-level WINDOW_UPDATE
FrameHeader.WriteTo(_outgoingBuffer.AvailableSpan, FrameHeader.WindowUpdateLength, FrameType.WindowUpdate, FrameFlags.None, streamId: 0);
_outgoingBuffer.Commit(FrameHeader.Size);
BinaryPrimitives.WriteUInt32BigEndian(_outgoingBuffer.AvailableSpan, ConnectionWindowSize - DefaultInitialWindowSize);
_outgoingBuffer.Commit(4);
await _stream.WriteAsync(_outgoingBuffer.ActiveMemory).ConfigureAwait(false);
_outgoingBuffer.Discard(_outgoingBuffer.ActiveLength);
_expectingSettingsAck = true;
try
{
_outgoingBuffer.EnsureAvailableSpace(s_http2ConnectionPreface.Length +
FrameHeader.Size + FrameHeader.SettingLength +
FrameHeader.Size + FrameHeader.WindowUpdateLength);
// Send connection preface
s_http2ConnectionPreface.AsSpan().CopyTo(_outgoingBuffer.AvailableSpan);
_outgoingBuffer.Commit(s_http2ConnectionPreface.Length);
// Send SETTINGS frame. Disable push promise.
FrameHeader.WriteTo(_outgoingBuffer.AvailableSpan, FrameHeader.SettingLength, FrameType.Settings, FrameFlags.None, streamId: 0);
_outgoingBuffer.Commit(FrameHeader.Size);
BinaryPrimitives.WriteUInt16BigEndian(_outgoingBuffer.AvailableSpan, (ushort)SettingId.EnablePush);
_outgoingBuffer.Commit(2);
BinaryPrimitives.WriteUInt32BigEndian(_outgoingBuffer.AvailableSpan, 0);
_outgoingBuffer.Commit(4);
// Send initial connection-level WINDOW_UPDATE
FrameHeader.WriteTo(_outgoingBuffer.AvailableSpan, FrameHeader.WindowUpdateLength, FrameType.WindowUpdate, FrameFlags.None, streamId: 0);
_outgoingBuffer.Commit(FrameHeader.Size);
BinaryPrimitives.WriteUInt32BigEndian(_outgoingBuffer.AvailableSpan, ConnectionWindowSize - DefaultInitialWindowSize);
_outgoingBuffer.Commit(4);
await _stream.WriteAsync(_outgoingBuffer.ActiveMemory).ConfigureAwait(false);
_outgoingBuffer.Discard(_outgoingBuffer.ActiveLength);
_expectingSettingsAck = true;
}
catch (Exception e)
{
Dispose();
throw new IOException(SR.net_http_http2_connection_not_established, e);
}
_ = ProcessIncomingFramesAsync();
_ = ProcessOutgoingFramesAsync();
......
......@@ -1439,7 +1439,15 @@ private async ValueTask<Http2Connection> ConstructHttp2ConnectionAsync(Stream st
stream = await ApplyPlaintextFilterAsync(async: true, stream, HttpVersion.Version20, request, cancellationToken).ConfigureAwait(false);
Http2Connection http2Connection = new Http2Connection(this, stream);
await http2Connection.SetupAsync().ConfigureAwait(false);
try
{
await http2Connection.SetupAsync().ConfigureAwait(false);
}
catch (Exception e)
{
// Note, SetupAsync will dispose the connection if there is an exception.
throw new HttpRequestException(SR.net_http_client_execution_error, e);
}
AddHttp2Connection(http2Connection);
......
......@@ -2411,9 +2411,13 @@ public async Task ConnectCallback_ContextHasCorrectProperties_Success(bool syncR
});
}
[Fact]
public async Task ConnectCallback_BindLocalAddress_Success()
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task ConnectCallback_BindLocalAddress_Success(bool useSsl)
{
GenericLoopbackOptions options = new GenericLoopbackOptions() { UseSsl = useSsl };
await LoopbackServerFactory.CreateClientAndServerAsync(
async uri =>
{
......@@ -2438,7 +2442,7 @@ public async Task ConnectCallback_BindLocalAddress_Success()
async server =>
{
await server.AcceptConnectionSendResponseAndCloseAsync(content: "foo");
});
}, options: options);
}
[Theory]
......@@ -2587,6 +2591,35 @@ public async Task ConnectCallback_ConnectionPrefix_Success(bool useSsl)
Assert.Equal("foo", response);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task ConnectCallback_StreamThrowsOnWrite_ExceptionAndStreamDisposed(bool useSsl)
{
const string ExceptionMessage = "THROWONWRITE";
bool disposeCalled = false;
using HttpClientHandler handler = CreateHttpClientHandler();
handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates;
var socketsHandler = (SocketsHttpHandler)GetUnderlyingSocketsHttpHandler(handler);
socketsHandler.ConnectCallback = (context, token) =>
{
var throwOnWriteStream = new DelegateDelegatingStream(Stream.Null);
throwOnWriteStream.WriteAsyncMemoryFunc = (buffer, token) => ValueTask.FromException(new IOException(ExceptionMessage));
throwOnWriteStream.DisposeFunc = (_) => { disposeCalled = true; };
throwOnWriteStream.DisposeAsyncFunc = () => { disposeCalled = true; return default; };
return ValueTask.FromResult<Stream>(throwOnWriteStream);
};
using HttpClient client = CreateHttpClient(handler);
client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionExact;
HttpRequestException hre = await Assert.ThrowsAnyAsync<HttpRequestException>(async () => await client.GetStringAsync($"{(useSsl ? "https" : "http")}://nowhere.invalid/foo"));
Debug.Assert(disposeCalled);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
......
......@@ -25,8 +25,12 @@
Link="Common\System\Diagnostics\Tracing\ConsoleEventListener.cs" />
<Compile Include="$(CommonTestPath)System\IO\ByteLoggingStream.cs"
Link="Common\System\IO\ByteLoggingStream.cs" />
<Compile Include="$(CommonTestPath)System\IO\DelegateDelegatingStream.cs"
Link="CommonTest\System\IO\DelegateDelegatingStream.cs" />
<Compile Include="$(CommonTestPath)System\IO\DelegateStream.cs"
Link="Common\System\IO\DelegateStream.cs" />
<Compile Include="$(CommonPath)System\IO\DelegatingStream.cs"
Link="Common\System\IO\DelegatingStream.cs" />
<Compile Include="$(CommonTestPath)System\Net\RemoteServerQuery.cs"
Link="Common\System\Net\RemoteServerQuery.cs" />
<Compile Include="$(CommonTestPath)System\Security\Cryptography\PlatformSupport.cs"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册