Summary
When the watchdog ping fails, Keep_Connectivity tears down the shared RPC client in place:
|
if IsDaemonOnline() { |
|
var result string |
|
if err := rpc_client.Call("DERO.Ping", nil, &result); err != nil { |
|
// fmt.Printf("Ping failed: %v", err) |
|
rpc_client.RPC.Close() |
|
rpc_client.WS = nil |
|
rpc_client.RPC = nil |
|
Connected = false |
|
Connect("") // try to connect again |
|
|
|
} else { |
if err := rpc_client.Call("DERO.Ping", nil, &result); err != nil {
rpc_client.RPC.Close()
rpc_client.WS = nil
rpc_client.RPC = nil
Connected = false
Connect("")
}
Meanwhile every other goroutine (sync loop, wallet RPC handlers, transfer building, history sync) uses the same rpc_client through the check-then-use pattern:
func IsDaemonOnline() bool {
if rpc_client.WS == nil || rpc_client.RPC == nil {
return false
}
return true
}
Impact
There is no synchronization between the nil-out and concurrent users:
- A goroutine that passed
IsDaemonOnline() can dereference rpc_client.RPC just after it was set to nil → panic.
RPC.Close() while another goroutine is inside CallResult aborts that call mid-flight (acceptable), but the unsynchronized pointer writes themselves are also a data race (same class as the globals race — see companion issue).
Connect("") then re-populates WS/RPC non-atomically, so readers can observe a half-initialized client.
Fix
Replace the two mutable fields with an atomically-swapped immutable client value: build the new Client fully, then client.Store(newClient) (atomic.Pointer[Client]); IsDaemonOnline and all callers load the pointer once and use that snapshot. Alternatively, guard connect/teardown/use with an RWMutex.
Summary
When the watchdog ping fails,
Keep_Connectivitytears down the shared RPC client in place:derohe/walletapi/daemon_connectivity_loop.go
Lines 48 to 58 in 6f9bd2c
Meanwhile every other goroutine (sync loop, wallet RPC handlers, transfer building, history sync) uses the same
rpc_clientthrough the check-then-use pattern:Impact
There is no synchronization between the nil-out and concurrent users:
IsDaemonOnline()can dereferencerpc_client.RPCjust after it was set to nil → panic.RPC.Close()while another goroutine is insideCallResultaborts that call mid-flight (acceptable), but the unsynchronized pointer writes themselves are also a data race (same class as the globals race — see companion issue).Connect("")then re-populatesWS/RPCnon-atomically, so readers can observe a half-initialized client.Fix
Replace the two mutable fields with an atomically-swapped immutable client value: build the new
Clientfully, thenclient.Store(newClient)(atomic.Pointer[Client]);IsDaemonOnlineand all callers load the pointer once and use that snapshot. Alternatively, guard connect/teardown/use with an RWMutex.