Skip to content
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

Catch a gson JsonIOException when parsing SimpleRequest response [SDK-1981] #355

Merged
merged 6 commits into from
Oct 7, 2020

Conversation

quibi-jlk
Copy link
Contributor

This addresses support ticket #00467724. IOExceptions are already caught, but it turns out in some cases, gson actually wraps underlying IOExceptions in JsonIOExceptions (which does not extent IOException).

Example stack trace:

Fatal Exception: com.google.gson.JsonIOException: java.net.SocketTimeoutException: timeout
   at com.google.gson.internal.Streams.parse(Streams.java:62)
   at com.google.gson.internal.bind.TreeTypeAdapter.read(TreeTypeAdapter.java:65)
   at com.google.gson.TypeAdapter.fromJson(TypeAdapter.java:260)
   at com.auth0.android.request.internal.SimpleRequest.onResponse(SimpleRequest.java:76)
   at com.squareup.okhttp.Call$AsyncCall.execute(Call.java:177)
   at com.squareup.okhttp.internal.NamedRunnable.run(NamedRunnable.java:33)
   at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
   at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
   at java.lang.Thread.run(Thread.java:764)

Caused by java.net.SocketTimeoutException: timeout
   at okio.SocketAsyncTimeout.newTimeoutException(SocketAsyncTimeout.java:143)
   at okio.AsyncTimeout.access$newTimeoutException(AsyncTimeout.java:162)
   at okio.AsyncTimeout.withTimeout(AsyncTimeout.java:154)
   at okio.AsyncTimeout$source$1.read(AsyncTimeout.java:340)
   at okio.internal.RealBufferedSourceKt.commonRead(RealBufferedSourceKt.java:39)
   at okio.RealBufferedSource.read(RealBufferedSource.java:188)
   at com.squareup.okhttp.internal.http.Http1xStream$ChunkedSource.read(Http1xStream.java:439)
   at okio.internal.RealBufferedSourceKt.commonRead(RealBufferedSourceKt.java:39)
   at okio.RealBufferedSource.read(RealBufferedSource.java:188)
   at okio.internal.RealBufferedSourceKt.commonExhausted(RealBufferedSourceKt.java:49)
   at okio.RealBufferedSource.exhausted(RealBufferedSource.java:198)
   at okio.InflaterSource.refill(InflaterSource.java:112)
   at okio.InflaterSource.readOrInflate(InflaterSource.java:76)
   at okio.InflaterSource.read(InflaterSource.java:49)
   at okio.GzipSource.read(GzipSource.java:69)
   at okio.RealBufferedSource$inputStream$1.read(RealBufferedSource.java:158)
   at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:288)
   at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:351)
   at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:180)
   at java.io.InputStreamReader.read(InputStreamReader.java:184)
   at com.google.gson.stream.JsonReader.fillBuffer(JsonReader.java:1291)
   at com.google.gson.stream.JsonReader.nextNonWhitespace(JsonReader.java:1329)
   at com.google.gson.stream.JsonReader.doPeek(JsonReader.java:550)
   at com.google.gson.stream.JsonReader.peek(JsonReader.java:426)
   at com.google.gson.internal.bind.TypeAdapters$29.read(TypeAdapters.java:700)
   at com.google.gson.internal.bind.TypeAdapters$29.read(TypeAdapters.java:723)
   at com.google.gson.internal.bind.TypeAdapters$29.read(TypeAdapters.java:698)
   at com.google.gson.internal.Streams.parse(Streams.java:48)
   at com.google.gson.internal.bind.TreeTypeAdapter.read(TreeTypeAdapter.java:65)
   at com.google.gson.TypeAdapter.fromJson(TypeAdapter.java:260)
   at com.auth0.android.request.internal.SimpleRequest.onResponse(SimpleRequest.java:76)
   at com.squareup.okhttp.Call$AsyncCall.execute(Call.java:177)
   at com.squareup.okhttp.internal.NamedRunnable.run(NamedRunnable.java:33)
   at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
   at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
   at java.lang.Thread.run(Thread.java:764)

Testing

