From 62da33f02bd772847bb1ec431ca7b025ac150a38 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 19 May 2026 22:13:58 +0000 Subject: [PATCH] fix(relay-info): switch to Dispatchers.IO around the whole executeAsync call MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When loadRelayInfo was invoked from a main-thread Compose coroutine and the call got cancelled, okhttp's executeAsync cancellation handler closes the Response on the resuming dispatcher (AndroidUiDispatcher). Closing the response drains any unread bytes from the SSL socket, which triggers a blocking read on the main thread and throws NetworkOnMainThreadException — surfaced to the user as CompletionHandlerException. The previous withContext(Dispatchers.IO) was nested inside the .use {} block, so neither the initial suspension at executeAsync() nor the implicit response close in the cancellation handler ran on IO. Moving the dispatcher switch to wrap the entire function ensures the continuation is dispatched on IO and the cancellation/close path runs there too. --- .../model/nip11RelayInfo/Nip11Retriever.kt | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip11RelayInfo/Nip11Retriever.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip11RelayInfo/Nip11Retriever.kt index 46820521ac..41ce3a3bf9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip11RelayInfo/Nip11Retriever.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip11RelayInfo/Nip11Retriever.kt @@ -45,7 +45,7 @@ class Nip11Retriever( relay: NormalizedRelayUrl, onInfo: (Nip11RelayInformation) -> Unit, onError: (NormalizedRelayUrl, ErrorCode, String?) -> Unit, - ) { + ) = withContext(Dispatchers.IO) { val url = relay.toHttp() try { val request: Request = @@ -58,27 +58,25 @@ class Nip11Retriever( val client = okHttpClient(relay) client.newCall(request).executeAsync().use { response -> - withContext(Dispatchers.IO) { - val body = response.body.string() - try { - if (response.isSuccessful) { - if (body.startsWith("{")) { - onInfo(Nip11RelayInformation.fromJson(body)) - } else { - onError(relay, ErrorCode.FAIL_TO_PARSE_RESULT, body) - } + val body = response.body.string() + try { + if (response.isSuccessful) { + if (body.startsWith("{")) { + onInfo(Nip11RelayInformation.fromJson(body)) } else { - onError(relay, ErrorCode.FAIL_WITH_HTTP_STATUS, response.code.toString()) + onError(relay, ErrorCode.FAIL_TO_PARSE_RESULT, body) } - } catch (e: Exception) { - if (e is CancellationException) throw e - Log.e( - "RelayInfoFail", - "Resulting Message from Relay ${relay.url} in not parseable: $body", - e, - ) - onError(relay, ErrorCode.FAIL_TO_PARSE_RESULT, e.message) + } else { + onError(relay, ErrorCode.FAIL_WITH_HTTP_STATUS, response.code.toString()) } + } catch (e: Exception) { + if (e is CancellationException) throw e + Log.e( + "RelayInfoFail", + "Resulting Message from Relay ${relay.url} in not parseable: $body", + e, + ) + onError(relay, ErrorCode.FAIL_TO_PARSE_RESULT, e.message) } } } catch (e: Exception) {