From dec95a0757bbf9796c6c73cdefe03e0e5d431461 Mon Sep 17 00:00:00 2001 From: Zoltan Papp Date: Thu, 25 Jun 2026 18:46:34 +0200 Subject: [PATCH] peer/status: move relay-state reads off the main mux GetRelayStates held d.mux (RLock) while calling into the relay Manager (RelayStates/RelayConnectError/ServerURLs). Those calls can be slow or block on the relay manager's own locks while it is reconnecting, which kept the central Status mutex held and stalled every peer state writer (UpdatePeerState, ReplaceOfflinePeers, etc.) contending for it. Guard relayMgr/relayStates with a dedicated muxRelays mutex and release it before invoking the relay Manager, so the relay read path no longer contends with the hot peer-state writers on d.mux. --- client/internal/peer/status.go | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/client/internal/peer/status.go b/client/internal/peer/status.go index 3e5c56dd2..9f73202bf 100644 --- a/client/internal/peer/status.go +++ b/client/internal/peer/status.go @@ -192,6 +192,7 @@ func (s *StatusChangeSubscription) Events() chan map[string]RouterState { // Pure read methods take RLock; anything that mutates state takes Lock. type Status struct { mux sync.RWMutex + muxRelays sync.RWMutex peers map[string]State ipToKey map[string]string changeNotify map[string]map[string]*StatusChangeSubscription // map[peerID]map[subscriptionID]*StatusChangeSubscription @@ -244,8 +245,8 @@ func NewRecorder(mgmAddress string) *Status { } func (d *Status) SetRelayMgr(manager *relayClient.Manager) { - d.mux.Lock() - defer d.mux.Unlock() + d.muxRelays.Lock() + defer d.muxRelays.Unlock() d.relayMgr = manager } @@ -906,8 +907,8 @@ func (d *Status) MarkSignalConnected() { } func (d *Status) UpdateRelayStates(relayResults []relay.ProbeResult) { - d.mux.Lock() - defer d.mux.Unlock() + d.muxRelays.Lock() + defer d.muxRelays.Unlock() d.relayStates = relayResults } @@ -1018,24 +1019,26 @@ func (d *Status) GetSignalState() SignalState { // GetRelayStates returns the stun/turn/permanent relay states func (d *Status) GetRelayStates() []relay.ProbeResult { - d.mux.RLock() - defer d.mux.RUnlock() + d.muxRelays.RLock() if d.relayMgr == nil { + defer d.muxRelays.RUnlock() return d.relayStates } + relayMgr := d.relayMgr // extend the list of stun, turn servers with the relay server connections relayStates := slices.Clone(d.relayStates) + d.muxRelays.RUnlock() - states := d.relayMgr.RelayStates() + states := relayMgr.RelayStates() if len(states) == 0 { // no relay connection tracked yet; surface configured servers as // unavailable with the real reconnect error when known err := relayClient.ErrRelayClientNotConnected - if connErr := d.relayMgr.RelayConnectError(); connErr != nil { + if connErr := relayMgr.RelayConnectError(); connErr != nil { err = connErr } - for _, r := range d.relayMgr.ServerURLs() { + for _, r := range relayMgr.ServerURLs() { relayStates = append(relayStates, relay.ProbeResult{ URI: r, Err: err,