-
Notifications
You must be signed in to change notification settings - Fork 10.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Framework does not handle Request cancellation properly #44697
Comments
@Tratcher I think this should be handled in the ExceptionHandlerMiddleware, and maybe in the server implementations as well. |
|
Yep. Is returning 499 something standard? (I'll admit I haven't' seen that status code before). |
Returning 499? There's nothing to return, the request was aborted. What gets logged is the only question. It also depends on if the response has started or not. |
Triage: we can reduce the amount of logging for This would have to happen in multiple places. |
Thanks for contacting us. We're moving this issue to the |
You're right. There's no client to respond to 😄 |
Depending on where in the pipeline this is handled setting the response code to 499 (or some other value) can make for nicer request logging output, as an example of where it might be useful even though there is nothing to respond to. |
@Tratcher @davidfowl I have dig into the code and below are my findings: a) On line no 102 the exception is being logged when the browser close. Lines 94 to 108 in 04bb460
b) When I go to Implementation of this method it goes to the below file on line no 12. With LoggerMessageAttribute it log the error. Just Brainstorming where should I fix this ? LoggerMessageAttribute is in the Runtime Repository
Note: When I go to definition it goes to below file (Autogenerated file): |
The UnhandledException log is not the one that emits the end-of-response data like status code. However, this is where we check HasStarted and decide if we should change the response to a 500. If we first check if the client has already disconnected we could consider changing the status to 499 for OCE's instead. We already have a similar check for BadHttpRequestException. Lines 104 to 122 in 04bb460
|
@Tratcher Do we need to do something like this: if (ex is BadHttpRequestException badHttpRequestException)
{
context.Response.StatusCode = badHttpRequestException.StatusCode;
}
else if (ex is OperationCancelledException operationCancelledException)
{
context.Response.StatusCode = operationCancelledException.StatusCode; //(499)
}
else
{
context.Response.StatusCode = 500;
}
Also in this issue there is requirement of no error logging. But we are logging on line no 102. Please share your views. Lines 94 to 108 in 04bb460
|
@singh733 we'd also want to ensure that the OperationCancelledException was caused by the request aborting and not something else. See the discussion starting at #44697 (comment)
DeveloperExceptionPageMiddleware is only one use case. Also consider, ExceptionHandlerMiddlewareImpl aspnetcore/src/Middleware/Diagnostics/src/ExceptionHandler/ExceptionHandlerMiddlewareImpl.cs Lines 115 to 121 in 04bb460
Each server (Kestrel, HttpSys, IIS) will also have a catch like this: aspnetcore/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs Lines 599 to 602 in 04bb460
|
@Tratcher, There is no OperationCancelledException Exception right now . Should I need to create the OperationCancelledException same as BadHttpRequestException. If yes, then
We need the CancellationToken property because of the upper comment |
You mean there's no OperationCancelledException catch statement now? Yes, you'll need to add those. You won't be working with BadHttpRequestException. |
@Tratcher Below is the proposed Solution for DeveloperExceptionPageMiddlewareImpl Please share your views. try
{
await _next(context);
}
catch (Exception ex)
{
if (ex is OperationCanceledException operationCanceledException && operationCanceledException.CancellationToken == context.RequestAborted)
{
// TODO: Better message
_logger.LogDebug("Request aborted Exception");
}
else
{
_logger.UnhandledException(ex);
}
if (context.Response.HasStarted)
{
_logger.ResponseStartedErrorPageMiddleware();
throw;
}
try
{
context.Response.Clear();
// Preserve the status code that would have been written by the server automatically when a BadHttpRequestException is thrown.
if (ex is BadHttpRequestException badHttpRequestException)
{
context.Response.StatusCode = badHttpRequestException.StatusCode;
}
else if (ex is OperationCanceledException operationCancelException && operationCancelException.CancellationToken == context.RequestAborted)
{
context.Response.StatusCode = 499;
}
else
{
context.Response.StatusCode = 500;
}
await _exceptionHandler(new ErrorContext(context, ex)); |
Does You should |
It doesn't and your suggestion is what we should be using. |
@Tratcher @davidfowl Below is the proposed Solution for DeveloperExceptionPageMiddlewareImpl try
{
await _next(context);
}
catch (Exception ex)
{
if (ex is OperationCanceledException operationCanceledException && operationCanceledException.CancellationToken.IsCancellationRequested)
{
// TODO: Better message
_logger.LogDebug("Request aborted");
context.Response.StatusCode = 499;
return;
} and For ExceptionHandlerMiddlewareImpl private async Task HandleException(HttpContext context, ExceptionDispatchInfo edi)
{
if (edi.SourceException is OperationCanceledException operationCanceledException && operationCanceledException.CancellationToken.IsCancellationRequested)
{
// TODO: Better message
_logger.LogDebug("Request aborted");
context.Response.StatusCode = 499;
return;
}
_logger.UnhandledException(edi.SourceException);
// We can't do anything if the response has already started, just abort.
if (context.Response.HasStarted)
{
_logger.ResponseStartedErrorHandler();
edi.Throw();
}
PathString originalPath = context.Request.Path;
if (_options.ExceptionHandlingPath.HasValue)
{
context.Request.Path = _options.ExceptionHandlingPath;
}
try
{ c) OperationCanceledException is already handled in kestrel. Do we need to make the following changes ? d) I didn't see any ConnectionAbortedException(TaskCanceledException) in httpsys project. Would you please let me know in which files I need to change in HttpSys and IIS server ? e) I have seen ConnectionAbortedException(TaskCanceledException) at many places. Do we need to take action on any other place. |
I think you missed the point here.
Always check
That's a ConnectionAbortedException and shouldn't be changed (what's it's log level?). What about OperationCanceledException's thrown by CancellationTokens? All the servers would need a specific check for OperationCanceledException that looks the same as ExceptionHandlerMiddlewareImpl. |
Log Level is Debug b) Just Curious Do we need to handle both ConnectionAbortedException and OperationCanceledException, because ConnectionAbortedException is already inheriting OperationCanceledException. If yes, Then we need to handle OperationCanceledException after ConnectionAbortedException. Please share your views |
This feels related to #45086 which is about ConnectionResetExceptions. @Tratcher Do we want to do anything similar for IOExceptions that happen because of a request abort while reading the request body? It seems very similar because the exception is caused by a disconnecting client through no fault of the application. I'm not sure that MVC model binding is really special here. |
The discussion around model binding is about control flow. Model binding is doing IO from Streams which can only report errors by throwing IOExceptions. Since these are expected from Streams, we should have model binding catch them and exit gracefully. We don't want to flow IOExceptions all the way up the stack when we can avoid it, that's expensive and unnecessary. Having a backup check in the various exception handlers for this case isn't a bad idea, it would at least cut down on the log noise. |
Background and Motivation@Tratcher mentions:
Proposed APInamespace Microsoft.AspNetCore.Http;
/// <summary>
/// A collection of constants for
/// <see href="http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml" >HTTP status codes</see >.
/// </summary>
/// <remarks>
/// Descriptions for status codes are available from
/// <see cref="M:Microsoft.AspNetCore.WebUtilities.ReasonPhrases.GetReasonPhrase(Int32)" />.
/// </remarks>
public static class StatusCodes
{
+ public const int Status499ClientClosedRequest = 499;
} Usage ExamplesThis API will mostly be used by framework code, but you could set it yourself so diagnostics from custom middleware see the 499 status code. You'd be unlikely to check if the response already has a 499 status code, since this would only be set automatically fairly late in the pipeline, but it is visible in some places like
Alternative DesignsContinue using RisksPeople could theoretically already be looking for status code 0 instead of 499 in their logs for this case, but it seems unlikely. |
Thank you for submitting this for API review. This will be reviewed by @dotnet/aspnet-api-review at the next meeting of the ASP.NET Core API Review group. Please ensure you take a look at the API review process documentation and ensure that:
|
API Review Notes:
aspnetcore/src/Http/Http.Abstractions/src/StatusCodes.cs Lines 236 to 239 in 105e1b2
API Approved as proposed! namespace Microsoft.AspNetCore.Http;
public static class StatusCodes
{
+ public const int Status499ClientClosedRequest = 499;
} |
Is there an existing issue for this?
Describe the bug
When a browser closes the connection during the active request, asp.net core raises the cancellation.
As a result the log file is full of errors like this:
Similar question: https://stackoverflow.com/questions/69607456/unhandled-taskcancelledexception-when-request-is-aborted-by-client-in-asp-net-co
Expected Behavior
Steps To Reproduce
Run the code similar to this:
Refresh the page several times while waiting.
Exceptions (if any)
No response
.NET Version
6.0.301
Anything else?
No response
The text was updated successfully, but these errors were encountered: