anaisbetts / ModernHttpClient

HttpClient implementations that use platform-native HTTP clients for :rocket:
MIT License
657 stars 260 forks source link

Android request not canceled/canceleable #184

Open molinch opened 9 years ago

molinch commented 9 years ago

On Android if ModernHttpClient GetAsync method is called with HttpCompletionOption.ResponseHeadersRead and a CancelationToken is specified then the token status is never checked while connecting/retrieving headers.

Currently if you have a token that cancels after 15 seconds and httpClient cannot connect to the website/get the headers in these 15 seconds, it will continue until it miserably fails.

Example:

var cancelReadTokenSource = new CancellationTokenSource();
cancelReadTokenSource.CancelAfter(TimeSpan.FromSeconds(readTimeout));
var response = await httpClient.GetAsync("http://notexisting.com/notexisting.jpg", HttpCompletionOption.ResponseHeadersRead, cancelReadTokenSource.Token).ConfigureAwait(false);

Request will not stop after 15 seconds. Instead it takes a while and at some point throws a ConnectException. Please note that even the HttpClient.Timeout is not taken into consideration (on my side it was set to 30s), it just throw at 1 mn.

Stacktrace:

Java.Net.ConnectException: Exception of type 'Java.Net.ConnectException' was thrown.
  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000b] in /Users/builder/data/lanes/2058/58099c53/source/mono/mcs/class/corlib/System.Runtime.ExceptionServices/ExceptionDispatchInfo.cs:61 
  at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) [0x00047] in /Users/builder/data/lanes/2058/58099c53/source/mono/external/referencesource/mscorlib/system/runtime/compilerservices/TaskAwaiter.cs:201 
  at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) [0x0002e] in /Users/builder/data/lanes/2058/58099c53/source/mono/external/referencesource/mscorlib/system/runtime/compilerservices/TaskAwaiter.cs:170 
  at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) [0x0000b] in /Users/builder/data/lanes/2058/58099c53/source/mono/external/referencesource/mscorlib/system/runtime/compilerservices/TaskAwaiter.cs:142 
  at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1+ConfiguredTaskAwaiter[OkHttp.Response].GetResult () [0x00000] in /Users/builder/data/lanes/2058/58099c53/source/mono/external/referencesource/mscorlib/system/runtime/compilerservices/TaskAwaiter.cs:580 
  at ModernHttpClient.NativeMessageHandler+<SendAsync>c__async0.MoveNext () [0x002a5] in /Users/paul/code/paulcbetts/modernhttpclient/src/ModernHttpClient/Android/OkHttpNetworkHandler.cs:108 
--- End of stack trace from previous location where exception was thrown ---
  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000b] in /Users/builder/data/lanes/2058/58099c53/source/mono/mcs/class/corlib/System.Runtime.ExceptionServices/ExceptionDispatchInfo.cs:61 
  at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) [0x00047] in /Users/builder/data/lanes/2058/58099c53/source/mono/external/referencesource/mscorlib/system/runtime/compilerservices/TaskAwaiter.cs:201 
  at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) [0x0002e] in /Users/builder/data/lanes/2058/58099c53/source/mono/external/referencesource/mscorlib/system/runtime/compilerservices/TaskAwaiter.cs:170 
  at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) [0x0000b] in /Users/builder/data/lanes/2058/58099c53/source/mono/external/referencesource/mscorlib/system/runtime/compilerservices/TaskAwaiter.cs:142 
  at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1+ConfiguredTaskAwaiter[FFImageLoading.Work.WithLoadingResult`1[System.IO.Stream]].GetResult () [0x00000] in /Users/builder/data/lanes/2058/58099c53/source/mono/external/referencesource/mscorlib/system/runtime/compilerservices/TaskAwaiter.cs:580 
  at FFImageLoading.Work.ImageLoaderTask+<GetStreamAsync>c__async5.MoveNext () [0x00082] in /Users/molinef/Projects/ecat/ecat.solution/externals/HGR.Mobile/externals/ImageLoading/FFImageLoading.Droid/Work/ImageLoaderTask.cs:486 
  --- End of managed exception stack trace ---
java.net.ConnectException: failed to connect to notexisting.com/91.233.105.125 (port 80): connect failed: ETIMEDOUT (Connection timed out)
    at libcore.io.IoBridge.connect(IoBridge.java:114)
    at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:192)
    at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:459)
    at java.net.Socket.connect(Socket.java:842)
    at com.squareup.okhttp.internal.Platform$Android.connectSocket(Platform.java:220)
    at com.squareup.okhttp.Connection.connect(Connection.java:148)
    at com.squareup.okhttp.OkHttpClient$1.connect(OkHttpClient.java:84)
    at com.squareup.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:321)
    at com.squareup.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:241)
    at com.squareup.okhttp.Call.getResponse(Call.java:198)
    at com.squareup.okhttp.Call.access$200(Call.java:36)
    at com.squareup.okhttp.Call$AsyncCall.execute(Call.java:143)
    at com.squareup.okhttp.internal.NamedRunnable.run(NamedRunnable.java:33)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
    at java.lang.Thread.run(Thread.java:841)
Caused by: libcore.io.ErrnoException: connect failed: ETIMEDOUT (Connection timed out)
    at libcore.io.Posix.connect(Native Method)
    at libcore.io.BlockGuardOs.connect(BlockGuardOs.java:85)
    at libcore.io.IoBridge.connectErrno(IoBridge.java:127)
    at libcore.io.IoBridge.connect(IoBridge.java:112)
    ... 15 more

The interesting part is that it will even block other requests until this one fails. Really strange.

molinch commented 9 years ago

In fact it's even more weird: the standard GetAsync() is also affected

var cancelReadTokenSource = new CancellationTokenSource();
cancelReadTokenSource.CancelAfter(TimeSpan.FromSeconds(15));
var response = await httpClient.GetAsync("http://notexisting.com/notexisting.jpg", cancelReadTokenSource.Token).ConfigureAwait(false);

It won't stop at 15 seconds, instead it will wait 1mn. The worst part is that all other requests are blocked until this one fails...

Cheesebaron commented 9 years ago

Are you awaiting the task?

molinch commented 9 years ago

Of course :) I updated the code to reflect that. In fact it's the same code on iOS, there it works perfectly fine. It's really just on Android that it behaves weirdly.

molinch commented 9 years ago

I'm pretty sure it's related to that https://github.com/square/okhttp/issues/1792#issuecomment-131018682 but my knowledge stops here...

molinch commented 9 years ago

Using Mono HttpClient this issue doesn't happen. It errors in time and doesn't cause any other request to be blocked.

Mono implementation outputs this in the console log:

Generating/retrieving image: http://notexisting.com/notexisting.jpg
[] _wapi_connect: error looking up socket handle 0x3b