Skip to content

Commit f4f3b29

Browse files
aajtoddianbotsf
andauthored
fix(rt): handle exceptions in background okhttp response body coroutine (#734)
Wrap okhttp response body handling to avoid fatal crash on Android due to unhandled exception Co-authored-by: Ian Smith Botsford <[email protected]>
1 parent acd1138 commit f4f3b29

File tree

3 files changed

+24
-9
lines changed

3 files changed

+24
-9
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"id": "156447f6-0f80-4fde-a38d-6b92260432b8",
3+
"type": "bugfix",
4+
"description": "Fix Android crash when OkHttp response body coroutine throws an exception",
5+
"issues": [
6+
"awslabs/aws-sdk-kotlin#753"
7+
]
8+
}

runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/src/aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpUtils.kt

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -80,17 +80,18 @@ internal fun OkHttpResponse.toSdkResponse(callContext: CoroutineContext): HttpRe
8080
val ch = SdkByteChannel(true)
8181
val writerContext = callContext + Dispatchers.IO + callContext.derivedName("response-body-writer")
8282
val job = GlobalScope.launch(writerContext) {
83-
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
84-
val source = body.source()
85-
86-
while (!source.exhausted()) {
87-
val rc = source.read(buffer)
88-
if (rc == -1) break
89-
ch.writeFully(buffer, 0, rc)
83+
val result = runCatching {
84+
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
85+
val source = body.source()
86+
while (!source.exhausted()) {
87+
val rc = source.read(buffer)
88+
if (rc == -1) break
89+
ch.writeFully(buffer, 0, rc)
90+
}
9091
}
9192

9293
// immediately close when done to signal end of body stream
93-
ch.close()
94+
ch.close(result.exceptionOrNull())
9495
}
9596

9697
job.invokeOnCompletion { cause ->

runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/test/aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpResponseTest.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,13 +111,14 @@ class OkHttpResponseTest {
111111

112112
@Test
113113
fun testSourceReadError(): Unit = runBlocking {
114+
var exceptionBubbledUp: Throwable? = null
114115
val request = HttpRequest(HttpMethod.GET, Url.parse("https://aws.amazon.com"), Headers.Empty, HttpBody.Empty)
115116

116117
val execContext = ExecutionContext()
117118

118119
// replace default exception handler which will print out the stack trace by default.
119120
// We are expecting an exception so this message is misleading/confusing
120-
val exHandler = CoroutineExceptionHandler { _, _ -> }
121+
val exHandler = CoroutineExceptionHandler { _, t -> exceptionBubbledUp = t }
121122

122123
// don't tie this to the current job or else it will tear down the test as well
123124
val callJob = Job()
@@ -173,5 +174,10 @@ class OkHttpResponseTest {
173174
callJob.complete()
174175
callJob.join()
175176
assertEquals(0, callContext.job.children.toList().size)
177+
178+
assertNull(
179+
exceptionBubbledUp,
180+
"Unexpected exception bubbled up. It should've be pushed to channel's close() method instead.",
181+
)
176182
}
177183
}

0 commit comments

Comments
 (0)