feat(quartz): include exception type in relay connect-failure message

BasicRelayClient collapsed a connection failure into a message string built
from the throwable's text alone. Message text is localized and inconsistent
across platforms, so a listener can't reliably tell a busy relay (a connect
timeout) from a dead one (bad domain / TLS misconfig) from it. Always append
the exception class name (SocketTimeoutException / UnknownHostException /
SSLHandshakeException / ConnectException …), which is stable, so listeners
can classify the failure by type. Message text is preserved; the type is
added in parentheses. Updated the one test that pinned the old format.
This commit is contained in:
Claude
2026-07-07 13:10:58 +00:00
parent 9611be4135
commit 6d64b7b41c
2 changed files with 15 additions and 5 deletions
@@ -136,7 +136,9 @@ open class BasicRelayClient(
socket?.connect()
} catch (e: Exception) {
if (e is CancellationException) throw e
listener.onCannotConnect(this, "Error when trying to connect: ${e.message ?: e::class.simpleName}")
val typeName = e::class.simpleName
val detail = e.message?.let { "$it ($typeName)" } ?: (typeName ?: "unknown error")
listener.onCannotConnect(this, "Error when trying to connect: $detail")
listener.onDisconnected(this)
dontTryAgainForALongTime()
markConnectionAsClosed()
@@ -187,9 +189,15 @@ open class BasicRelayClient(
} else {
socket?.disconnect()
// suppression rules below must match the raw message; displayMsg is for listener output only
// suppression rules below must match the raw message; displayMsg is for listener output only.
// Always include the exception's class name: message text is
// localized and inconsistent across platforms, but the type
// (SocketTimeoutException / UnknownHostException / SSLHandshakeException /
// ConnectException …) is stable and lets listeners classify a failure
// reliably — a busy relay (timeout) vs a dead one (bad domain / TLS).
val msg = t.message
val displayMsg = msg ?: t::class.simpleName
val typeName = t::class.simpleName
val displayMsg = if (msg != null) "$msg ($typeName)" else (typeName ?: "unknown error")
// checks if this is an actual failure. Closing the socket generates an onFailure as well.
// ignore tor errors.
@@ -72,13 +72,15 @@ class BasicRelayClientTest {
}
@Test
fun onFailureWithMessageKeepsExistingFormat() {
fun onFailureWithMessageAppendsExceptionClassName() {
val (socket, listener) = connectAndCapture()
socket.onFailure(Exception("Connection reset"), null, null)
// The exception type is appended so listeners can classify the failure by
// its stable class name rather than by localized message text.
assertEquals(
listOf("WebSocket Failure: Connection reset"),
listOf("WebSocket Failure: Connection reset (Exception)"),
listener.cannotConnectMessages,
)
}