Bug 44673 - AndroidClientHandler ignores Timeout value and CancellationToken
Summary: AndroidClientHandler ignores Timeout value and CancellationToken
Alias: None
Product: Android
Classification: Xamarin
Component: General ()
Version: 7.0 (C8)
Hardware: PC Windows
: --- normal
Target Milestone: ---
Assignee: Jonathan Pryor
Depends on:
Reported: 2016-09-23 09:44 UTC by Leon
Modified: 2017-04-04 18:51 UTC (History)
7 users (show)

Is this bug a regression?: ---
Last known good build:

Notice (2018-05-24): bugzilla.xamarin.com is now in read-only mode.

Please join us on Visual Studio Developer Community and in the Xamarin and Mono organizations on GitHub to continue tracking issues. Bugzilla will remain available for reference in read-only mode. We will continue to work on open Bugzilla bugs, copy them to the new locations as needed for follow-up, and add the new items under Related Links.

Our sincere thanks to everyone who has contributed on this bug tracker over the years. Thanks also for your understanding as we make these adjustments and improvements for the future.

Please create a new report on Developer Community or GitHub with your current version information, steps to reproduce, and relevant error messages or log files if you are hitting an issue that looks similar to this resolved bug and you do not yet see a matching new report.

Related Links:

Description Leon 2016-09-23 09:44:24 UTC
Currently I have a HttpClient with a timeout defined like this:

var httpClient = new HttpClient(new Xamarin.Android.Net.AndroidClientHandler());
httpClient.Timeout = TimeSpan.FromMilliseconds(10000);
ByteArrayContent content = new ByteArrayContent(postData);
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
HttpResponseMessage response = await httpClient.PostAsync(Server + CallPath, content);

While this code works fine when using the HttpClientHandler (it stops and generates a expected timeout exception after 10 seconds), whenever I'm using the AndroidClientHandler the request times out after about 60 seconds (as a WebException with a Java.Net.ConnectException inner exception), no matter the timeout value.

I tried migrating to a CancellationToken implementation as shown below:

CancellationTokenSource timeoutCts = new CancellationTokenSource();

var httpClient = new HttpClient(new Xamarin.Android.Net.AndroidClientHandler());
//... Setup headers and content
HttpResponseMessage response = await httpClient.PostAsync(Server + CallPath, content,timeoutCts.Token);

While this works when using HttpClientHandler (the PostAsync times out with an expected OperationCanceledException), the CancellationToken is ignored when using the AndroidClientHandler and as with the non-token implementation the PostAsync times out after about 60 seconds with the same WebException.
Comment 1 Sam 2016-10-05 12:47:58 UTC
I can confirm that the AndroidClientHandler ignores the TimeOut setting, although it doesn't seem to happen all the time.
Comment 2 David Petrík 2016-10-06 12:10:18 UTC
I looked into your code

and i can confirm that CancelationToken is only declared in DoProcessRequest as input parameter and never used for cancelling long running requests.
Comment 3 Marek Habersack 2016-10-10 11:58:48 UTC
This is fixed in Xamarin.Android/master, commit 5055a209de3f782468e0b0f1e7526e32ec2bd3bd
Comment 4 David Petrík 2016-10-10 12:31:42 UTC
Hi Marek,
coul'd you check this code? I not study it deeply :-)

public AndroidHttpResponseMessage (URL javaUrl, HttpURLConnection httpConnection) {
			javaUrl = javaUrl;
			httpConnection = httpConnection;

We added your code directly into our project and we received this warning

Warning	CS1717	Assignment made to same variable; did you mean to assign something else?	

Thank you for above fix

Comment 5 David Petrík 2016-10-12 11:41:34 UTC
i created new request in Bugzilla for previous comment


Comment 6 Leon 2016-11-29 10:40:29 UTC
The fix still has a few issues.
The handler will not cancel properly if it was cancelled during a await call in the DoProcessRequest. Which might be an issue on slow connections or in case of timeouts.

Example scenario:

1. App makes request and sends along cancellation token to the handler.

2. Eventually processing reaches following bit of code at line 248:
await httpConnection.ConnectAsync().ConfigureAwait(false);

3. Since it's a slow connection the user decides to cancel the request. App sets Cancellationtoken's IsCancellationRequested to true.

4. The Task doesn't actually cancel until the ConnectAsync finishes or until the request times out (for which the timeout value in HttpClient is still ignored).
In case of a timeout it will also throw a WebException instead of the expected TaskCanceledException, causing a issue in the expected app flow.
Comment 7 Leon 2016-11-29 13:32:13 UTC
Would something like 

await Task.WhenAny(httpConnection.ConnectAsync(),Task.Run(()=> { cancellationToken.WaitHandle.WaitOne(); })).ConfigureAwait(false);

work to solve this issue, or are there major caveats to this approach?
Comment 8 Sam 2016-11-29 13:36:18 UTC
This would help, but it is not a definite fix, because this is only the init part of the call:
await httpConnection.ConnectAsync ().ConfigureAwait (false);

The call itself happens on this line:
var statusCode = await Task.Run (() => (HttpStatusCode)httpConnection.ResponseCode).ConfigureAwait (false);
Comment 9 Leon 2016-11-29 16:07:28 UTC
Judging by network activity most of the call takes place in the CopyToAsync method. By using the ReadAsStreamAsync method the content can be exposed as a Stream. The CopyToAsync method for streams does support CancellationTokens, solving that part of the process as well:

 if (httpConnection.DoOutput) {
   using (var stream = await request.Content.ReadAsStreamAsync())
       await stream.CopyToAsync(httpConnection.OutputStream, 4096, cancellationToken).ConfigureAwait(false);
Comment 10 David Petrík 2017-01-10 15:05:01 UTC
we tried your last implementaion from GitHub and we have also problem with this exception

System.ObjectDisposedExceptionThe CancellationTokenSource has been disposed.

This never happened before
Comment 11 Jonathan Pryor 2017-03-23 13:01:04 UTC
I believe that this was fixed in:


The fix is part of the Xamarin.Android 7.2 release, currently in the beta channel.
Comment 12 Jeremy Cook 2017-04-04 18:51:04 UTC
The latest changes in Android 7.2 release still do not work if using a network that requires a proxy to be configured, and that proxy is not configured correctly.  In other words, all HTTP connect calls simply hang on the Connect() call.  In this case, I do see the httpConnection.Disconnect() call made when the token is cancelled, but that has no effect.   The timeout in this case defaults to a 2 minute timeout, which must be the default for HttpUrlConnection.

The only way I am able to get around this is to specify a ConnectTimeout to the HttpURLConnection in AndroidClientHandler's SendAsync() method.  For example (around line 210 of AndroidClientHandler.cs):
            while (true)
                URL java_url = new URL(EncodeUrl(redirectState.NewUrl));
                URLConnection java_connection = java_url.OpenConnection();
                java_connection.ConnectTimeout = 10000; // 10 seconds
                HttpURLConnection httpConnection = await SetupRequestInternal(request, java_connection).ConfigureAwait(continueOnCapturedContext: false); ;
                HttpResponseMessage response = await ProcessRequest(request, java_url, httpConnection, cancellationToken, redirectState).ConfigureAwait(continueOnCapturedContext: false); ;

It would be nice to see this added to AndroidClientHandler.   Even better would be for HttpClient to pass in its default timeout into the handler so it can set the ConnectTimeout using that.