Please describe how this can be tested by reviewers. Be specific about anything not tested and reasons why. Since this library has unit testing, tests should be added for new functionality and existing tests should complete without errors.

  • This change adds unit test coverage

  • This change adds integration test coverage

  • This change has been tested on the latest version of the platform/language or why not

Checklist

IOExceptions are already caught, but it turns out in some cases, gson actually wraps underlying IOExceptions in JsonIOExceptions (which does not extent IOException).

Example stack trace:
```
Fatal Exception: com.google.gson.JsonIOException: java.net.SocketTimeoutException: timeout
   at com.google.gson.internal.Streams.parse(Streams.java:62)
   at com.google.gson.internal.bind.TreeTypeAdapter.read(TreeTypeAdapter.java:65)
   at com.google.gson.TypeAdapter.fromJson(TypeAdapter.java:260)
   at com.auth0.android.request.internal.SimpleRequest.onResponse(SimpleRequest.java:76)
   at com.squareup.okhttp.Call$AsyncCall.execute(Call.java:177)
   at com.squareup.okhttp.internal.NamedRunnable.run(NamedRunnable.java:33)
   at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
   at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
   at java.lang.Thread.run(Thread.java:764)

Caused by java.net.SocketTimeoutException: timeout
   at okio.SocketAsyncTimeout.newTimeoutException(SocketAsyncTimeout.java:143)
   at okio.AsyncTimeout.access$newTimeoutException(AsyncTimeout.java:162)
   at okio.AsyncTimeout.withTimeout(AsyncTimeout.java:154)
   at okio.AsyncTimeout$source$1.read(AsyncTimeout.java:340)
   at okio.internal.RealBufferedSourceKt.commonRead(RealBufferedSourceKt.java:39)
   at okio.RealBufferedSource.read(RealBufferedSource.java:188)
   at com.squareup.okhttp.internal.http.Http1xStream$ChunkedSource.read(Http1xStream.java:439)
   at okio.internal.RealBufferedSourceKt.commonRead(RealBufferedSourceKt.java:39)
   at okio.RealBufferedSource.read(RealBufferedSource.java:188)
   at okio.internal.RealBufferedSourceKt.commonExhausted(RealBufferedSourceKt.java:49)
   at okio.RealBufferedSource.exhausted(RealBufferedSource.java:198)
   at okio.InflaterSource.refill(InflaterSource.java:112)
   at okio.InflaterSource.readOrInflate(InflaterSource.java:76)
   at okio.InflaterSource.read(InflaterSource.java:49)
   at okio.GzipSource.read(GzipSource.java:69)
   at okio.RealBufferedSource$inputStream$1.read(RealBufferedSource.java:158)
   at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:288)
   at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:351)
   at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:180)
   at java.io.InputStreamReader.read(InputStreamReader.java:184)
   at com.google.gson.stream.JsonReader.fillBuffer(JsonReader.java:1291)
   at com.google.gson.stream.JsonReader.nextNonWhitespace(JsonReader.java:1329)
   at com.google.gson.stream.JsonReader.doPeek(JsonReader.java:550)
   at com.google.gson.stream.JsonReader.peek(JsonReader.java:426)
   at com.google.gson.internal.bind.TypeAdapters$29.read(TypeAdapters.java:700)
   at com.google.gson.internal.bind.TypeAdapters$29.read(TypeAdapters.java:723)
   at com.google.gson.internal.bind.TypeAdapters$29.read(TypeAdapters.java:698)
   at com.google.gson.internal.Streams.parse(Streams.java:48)
   at com.google.gson.internal.bind.TreeTypeAdapter.read(TreeTypeAdapter.java:65)
   at com.google.gson.TypeAdapter.fromJson(TypeAdapter.java:260)
   at com.auth0.android.request.internal.SimpleRequest.onResponse(SimpleRequest.java:76)
   at com.squareup.okhttp.Call$AsyncCall.execute(Call.java:177)
   at com.squareup.okhttp.internal.NamedRunnable.run(NamedRunnable.java:33)
   at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
   at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
   at java.lang.Thread.run(Thread.java:764)
```
@quibi-jlk quibi-jlk requested a review from a team September 25, 2020 19:39
@lbalmaceda
Copy link
Contributor

