fix(relay-info): switch to Dispatchers.IO around the whole executeAsync call

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.
This commit is contained in:
Claude
2026-05-19 22:13:58 +00:00
parent 6e37133b27
commit 62da33f02b
@@ -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) {