Thanks to visit codestin.com
Credit goes to github.com

Skip to content

HttpClient async calls not throwing TaskCanceledException on Android when token canceled #99568

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

Open
mdemler opened this issue Mar 12, 2024 · 12 comments

Comments

@mdemler
Copy link

mdemler commented Mar 12, 2024

Description

When running on Windows, TaskCanceledException is being thrown by async HttpClient calls when the timeout elapses. When running the same code under Android, receiving a WebException instead.

Reproduction Steps

  1. Create a new Maui app project using VS2022 and .NET 8.
  2. Paste the below provided code anywhere within the program that executes.
  3. Run the program on Windows.
  4. Notice a TaskCanceledException is caught.
  5. Run the program on Android.
  6. Notice that the general Exception handler triggers.

`
using var client = new HttpClient();

        using var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(5));

        try
        {
            var response = await client.GetAsync("https://httpbin.org/delay/10", cancellationTokenSource.Token);
        }
        catch (TaskCanceledException)
        {
            // This is what we expect
        }
        catch (Exception exception)
        {
            // This is where we get on Android
        }

`

Expected behavior

Behavior is identical between Windows and Android. TaskCanceledException is thrown for both.

Actual behavior

WebException is thrown when a timeout is reached.

{System.Net.WebException: Socket closed ---> Java.Net.SocketException: Socket closed at Java.Interop.JniEnvironment.InstanceMethods.CallIntMethod(JniObjectReference instance, JniMethodInfo method, JniArgumentValue* args) in /Users/runner/work/1/s/xamarin-android/external/Java.Interop/src/Java.Interop/obj/Release/net7.0/JniEnvironment.g.cs:line 20203 at Java.Interop.JniPeerMembers.JniInstanceMethods.InvokeVirtualInt32Method(String encodedMember, IJavaPeerable self, JniArgumentValue* parameters) in /Users/runner/work/1/s/xamarin-android/external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods_Invoke.cs:line 511 at Java.Net.HttpURLConnection.get_ResponseCode() in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/obj/Release/net8.0/android-34/mcw/Java.Net.HttpURLConnection.cs:line 521 at Xamarin.Android.Net.AndroidMessageHandler.<>c__DisplayClass136_0.<DoProcessRequest>b__2() in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs:line 625 at System.Threading.Tasks.Task1[[System.Net.HttpStatusCode, System.Net.Primitives, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]].InnerInvoke()
at System.Threading.Tasks.Task.<>c.<.cctor>b__281_0(Object obj)
at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location ---
at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread)
--- End of stack trace from previous location ---
at Xamarin.Android.Net.AndroidMessageHandler.DoProcessRequest(HttpRequestMessage request, URL javaUrl, HttpURLConnection httpConnection, CancellationToken cancellationToken, RequestRedirectionState redirectState) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs:line 625
at Xamarin.Android.Net.AndroidMessageHandler.DoSendAsync(HttpRequestMessage request, CancellationToken cancellationToken) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs:line 456
--- End of managed Java.Net.SocketException stack trace ---
java.net.SocketException: Socket closed
at java.net.SocketInputStream.read(SocketInputStream.java:188)
at java.net.SocketInputStream.read(SocketInputStream.java:143)
at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.readFromSocket(ConscryptEngineSocket.java:945)
at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.processDataFromSocket(ConscryptEngineSocket.java:909)
at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.readUntilDataAvailable(ConscryptEngineSocket.java:824)
at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.read(ConscryptEngineSocket.java:797)
at com.android.okhttp.okio.Okio$2.read(Okio.java:138)
at com.android.okhttp.okio.AsyncTimeout$2.read(AsyncTimeout.java:213)
at com.android.okhttp.okio.RealBufferedSource.indexOf(RealBufferedSource.java:307)
at com.android.okhttp.okio.RealBufferedSource.indexOf(RealBufferedSource.java:301)
at com.android.okhttp.okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.java:197)
at com.android.okhttp.internal.http.Http1xStream.readResponse(Http1xStream.java:188)
at com.android.okhttp.internal.http.Http1xStream.readResponseHeaders(Http1xStream.java:129)
at com.android.okhttp.internal.http.HttpEngine.readNetworkResponse(HttpEngine.java:750)
at com.android.okhttp.internal.http.HttpEngine.readResponse(HttpEngine.java:622)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:475)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:411)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:542)
at com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.getResponseCode(DelegatingHttpsURLConnection.java:106)
at com.android.okhttp.internal.huc.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:30)

--- End of managed Java.Net.SocketException stack trace ---
java.net.SocketException: Socket closed
at java.net.SocketInputStream.read(SocketInputStream.java:188)
at java.net.SocketInputStream.read(SocketInputStream.java:143)
at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.readFromSocket(ConscryptEngineSocket.java:945)
at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.processDataFromSocket(ConscryptEngineSocket.java:909)
at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.readUntilDataAvailable(ConscryptEngineSocket.java:824)
at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.read(ConscryptEngineSocket.java:797)
at com.android.okhttp.okio.Okio$2.read(Okio.java:138)
at com.android.okhttp.okio.AsyncTimeout$2.read(AsyncTimeout.java:213)
at com.android.okhttp.okio.RealBufferedSource.indexOf(RealBufferedSource.java:307)
at com.android.okhttp.okio.RealBufferedSource.indexOf(RealBufferedSource.java:301)
at com.android.okhttp.okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.java:197)
at com.android.okhttp.internal.http.Http1xStream.readResponse(Http1xStream.java:188)
at com.android.okhttp.internal.http.Http1xStream.readResponseHeaders(Http1xStream.java:129)
at com.android.okhttp.internal.http.HttpEngine.readNetworkResponse(HttpEngine.java:750)
at com.android.okhttp.internal.http.HttpEngine.readResponse(HttpEngine.java:622)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:475)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:411)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:542)
at com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.getResponseCode(DelegatingHttpsURLConnection.java:106)
at com.android.okhttp.internal.huc.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:30)