lbalmaceda commented Sep 30, 2020

@quibi-jlk hey 👋 looks like the actual cause was a SocketTimeoutException, which I wouldn't expect the Gson#fromJson method to throw internally as there are no more network requests at this point in the code. Although it might refer to the string stream returned by the response object being closed before time, maybe the raw content is too long?

Can you please share the steps to validate and reproduce this on our end?

@lbalmaceda lbalmaceda added the waiting for customer This issue is waiting for a response from the issue or PR author label Sep 30, 2020
@quibi-jlk
Copy link
Contributor Author

@quibi-jlk hey 👋 looks like the actual cause was a SocketTimeoutException, which I wouldn't expect the Gson#fromJson method to throw internally as there are no more network requests at this point in the code. Although it might refer to the string stream returned by the response object being closed before time, maybe the raw content is too long?

Can you please share the steps to validate and reproduce this on our end?

Hi @lbalmaceda! Unfortunately we don't have a local repro on this, but we've been getting reports to crashlytics. There is one more cause wrapped by the SocketTimeoutException if that's helpful:

Caused by java.net.SocketException: socket is closed
       at com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLInputStream.read(ConscryptFileDescriptorSocket.java:554)
       at okio.InputStreamSource.read(InputStreamSource.java:90)
       at okio.AsyncTimeout$source$1.read(AsyncTimeout.java:129)
       at okio.internal.RealBufferedSourceKt.commonRead(RealBufferedSourceKt.java:39)
       at okio.RealBufferedSource.read(RealBufferedSource.java:188)
       at com.squareup.okhttp.internal.http.Http1xStream$ChunkedSource.read(Http1xStream.java:439)
       at okio.internal.RealBufferedSourceKt.commonRead(RealBufferedSourceKt.java:39)
       at okio.RealBufferedSource.read(RealBufferedSource.java:188)
       at okio.internal.RealBufferedSourceKt.commonExhausted(RealBufferedSourceKt.java:49)
       at okio.RealBufferedSource.exhausted(RealBufferedSource.java:198)
       at okio.InflaterSource.refill(InflaterSource.java:112)
       at okio.InflaterSource.readOrInflate(InflaterSource.java:76)
       at okio.InflaterSource.read(InflaterSource.java:49)
       at okio.GzipSource.read(GzipSource.java:69)
       at okio.RealBufferedSource$inputStream$1.read(RealBufferedSource.java:158)
       at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:291)
       at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:355)
       at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:181)
       at java.io.InputStreamReader.read(InputStreamReader.java:184)
       at com.google.gson.stream.JsonReader.fillBuffer(JsonReader.java:1291)
       at com.google.gson.stream.JsonReader.nextQuotedValue(JsonReader.java:1031)
       at com.google.gson.stream.JsonReader.nextString(JsonReader.java:816)
       at com.google.gson.internal.bind.TypeAdapters$29.read(TypeAdapters.java:702)
       at com.google.gson.internal.bind.TypeAdapters$29.read(TypeAdapters.java:723)
       at com.google.gson.internal.bind.TypeAdapters$29.read(TypeAdapters.java:698)
       at com.google.gson.internal.Streams.parse(Streams.java:48)
       at com.google.gson.internal.bind.TreeTypeAdapter.read(TreeTypeAdapter.java:65)
       at com.google.gson.TypeAdapter.fromJson(TypeAdapter.java:260)
       at com.auth0.android.request.internal.SimpleRequest.onResponse(SimpleRequest.java:76)
       at com.squareup.okhttp.Call$AsyncCall.execute(Call.java:177)
       at com.squareup.okhttp.internal.NamedRunnable.run(NamedRunnable.java:33)
       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
       at java.lang.Thread.run(Thread.java:919)

It does seem like it's still reading from a buffered okio stream at that point, so it will have a network connection active. It seems like this should still be caught here.

@lbalmaceda
Copy link
Contributor

Thanks for that additional stack trace. If you happen to cross-reference and find the endpoint that was being called when this happened, please let me know. I'm tracking this internally in SDK-1981. To set expectations, I can pick this up not before the next week.

