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.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Zoltán Papp
2026-06-25 13:02:58 +02:00
co-authored by Claude Opus 4.8
parent 566d21c2c3
commit 31c277b1df
+12 -9
View File
@@ -192,6 +192,7 @@ func (s *StatusChangeSubscription) Events() chan map[string]RouterState {
// Pure read methods take RLock; anything that mutates state takes Lock. // Pure read methods take RLock; anything that mutates state takes Lock.
type Status struct { type Status struct {
mux sync.RWMutex mux sync.RWMutex
muxRelays sync.RWMutex
peers map[string]State peers map[string]State
ipToKey map[string]string ipToKey map[string]string
changeNotify map[string]map[string]*StatusChangeSubscription // map[peerID]map[subscriptionID]*StatusChangeSubscription 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) { func (d *Status) SetRelayMgr(manager *relayClient.Manager) {
d.mux.Lock() d.muxRelays.Lock()
defer d.mux.Unlock() defer d.muxRelays.Unlock()
d.relayMgr = manager d.relayMgr = manager
} }
@@ -906,8 +907,8 @@ func (d *Status) MarkSignalConnected() {
} }
func (d *Status) UpdateRelayStates(relayResults []relay.ProbeResult) { func (d *Status) UpdateRelayStates(relayResults []relay.ProbeResult) {
d.mux.Lock() d.muxRelays.Lock()
defer d.mux.Unlock() defer d.muxRelays.Unlock()
d.relayStates = relayResults d.relayStates = relayResults
} }
@@ -1018,24 +1019,26 @@ func (d *Status) GetSignalState() SignalState {
// GetRelayStates returns the stun/turn/permanent relay states // GetRelayStates returns the stun/turn/permanent relay states
func (d *Status) GetRelayStates() []relay.ProbeResult { func (d *Status) GetRelayStates() []relay.ProbeResult {
d.mux.RLock() d.muxRelays.RLock()
defer d.mux.RUnlock()
if d.relayMgr == nil { if d.relayMgr == nil {
defer d.muxRelays.RUnlock()
return d.relayStates return d.relayStates
} }
relayMgr := d.relayMgr
// extend the list of stun, turn servers with the relay server connections // extend the list of stun, turn servers with the relay server connections
relayStates := slices.Clone(d.relayStates) relayStates := slices.Clone(d.relayStates)
d.muxRelays.RUnlock()
states := d.relayMgr.RelayStates() states := relayMgr.RelayStates()
if len(states) == 0 { if len(states) == 0 {
// no relay connection tracked yet; surface configured servers as // no relay connection tracked yet; surface configured servers as
// unavailable with the real reconnect error when known // unavailable with the real reconnect error when known
err := relayClient.ErrRelayClientNotConnected err := relayClient.ErrRelayClientNotConnected
if connErr := d.relayMgr.RelayConnectError(); connErr != nil { if connErr := relayMgr.RelayConnectError(); connErr != nil {
err = connErr err = connErr
} }
for _, r := range d.relayMgr.ServerURLs() { for _, r := range relayMgr.ServerURLs() {
relayStates = append(relayStates, relay.ProbeResult{ relayStates = append(relayStates, relay.ProbeResult{
URI: r, URI: r,
Err: err, Err: err,