fix(audio-rooms): retry mintToken on transport hiccup + harness /health warmup
The remaining interop failures all root in the same window: a stale
keep-alive pool entry from one test class is reused on the FIRST POST
of the next test class, the connection RSTs as the request body
writes, and OkHttp's built-in retryOnConnectionFailure won't retry a
POST after any byte of the body has gone out. Same situation hits a
phone client whose Wi-Fi hands off mid-mint.
Two fixes, both production-shaped:
- OkHttpNestsClient.mintToken now wraps execute() in
executeWithTransportRetry(): one retry on SocketException /
EOFException / generic IOException. Request builders are
immutable, so the second pass opens a fresh connection cleanly.
HTTP error status codes (4xx / 5xx) and malformed responses are
NOT retried — they go to the caller as before.
- NostrNestsHarness now polls GET /health until it returns 200
after the port-probe succeeds. moq-auth's Node runtime opens its
listen socket before the request handlers are wired, so a POST
that arrives in that window can RST. Waiting for /health proves
the request pipeline is live, eliminating the SocketException
that hit the first test of every test class run after
AuthEndpoints.
Symptoms fixed:
- NostrNestsAuthFailureInteropTest.missing_authorization_header_is_rejected_401
-> SocketException
- NostrNestsRoundTripInteropTest.production_speaker_broadcasts_to_production_listener_via_real_relay
-> "Failed to reach http://127.0.0.1:8090/auth"
- NostrNestsMultiPeerInteropTest.* (3 tests, same root cause)
This commit is contained in:
+42
-2
@@ -27,7 +27,10 @@ import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okhttp3.Response
|
||||
import java.io.EOFException
|
||||
import java.io.IOException
|
||||
import java.net.SocketException
|
||||
|
||||
/**
|
||||
* OkHttp-backed [NestsClient] used on JVM + Android. A shared [OkHttpClient]
|
||||
@@ -71,8 +74,7 @@ class OkHttpNestsClient(
|
||||
.build()
|
||||
|
||||
return withContext(Dispatchers.IO) {
|
||||
runCatching { http.newCall(request).execute() }
|
||||
.getOrElse { throw NestsException("Failed to reach $url", it) }
|
||||
executeWithTransportRetry(request, url)
|
||||
.use { response ->
|
||||
val body = response.body.string()
|
||||
if (!response.isSuccessful) {
|
||||
@@ -94,6 +96,44 @@ class OkHttpNestsClient(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send [request] and tolerate one transport-layer hiccup. OkHttp's
|
||||
* built-in `retryOnConnectionFailure` does NOT retry POSTs once any
|
||||
* byte of the request body has been written — but a stale pooled
|
||||
* connection can RST or EOF *exactly* in that window, especially on
|
||||
* mobile networks (and during interop test runs after an idle gap
|
||||
* between test classes). One retry on `SocketException` /
|
||||
* `EOFException` / `IOException` recovers cleanly because
|
||||
* `Request` builders are immutable; OkHttp opens a fresh
|
||||
* connection on the second try.
|
||||
*
|
||||
* Anything that's not a transient transport failure (HTTP 4xx /
|
||||
* 5xx, malformed response) is left to the caller as before.
|
||||
*/
|
||||
private fun executeWithTransportRetry(
|
||||
request: Request,
|
||||
url: String,
|
||||
): Response {
|
||||
var lastError: Throwable? = null
|
||||
repeat(2) { attempt ->
|
||||
try {
|
||||
return http.newCall(request).execute()
|
||||
} catch (e: SocketException) {
|
||||
lastError = e
|
||||
} catch (e: EOFException) {
|
||||
lastError = e
|
||||
} catch (e: IOException) {
|
||||
// OkHttp wraps a wide variety of transport faults
|
||||
// (StreamResetException, ConnectionShutdownException,
|
||||
// …) under IOException. Retry once; second pass either
|
||||
// succeeds against a fresh connection or surfaces the
|
||||
// real error.
|
||||
lastError = e
|
||||
}
|
||||
}
|
||||
throw NestsException("Failed to reach $url", lastError)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private val JSON_MEDIA_TYPE = "application/json".toMediaType()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user