@lbalmaceda lbalmaceda added needs investigation An issue that has more questions to answer or otherwise needs work to fully understand the issue and removed waiting for customer This issue is waiting for a response from the issue or PR author labels Sep 30, 2020
@lbalmaceda lbalmaceda changed the title Catch a gson JsonIOException when parsing SimpleRequest response. Catch a gson JsonIOException when parsing SimpleRequest response [SDK-1981] Sep 30, 2020
@quibi-jlk
Copy link
Contributor Author

Thanks for that additional stack trace. If you happen to cross-reference and find the endpoint that was being called when this happened, please let me know. I'm tracking this internally in SDK-1981. To set expectations, I can pick this up not before the next week.

As far as I can tell, the endpoint isn't relevant. I see this both at log in (presumably hitting our log in endpoint) and post-login trying to call renewAuth. It seems like this just happens on really bad connections or networks that don't actually have an internet connection. I see other requests timing out as well, but they don't crash the app like this one does. Can you clarify why you wouldn't want to catch this exception in this case? What would be the downside of re-wrapping and properly firing this exception as a request failure instead of crashing?

@lbalmaceda
Copy link
Contributor

What's being caught today is what is declared by the Gson#fromJson() method, which is an IOException.
image

Digging a bit into these issues:

I think the best path moving forward is to catch both IOException and JsonParseException (parent class of JsonIOException), as that would ensure future subclasses are also wrapped.

I checked the SimpleRequest class and it doesn't have tests covering the error scenarios. Would you like to add those to this PR in addition to the class name change, or do you prefer us to create a new PR with the changes?

@quibi-jlk
Copy link
Contributor Author

quibi-jlk commented Oct 5, 2020

Thanks @lbalmaceda. I've switched to a JsonParseException here. Unfortunately, I don't have the bandwidth to dig into your test suite and add new tests for SimpleRequest. Do you mind taking it from there?

lbalmaceda
lbalmaceda previously approved these changes Oct 5, 2020
@lbalmaceda
Copy link
Contributor

Actually, it doesn't make much sense for me to approve my own changes, even if just unit tests. Let me have someone on my team check it 👍

@quibi-jlk
Copy link
Contributor Author

Actually, it doesn't make much sense for me to approve my own changes, even if just unit tests. Let me have someone on my team check it 👍

Cool, thanks!!

@lbalmaceda lbalmaceda added CH: Fixed and removed needs investigation An issue that has more questions to answer or otherwise needs work to fully understand the issue labels Oct 5, 2020
@lbalmaceda lbalmaceda added this to the v1-Next milestone Oct 5, 2020
Copy link
Contributor

@Widcket Widcket left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, just left a couple of comments.

@lbalmaceda
Copy link
Contributor

@Widcket good catch rita. I've fixed those.

@lbalmaceda lbalmaceda merged commit a419556 into auth0:master Oct 7, 2020
@lbalmaceda
Copy link
Contributor

@quibi-jlk I've another PR planned for the next release. If I can't get it merged by the end of the week, I'll release a patch with this change on Friday.

@quibi-jlk
Copy link
Contributor Author

@quibi-jlk I've another PR planned for the next release. If I can't get it merged by the end of the week, I'll release a patch with this change on Friday.

Awesome, thanks so much @lbalmaceda!

@lbalmaceda
Copy link
Contributor

Hey, I wanted to keep you looped. I'm postponing this release for the next week.

@quibi-jlk
Copy link
Contributor Author

Hey, I wanted to keep you looped. I'm postponing this release for the next week.

Thanks for the heads up! Please let me know whenever this makes it into a release.

@lbalmaceda lbalmaceda modified the milestones: v1-Next, 1.28.0 Oct 13, 2020
@lbalmaceda
Copy link
Contributor

@quibi-jlk this was just uploaded to JCenter and should be available in Central in the next hour. Version 1.28.0.

@quibi-jlk
Copy link
Contributor Author

@quibi-jlk this was just uploaded to JCenter and should be available in Central in the next hour. Version 1.28.0.

Awesome, thanks!

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

Successfully merging this pull request may close these issues.

None yet

3 participants