-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
HttpClient throws TaskCanceledException on timeout #21965
Comments
@awithy what version of .NET Core are you using? |
1.1 |
Whether the right design or not, by design OperationCanceledExceptions are thrown for timeouts (and TaskCanceledException is an OperationCanceledException). |
Our team found this unintuitive, but it does seem to be working as designed. Unfortunately, when we hit this, we wasted a bit of time trying to find a source of cancelation. Having to handle this scenario differently from task cancelation is pretty ugly (we created custom handler to manage this). This also seems to be an overuse of cancelation as a concept. Thanks again for thee quick response. |
Seems like by design + there were only 2 customers complains in last 6+ months. |
@karelz adding a note as another customer impacted by this misleading exception :) |
Thanks for info, please upvote also top post (issues can be sorted by it), thanks! |
This is a bad design IMO. There's no way to tell if the request was actually canceled (i.e. the cancellation token passed to |
Yeah, this also threw our team for a loop. There's an entire Stack Overflow thread dedicated to trying to deal with it: https://stackoverflow.com/questions/10547895/how-can-i-tell-when-httpclient-has-timed-out/32230327#32230327 |
I published a workaround a few days ago: https://www.thomaslevesque.com/2018/02/25/better-timeout-handling-with-httpclient/ |
Awesome, thanks. |
Reopening as the interest is now bigger - the StackOverflow thread has now 69 upvotes. cc @dotnet/ncl |
Any news ? Still happen in dotnet core 2.0 |
It is on our list of top problems for post-2.1. It won't be fixed in 2.0/2.1 though. |
We have several options what to do when timeout happens:
I am leaning towards option [2], with discarding original stack of original BTW: The change should be fairly straightforward in |
The top-level exception should always be an HttpRequestException. That is the standard API model for HttpClient. Within that, as inner exception, "timeouts" should probably be a TimeoutException. In fact, if we were to combine this with the other issues (about modifying HttpRequestion to include a new enum property similar to WebExceptionStatus), then developers would only have to check that enum and not have to inspect inner exceptions at all. This is basically option 1) but continuing to preserve current app-compat API model that top-level exceptions from HttpClient APIs are always HttpRequestException. Note: that any "timeouts" from directly reading an HTTP response stream from Stream APIs like: var client = new HttpClient();
Stream responseStream = await client.GetStreamAsync(url);
int bytesRead = await responseStream.ReadAsync(buffer, 0, buffer.Length); should continue to throw System.IO.IOException etc. as top-level exceptions (we actually have a few bugs in SocketsHttpHandler about this). |
@davidsh are you suggesting to throw |
I need to test out the different behaviors of the stacks including .NET Framework to verify the exact current behaviors before I can answer this question. Also, in some cases, some of our stacks are throwing HttpRequestException with inner exception of OperationCanceledException. TaskCanceledException is a derived type of OperationCanceledException.
We also need clear definition of what "explicit" is. For example, is setting the HttpClient.Timeout property "explicit"? I think we're saying no. What about calling .Cancel() on the CancellationTokenSource object (which affects the passed in CancellationToken object)? Is that "explicit"? There are probably other examples we can think of. |
.NET Framework also throws TaskCanceledException when you set |
If you can't distinguish between cancellation from client and cancellation from implementation - just check both. Before using client token create derived one: var clientToken = ....
var innerTokenSource = CancellationTokenSource.CreateLinkedTokenSource(clientToken); If at some point you catch OperationCancelledException: try
{
//invocation with innerToken
}
catch(OperationCancelledException oce)
{
if(clientToken.IsCancellationRequested)
throw; //as is, it is client initiated and in higher priority
if(innerToken.IsCancellationRequested)
//wrap it in HttpException, TimeoutException, whatever, wrap it in another linked token until you process all cases.
} Basically you filter all possible layers from client token to deepest one, until you find token which caused cancellation. |
Any updates on this making 2.2 or 3.0? @karelz @davidsh, cc @NickCraver . I kind of wish it would just do option 1) of throwing a TimeoutException, or alternatively a new HttpTimeoutException that is derived from HttpRequestException. Thanks! |
Too late for 2.2, it was released last week ;) |
Argh, that's what happens when I take a break ;) |
Albeit a little goofy, the most simplistic workaround I've found is to nest a catch handler for OperationCanceledException and then evaluate the value of IsCancellationRequested to determine whether or not the cancellation token was the result of a timeout or an actual session abortion. Clearly the logic performing the API call would likely be in a business logic or service layer but I've consolidated the example for simplicity: [HttpGet]
public async Task<IActionResult> Index(CancellationToken cancellationToken)
{
try
{
//This would normally be done in a business logic or service layer rather than in the controller itself but I'm illustrating the point here for simplicity sake
try
{
//Using HttpClientFactory for HttpClient instances
var httpClient = _httpClientFactory.CreateClient();
//Set an absurd timeout to illustrate the point
httpClient.Timeout = new TimeSpan(0, 0, 0, 0, 1);
//Perform call that requires special timeout logic
var httpResponse = await httpClient.GetAsync("https://someurl.com/api/long/running");
//... (if GetAsync doesn't fail, handle the response as desired)
}
catch (OperationCanceledException innerOperationCanceled)
{
//If a canceled token exception occurs due to a timeout, "IsCancellationRequested" should be false
if (cancellationToken.IsCancellationRequested)
{
//Bubble exception to global handler
throw innerOperationCanceled;
}
else
{
//... (perform timeout logic here)
}
}
}
catch (OperationCanceledException operationCanceledEx)
{
_logger.LogWarning(operationCanceledEx, "Request was aborted by the end user.");
return new StatusCodeResult(499);
}
catch (Exception ex)
{
_logger.LogError(ex, "An unexepcted error occured.");
return new StatusCodeResult(500);
}
} I've seen other articles about how to handle this scenario more elegantly but they all require digging into the pipeline etc. I realize that this approach isn't perfect by my hope is that there will be better support for actual timeout exceptions with a future release. |
Working with http client is utterly abysmal. HttpClient needs deleted and started over from scratch. |
It's absolutely bonkers we need to write this atrocious code. HttpClient is the leakiest abstraction ever created by Microsoft |
I agree that it seems rather short sighted to not include more specific exception information around timeouts. Seems like a pretty basic thing for people to want to handle actual timeouts differently than cancelled token exceptions. |
That's not very helpful or constructive feedback @dotnetchris . Customers and MSFT employees alike are all here to try and make things better, and that's why folks like @karelz are still here listening to customer feedback and trying to improve. There is no requirement for them to do development in the open, and let's not burn their good will with negativity or they could decide to take their ball and go home, and that would be worse for the community. Thanks! :) |
Although I don't necessarily disagree with the wrapping option, I think it's worth pointing out that - as far as I've understood - the issue is not that the derived option is a breaking change, but that the wrapping one is simpler. As @stephentoub mentioned earlier in the discussion, throwing a derived exception is not considered a breaking change by the corefx guidelines. |
Deriving HttpTimeoutException from TaskCanceledException violates “is a” relationship between these two exceptions. It will still make calling code to think that cancellation happened, unless it specifically handles HttpTimeoutException. This is basically the same handling that we have to do at the moment when we check for cancellation token being canceled to determine whether it’s a timeout or not. Besides, having two timeout exceptions in core library is confusing. I think the only sensible solution is to change exception thrown on timeout to TimeoutException. Yes, it will be a breaking change, but the issue already spans several major releases and breaking changes is what major releases are for. |
Can the documentation for existing versions be updated to indicate that methods could throw |
@qidydl that's the plan... will fully document any ways timeouts can manifest and how to detail with them. |
Hopefully the last attempt to fix the weird cancellation bug [1][2] (that currently doesn't crash the app anymore, but generates a warning and marks the balance with '?' to mean 'unknown'). This change makes it so that there's independent cancellationTokenSources for each job, instead of having one shared across the FaultTPClient.Query work, because I suspected that one job running HttpClient and facing the TCE (to denote a timeout) could make the other jobs be cancelled as well. [1] https://gitlab.com/knocte/geewallet/issues/125 [2] https://github.com/dotnet/corefx/issues/20296 ?
I think I just found the culprit for the deadly TCE problem I've been having for the past few weeks/months: https://gitlab.com/knocte/geewallet/issues/125 The root cause might have been this .NETCore's BCL bug: [1]. However, after working on a workaround (which I committed here[2]), I was still getting cancellation[3] in code that wasn't supposed to cancel at all (at least from an external cancellationSource, since this only is supposed to happen when the user sends the app to the background while it is in the middle of refreshing balances; unlike what happens with the first retreival of balances on the Loading page). After committing the workaround, I started ironing out issues but which were just derived from the new workaround approach, while still overlooking the real root cause, which I just realised is the following: balance queries in the Ether world (i.e. ETH, ETC, DAI) need sometimes 2 distinct queries (not just one), to retreived both the confirmed and the unconfirmed balance. This was the reason why I was only reproducing this problem in this kind of currencies (which led me to believe the real culprit was the HttpClient bug[1], because the UTXO-based ones don't use HttpClient...). And another reason why this was hard to reproduce is because wallets that already have cached balances used them as a reference point (against unconfirmed balance amount) instead of querying for both unconfirmed&confirmed balances, so most people would reproduce this only the first time they used geewallet. The fix (see the diff of this commit) might look too simple, but it would have not been possible without the change in [2] (and especially, [4]). It basically consists of avoiding to dispose(or cancel) the cancelSource from within FaultTolerantParallelClient itself, and only let external code do it. This way, finishing a confirmed-balance-query would not cancel an unconfirmed-balance-query for the same account, and viceversa. I was actually going to commit this change just to dismiss this issue altogether and hide the shit under the rug, but the only thing that needed to be done besides the change is *UNDERSTAND* why it's needed, hence this long commit message. [1] https://github.com/dotnet/corefx/issues/20296 [2] 54b2a36 [3] e.g. https://sentry.io/organizations/nblockchain/issues/1353874620 [4] ab586a6
…on when request times out (#2281) Currently, HttpClient throws the same TaskCancellationException regardless of the request cancellation reason that can be caller's cancellation, all pending request cancellation or timeout. This makes it impossible to handle a request timeout in a way different from all other cases (e.g. special retry logic). This PR adds a timeout detection logic into HttpClient. It watches for all TaskCancelledExceptions and catches the one caused by timeout. Then, it creates two new exceptions and build a hierarchy. The first is a TimeoutException having its InnerException set to the original TaskCancelledException. The second is a new TaskCancelledException having its InnerException set to that new TimeoutException, but preserving the original stack trace, message and cancellation token. Finally, this top-level TaskCancelledException gets thrown. Fixes #21965
Thanks @alnikola and @scalablecory and @davidsh and @karelz and everyone else involved in this discussion/implementation, I really appreciate it. |
Don't see a Timeout exception wrapped inside TaskCanceledException on PostAsync() call, it is returning under aggregate exception - .net version 4.5 |
This discussion his not relevant to .NET Framework @adydeshmukh. That is completely different implementation. |
HttpClient is throwing a TaskCanceledException on timeout in some circumstances. This is happening for us when the server is under heavy load. We were able to work around by increasing the default timeout. The following MSDN forum thread captures the essence of the experienced issue: https://social.msdn.microsoft.com/Forums/en-US/d8d87789-0ac9-4294-84a0-91c9fa27e353/bug-in-httpclientgetasync-should-throw-webexception-not-taskcanceledexception?forum=netfxnetcom&prof=required
Thanks
The text was updated successfully, but these errors were encountered: