Always throw same exception type on read after connection error (#975).
This commit is contained in:
parent
6bcf7643df
commit
3c0e0f8d88
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Filter;
|
||||
|
|
@ -128,13 +129,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
return _socketClosedTcs.Task;
|
||||
}
|
||||
|
||||
public virtual void Abort()
|
||||
public virtual void Abort(Exception error = null)
|
||||
{
|
||||
// Frame.Abort calls user code while this method is always
|
||||
// called from a libuv thread.
|
||||
ThreadPool.Run(() =>
|
||||
{
|
||||
_frame.Abort();
|
||||
_frame.Abort(error);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -231,18 +232,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
}
|
||||
}
|
||||
|
||||
Exception error = null;
|
||||
IOException error = null;
|
||||
if (errorDone)
|
||||
{
|
||||
handle.Libuv.Check(status, out error);
|
||||
Log.ConnectionError(ConnectionId, error);
|
||||
Exception uvError;
|
||||
handle.Libuv.Check(status, out uvError);
|
||||
Log.ConnectionError(ConnectionId, uvError);
|
||||
error = new IOException(uvError.Message, uvError);
|
||||
}
|
||||
|
||||
SocketInput.IncomingComplete(readCount, error);
|
||||
|
||||
if (errorDone)
|
||||
{
|
||||
Abort();
|
||||
Abort(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -339,13 +339,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
/// <summary>
|
||||
/// Immediate kill the connection and poison the request and response streams.
|
||||
/// </summary>
|
||||
public void Abort()
|
||||
public void Abort(Exception error = null)
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref _requestAborted, 1, 0) == 0)
|
||||
{
|
||||
_requestProcessingStopping = true;
|
||||
|
||||
_frameStreams?.RequestBody.Abort();
|
||||
_frameStreams?.RequestBody.Abort(error);
|
||||
_frameStreams?.ResponseBody.Abort();
|
||||
|
||||
try
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
{
|
||||
private MessageBody _body;
|
||||
private FrameStreamState _state;
|
||||
private Exception _error;
|
||||
|
||||
public FrameRequestStream()
|
||||
{
|
||||
|
|
@ -163,13 +164,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
_body = null;
|
||||
}
|
||||
|
||||
public void Abort()
|
||||
public void Abort(Exception error = null)
|
||||
{
|
||||
// We don't want to throw an ODE until the app func actually completes.
|
||||
// If the request is aborted, we throw an TaskCanceledException instead.
|
||||
// If the request is aborted, we throw an TaskCanceledException instead,
|
||||
// unless error is not null, in which case we throw it.
|
||||
if (_state != FrameStreamState.Closed)
|
||||
{
|
||||
_state = FrameStreamState.Aborted;
|
||||
_error = error;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -186,7 +189,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
case FrameStreamState.Closed:
|
||||
throw new ObjectDisposedException(nameof(FrameRequestStream));
|
||||
case FrameStreamState.Aborted:
|
||||
return TaskUtilities.GetCancelledZeroTask();
|
||||
return _error != null ?
|
||||
TaskUtilities.GetFaultedTask(_error) :
|
||||
TaskUtilities.GetCancelledZeroTask();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -354,11 +353,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
var error = _tcs.Task.Exception?.InnerException;
|
||||
if (error != null)
|
||||
{
|
||||
if (error is TaskCanceledException || error is InvalidOperationException)
|
||||
{
|
||||
throw error;
|
||||
}
|
||||
throw new IOException(error.Message, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
|
@ -39,6 +40,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
|
|||
var tcs = new TaskCompletionSource<int>();
|
||||
tcs.TrySetCanceled();
|
||||
return tcs.Task;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static Task<int> GetFaultedTask(Exception error)
|
||||
{
|
||||
#if NETSTANDARD1_3
|
||||
return Task.FromException<int>(error);
|
||||
#else
|
||||
var tcs = new TaskCompletionSource<int>();
|
||||
tcs.SetException(error);
|
||||
return tcs.Task;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -248,25 +248,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
{
|
||||
await context.Request.Body.ReadAsync(new byte[1], 0, 1);
|
||||
}
|
||||
catch (BadHttpRequestException)
|
||||
{
|
||||
// We need this here because BadHttpRequestException derives from IOException,
|
||||
// and we're looking for an actual IOException.
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
// This is one of two exception types that might thrown in this scenario.
|
||||
// An IOException is thrown if ReadAsync is awaiting on SocketInput when
|
||||
// the connection is aborted.
|
||||
expectedExceptionThrown = ex.InnerException is UvException;
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
// This is the other exception type that might be thrown here.
|
||||
// A TaskCanceledException is thrown if ReadAsync is called when the
|
||||
// connection has already been aborted, since FrameRequestStream is
|
||||
// aborted and returns a canceled task from ReadAsync.
|
||||
expectedExceptionThrown = true;
|
||||
expectedExceptionThrown = ex.InnerException is UvException && ex.InnerException.Message.Contains("ECONNRESET");
|
||||
}
|
||||
|
||||
appDone.Release();
|
||||
|
|
|
|||
|
|
@ -114,5 +114,17 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
var task = stream.ReadAsync(new byte[1], 0, 1);
|
||||
Assert.True(task.IsCanceled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AbortWithErrorCausesReadToCancel()
|
||||
{
|
||||
var stream = new FrameRequestStream();
|
||||
stream.StartAcceptingReads(null);
|
||||
var error = new Exception();
|
||||
stream.Abort(error);
|
||||
var task = stream.ReadAsync(new byte[1], 0, 1);
|
||||
Assert.True(task.IsFaulted);
|
||||
Assert.Same(error, task.Exception.InnerException);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests.TestHelpers
|
|||
RequestAbortedSource = new CancellationTokenSource();
|
||||
}
|
||||
|
||||
public override void Abort()
|
||||
public override void Abort(Exception error = null)
|
||||
{
|
||||
if (RequestAbortedSource != null)
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue