dotnet / android

.NET for Android provides open-source bindings of the Android SDK for use with .NET managed languages such as C#
MIT License
1.93k stars 531 forks source link

NetworkOnMainThreadException thrown when EnsureSuccessStatusCode is called #4691

Closed Flash3001 closed 4 years ago

Flash3001 commented 4 years ago

Steps to Reproduce

  1. Send a new Http request using SendAsync with the HttpCompletionOption.ResponseHeadersRead option.
  2. The server response needs to be a non-2xx code with a big payload (don't know how big, we tested with 5MB, a few bytes won't cause the issue)
  3. Call HttpResponseMessage.EnsureSuccessStatusCode() from the main thread.

Expected Behavior

A HttpRequestException should be thrown.

Actual Behavior

A Android.OS.NetworkOnMainThreadException is thrown.

Version Information

Mono Framework MDK Runtime: Mono 6.8.0.123 (2019-10/1d0d939dc30) (64-bit) Package version: 608000123

Xamarin.Android Version: 10.2.0.100 (Visual Studio Community) Commit: xamarin-android/d16-5/988c811

Log File

Sample of of a stack trace:

Error in application
Exception of type 'Android.OS.NetworkOnMainThreadException' was thrown.
  at Java.Interop.JniEnvironment+InstanceMethods.CallVoidMethod (Java.Interop.JniObjectReference instance, Java.Interop.JniMethodInfo method, Java.Interop.JniArgumentValue* args) [0x0006e] in <26521a5118b44c858c385715922b9d5d>:0 
  at Java.Interop.JniPeerMembers+JniInstanceMethods.InvokeVirtualVoidMethod (System.String encodedMember, Java.Interop.IJavaPeerable self, Java.Interop.JniArgumentValue* parameters) [0x0002a] in <26521a5118b44c858c385715922b9d5d>:0 
  at Java.IO.InputStream.Close () [0x0000a] in <d3b924763d4a465c85b26f6e8edc8a53>:0 
  at Android.Runtime.InputStreamInvoker.Close () [0x00006] in <d3b924763d4a465c85b26f6e8edc8a53>:0 
  at System.IO.Stream.Dispose () [0x00000] in /Users/builder/jenkins/workspace/archive-mono/2019-10/android/release/external/corert/src/System.Private.CoreLib/shared/System/IO/Stream.cs:236 
  at (wrapper remoting-invoke-with-check) System.IO.Stream.Dispose()
  at System.IO.BufferedStream.Dispose (System.Boolean disposing) [0x00013] in /Users/builder/jenkins/workspace/archive-mono/2019-10/android/release/external/corefx/src/System.Runtime.Extensions/src/System/IO/BufferedStream.cs:297 
  at System.IO.Stream.Close () [0x00000] in /Users/builder/jenkins/workspace/archive-mono/2019-10/android/release/external/corert/src/System.Private.CoreLib/shared/System/IO/Stream.cs:230 
  at System.IO.Stream.Dispose () [0x00000] in /Users/builder/jenkins/workspace/archive-mono/2019-10/android/release/external/corert/src/System.Private.CoreLib/shared/System/IO/Stream.cs:236 
  at (wrapper remoting-invoke-with-check) System.IO.Stream.Dispose()
  at System.Net.Http.StreamContent.Dispose (System.Boolean disposing) [0x00003] in /Users/builder/jenkins/workspace/archive-mono/2019-10/android/release/external/corefx/src/System.Net.Http/src/System/Net/Http/StreamContent.cs:82 
  at System.Net.Http.HttpContent.Dispose () [0x00000] in /Users/builder/jenkins/workspace/archive-mono/2019-10/android/release/external/corefx/src/System.Net.Http/src/System/Net/Http/HttpContent.cs:538 
  at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode () [0x00010] in /Users/builder/jenkins/workspace/archive-mono/2019-10/android/release/external/corefx/src/System.Net.Http/src/System/Net/Http/HttpResponseMessage.cs:165 
  at FishAngler.Core.Services.RestService.ExecuteRequestAsync[T] (System.String url, System.Net.Http.HttpMethod method, System.String content, System.String paginationToken, System.String accessToken, System.Boolean shouldRefreshToken, System.Threading.CancellationToken cancellationToken, System.Collections.Generic.Dictionary`2[TKey,TValue] headers) [0x00409] in /Users/flash/Work/FishAngler/mobileapp/FishAngler.Core/Services/RestService.cs:286 
  at FishAngler.Core.Services.RestService.ExecuteRequestAsync[T] (System.String url, System.Net.Http.HttpMethod method, System.String content, System.String paginationToken, System.String accessToken, System.Boolean shouldRefreshToken, System.Int32 maxConnectTries, System.Threading.CancellationToken cancellationToken, System.Collections.Generic.Dictionary`2[TKey,TValue] headers) [0x00099] in /Users/flash/Work/FishAngler/mobileapp/FishAngler.Core/Services/RestService.cs:172 
  --- End of managed Android.OS.NetworkOnMainThreadException stack trace ---