--- End of inner exception stack trace ---
at Xamarin.Android.Net.AndroidMessageHandler.DoSendAsync(HttpRequestMessage request, CancellationToken cancellationToken) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs:line 471
at System.Net.Http.HttpClient.g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
at MauiApp4.MainPage.RunTestAsync() in C:\Projects\Temp\MauiApp4\MauiApp4\MainPage.xaml.cs:line 41}
`

Regression?

Not sure. This is new code.

Known Workarounds

Unknown. Defeats the ability to use Polly to retry on a timeout.

Configuration

.NET 8.0.200

Windows 11.

Device: SAMSUNG ‎A9+, Android 13. Same behavior on Emulator running Android 13.

Other information

No response

@stephentoub
Copy link
Member

When running the same code under Android, receiving a WebException instead.

That's confusing to me, as WebException is part of System.Net.Requests, which HttpClient in .NET Core doesn't use at all; WebRequest is built on top of HttpClient, not the other way around.

Can you share the full stack trace from the exception you're receiving?

Is it possible you're actually running on the old mono (https://github.com/mono/mono) rather than on .NET 8?

@stephentoub
Copy link
Member

Ah, this might be coming from the Mono.Android HttpClientHandler that gets substituted in on Android (and which is not part of this repo). @steveisok ?

@steveisok
Copy link
Member

@simonrozsival, is that expected behavior from the Android handler?

@mdemler
Copy link
Author

mdemler commented Mar 12, 2024

When running the same code under Android, receiving a WebException instead.

That's confusing to me, as WebException is part of System.Net.Requests, which HttpClient in .NET Core doesn't use at all; WebRequest is built on top of HttpClient, not the other way around.

Can you share the full stack trace from the exception you're receiving?

Is it possible you're actually running on the old mono (https://github.com/mono/mono) rather than on .NET 8?

Updated my posting for that. Sorry, should have included that to begin with.

@steveisok
Copy link
Member

@mdemler if you put <UseNativeHttpHandler>false</UseNativeHttpHandler> in your project, that'll use SocketsHttpHandler and you should have similar behavior as you do on Windows.

@mdemler
Copy link
Author

mdemler commented Mar 12, 2024

Thanks, @steveisok! I've done that, and that will work for us for what we're trying to handle now. It would be nice if this worked as expected even when using the native handler though. Thanks again.

@simonrozsival
Copy link
Member

It's a known issue: dotnet/android#5761

@grendello do you know why we haven't changed the exceptions to HttpRequestException yet?

@grendello
Copy link
Contributor

@simonrozsival IIRC it was backward compatibility with classic. Shouldn't be an issue anymore

@divil5000
Copy link

The right time to change this behaviour was when going from Xamarin to Core. It's shameful that this wasn't done then. My other issue, linked above, was opened more than three years ago and nobody seems to care that impossible exceptions are being thrown from within the framework.

false might be a resolution or it might not. It's disingenuous to suggest that without also documenting exactly the benefits and drawbacks of moving away from Android's native HTTP stack.

@RSReswin
Copy link

RSReswin commented Apr 7, 2025

private async void OnInternetConnectionSuccess()
    {
        try
        {
            using var httpClient = new HttpClient();
            httpClient.Timeout = TimeSpan.FromSeconds(5);
            var reply = await httpClient.GetAsync($"{AppSettings.ApiSettings.ConnectionUrl}/health");
            if (!reply.IsSuccessStatusCode)
            {
                OnFailedToConnectWithServer?.Invoke();
                return;
            }

            var result = await _apiService.GetClientAppConfig();
            string appVersion = AppInfo.Current.VersionString;
            string configAppVersion =
                result.VersionSettings.ImportantUpdateVersion.ToString(CultureInfo.InvariantCulture);

            if (!appVersion.Equals(configAppVersion))
            {
                OnImportantUpdateAvailable?.Invoke();
                return;
            }

            OnUpdateNotAvailable?.Invoke();
        }
        catch (HttpException e)
        {
            _logger.LogError("AuthenticationViewModel Error : {message}", e.Message);
        }
        catch (TaskCanceledException e)
        {
            _logger.LogError("AuthenticationViewModel Error : {message}", e.Message);
        }
        catch (Exception e)
        {
            _logger.LogError("AuthenticationViewModel Error : {message}", e.Message);
            OnFailedToConnectWithServer?.Invoke();
        }
    }

    private void OnInternetConnectionFailed()
    {
        OnFailedToConnectWithInternet?.Invoke();
    }
var reply = await httpClient.GetAsync($"{AppSettings.ApiSettings.ConnectionUrl}/health");

when i debug the task was cancelling in this line without throwing any errors why ? have any idea.
all httpClients happing same behavior including Refit Client and Ping()

@RSReswin
Copy link

RSReswin commented Apr 7, 2025

2025-04-07.17-57-14.mp4

i have this same issue in both windows and android, console application and maui application.

and maui application was not crashing or close noting happening when this issue happen, it running normally.

@RSReswin
Copy link

RSReswin commented Apr 7, 2025

just now i saw the task was waiting until 1:45Min and after throwing exception.

i try using httpClient timeout, SocketsHttpHandler timeout, cancellationTokenSource timeout and Ping() class all are behave same anyone know why ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants