Symptom

Nightly architectural scan flagged HttpClientProvider in all three JVM/Native actual implementations (android, jvm, ios) as having an unsynchronized mutable client field accessed concurrently by ClientNodeManager, NodeObserver, and any other coroutines that call httpClient or sseHttpClient. Under concurrent load, two callers could both observe client == null and each build a new HttpClient, leaking one. rebuild() could close the old client while a concurrent getInstance() was still using it or was mid-check — producing a torn read or returning a closed client.

Root cause

All three HttpClientProvider implementations used a plain private var client: HttpClient? = null with an unsynchronized check-then-set:

1
2
3
4
5
6
7
8
9
10
11
fun getInstance(): HttpClient {
    if (client == null) {   // TOCTOU: two threads can both see null
        client = buildSslClient()
    }
    return client!!
}

fun rebuild() {
    client?.close()         // races with concurrent getInstance()
    client = buildSslClient()
}

rebuild() was also calling close() before assigning the new client, creating a window where getInstance() could return a closed or null client.

Fix

Prevention