android.os.NetworkOnMainThreadException
    at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1513)
    at com.android.org.conscrypt.Platform.blockGuardOnNetwork(Platform.java:415)
    at com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLInputStream.read(ConscryptFileDescriptorSocket.java:560)
    at com.android.okhttp.okio.Okio$2.read(Okio.java:141)
    at com.android.okhttp.okio.AsyncTimeout$2.read(AsyncTimeout.java:211)
    at com.android.okhttp.okio.RealBufferedSource.read(RealBufferedSource.java:60)
    at com.android.okhttp.internal.http.Http1xStream$FixedLengthSource.read(Http1xStream.java:602)
    at com.android.okhttp.internal.Util.skipAll(Util.java:172)
    at com.android.okhttp.internal.Util.discard(Util.java:154)
    at com.android.okhttp.internal.http.Http1xStream$FixedLengthSource.close(Http1xStream.java:619)
    at com.android.okhttp.okio.RealBufferedSource.close(RealBufferedSource.java:421)
    at com.android.okhttp.okio.RealBufferedSource$1.close(RealBufferedSource.java:409)
    at mono.java.lang.RunnableImplementor.n_run(Native Method)
    at mono.java.lang.RunnableImplementor.run(RunnableImplementor.java:30)
    at android.os.Handler.handleCallback(Handler.java:873)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:214)
    at android.app.ActivityThread.main(ActivityThread.java:7050)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:494)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:965)

More info

Our fix for that is just to make sure that code is running on another thread.

In the version of HttpResponseMessage that is being run the EnsureSuccessStatusCode method is calling Dispose() on its _content stream. Which down the line tries to read more content: https://github.com/duzechao/OKHttpUtils/blob/1c1ee3667737c67d0ff523658bb9acad769030f7/okhttp/src/main/java/okhttp3/internal/http/Http1xStream.java#L458-L465

Trying to read the code for HttpResponseMessage.cs caused some confusion was I don't know which on is really in use.

When I look at Mono repository with the tag 6.8.0.123 the Dispose is not being called. https://github.com/mono/mono/blob/1d0d939dc30a5b56f478bc9f097cab146276b9af/mcs/class/System.Net.Http/System.Net.Http/HttpResponseMessage.cs#L118-L124

But when I disassemble the code on System.Net.Http.dll it is there:

        public HttpResponseMessage EnsureSuccessStatusCode()
        {
            if (!IsSuccessStatusCode)
            {
                if (_content != null)
                {
                    _content.Dispose();
                }
                throw new HttpRequestException(string.Format(CultureInfo.InvariantCulture, "Response status code does not indicate success: {0} ({1}).", (int)_statusCode, ReasonPhrase));
            }
            return this;
        }

Resolved from:

Dependency "System.Net.Http, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a".
        Resolved file path is "/Library/Frameworks/Mono.framework/External/xbuild-frameworks/MonoAndroid/v1.0/System.Net.Http.dll".

This issue started to appear for us not long ago so we were trying to check HttpResponseMessage code to know if there was regression in there where the Dispose() started to be called recently or if it is just hiding another issue on our side - which we can now investigate by avoiding Android.OS.NetworkOnMainThreadException from being thrown.

moljac commented 4 years ago

Thanks for your feedback. We will look into the issue as soon as possible.

grendello commented 4 years ago

@Flash3001 This looks like a Mono issue, can you attach a simple repro which triggers the behavior? Thanks!

grendello commented 4 years ago

I suppose the code and situation here is similar to https://github.com/xamarin/xamarin-android/issues/4695, if this is so @Flash3001, please refer to https://github.com/xamarin/xamarin-android/issues/4695#issuecomment-630644846 for a possible solution. If not, please reopen this issue and attach the requested information, thanks!