Summary
DialForOutboundPeers() in p2p/p2p.go has two related bugs in the initial config peer dial loop:
- Data race the
dialing variable is written from spawned goroutines and read from the main loop without any synchronization.
- Counter never decremented goroutines increment
dialing but never decrement it after DialWithBackoff returns, so the counter stays permanently inflated.
Affected Code
File: p2p/p2p.go → DialForOutboundPeers()
for _, peerString := range p.config.DialPeers {
peerAddress, err := getPeerFromString(peerString)
if err != nil {
continue
}
go func() {
dialing++ // ← unsynchronized write from goroutine
p.DialWithBackoff(peerAddress, true)
// ← dialing is NEVER decremented after DialWithBackoff returns
}()
}
// main loop reads dialing without synchronization:
if outbound > 0 && outbound+dialing >= p.config.MaxOutbound {
return // ← falsely triggered once initial goroutines finish
}
Impact
- After the initial
DialPeers goroutines complete, dialing stays permanently at len(p.config.DialPeers).
- The main dial loop evaluates
outbound + dialing >= MaxOutbound as true and stops dialing new peers, even when the node is well under its outbound peer limit.
- Running with the
-race flag will surface the data race on dialing.
Steps to Reproduce
- Set one or more entries in
DialPeers in node config
- Start node with
go run -race ./cmd/...
- Observe data race warning on
dialing in DialForOutboundPeers
- After initial dial goroutines finish, observe node stops making new outbound connections despite being under
MaxOutbound
Suggested Fix
Use sync/atomic and add a deferred decrement:
var dialing atomic.Int64
for _, peerString := range p.config.DialPeers {
peerAddress, err := getPeerFromString(peerString)
if err != nil {
continue
}
go func() {
dialing.Add(1)
defer dialing.Add(-1)
p.DialWithBackoff(peerAddress, true)
}()
}
// main loop:
if outbound > 0 && int64(outbound)+dialing.Load() >= int64(p.config.MaxOutbound) {
return
}
Summary
DialForOutboundPeers()inp2p/p2p.gohas two related bugs in the initial config peer dial loop:dialingvariable is written from spawned goroutines and read from the main loop without any synchronization.dialingbut never decrement it afterDialWithBackoffreturns, so the counter stays permanently inflated.Affected Code
File:
p2p/p2p.go→DialForOutboundPeers()Impact
DialPeersgoroutines complete,dialingstays permanently atlen(p.config.DialPeers).outbound + dialing >= MaxOutboundas true and stops dialing new peers, even when the node is well under its outbound peer limit.-raceflag will surface the data race ondialing.Steps to Reproduce
DialPeersin node configgo run -race ./cmd/...dialinginDialForOutboundPeersMaxOutboundSuggested Fix
Use
sync/atomicand add a deferred decrement: