Skip to content

fix(p2p): synchronize access to the inbound listener - #568

Open
0xrlawrence wants to merge 1 commit into
canopy-network:mainfrom
0xrlawrence:fix/p2p-listener-data-race
Open

0xrlawrence wants to merge 1 commit into
canopy-network:mainfrom
0xrlawrence:fix/p2p-listener-data-race

Conversation

@0xrlawrence

Copy link
Copy Markdown

Description

p.listener is written by ListenForInboundPeers from the goroutine Start() spawns, and read unsynchronized by Stop() and by several test helpers. The race detector reports it as the single largest source of races in this package.

The practical consequence is at shutdown: Stop() can observe a nil listener that has in fact already been created, skip the Close(), and leak the socket — while the accept loop keeps running against a listener nobody closed.

Changes Made

  • Guard the field with a dedicated listenerMu.
  • Publish the listener under the lock, then run the accept loop against a local reference so the hot accept path stays lock-free.
  • Read it under the lock in Stop().
  • Add ListenerAddress(), a synchronized accessor, and use it in the tests that previously reached into n.listener directly.

Testing

go test -race ./p2p/: data races in this package drop from 65 to 8, and no remaining race involves the listener.

The 8 that remain are pre-existing and unrelated — Stream/MultiConn state in conn.go and encrypt.go. Left for separate changes rather than widening this one.

go test ./p2p/ passes.

Note for reviewers

This touches ListenForInboundPeers, which #521 also edits. The two changes are independent but will need a trivial rebase depending on merge order.

🤖 Generated with Claude Code

p.listener was written by ListenForInboundPeers from the goroutine Start()
spawns, and read unsynchronized by Stop() and by several test helpers. The
race detector reported it as the single largest source of races in this
package.

The practical consequence is at shutdown: Stop() can observe a nil listener
that has in fact already been created, skip the Close(), and leak the
socket; the accept loop then keeps running against a listener nobody closed.

Changes:
- guard the field with a dedicated listenerMu
- publish the listener under the lock, then run the accept loop against a
  local reference so the hot path stays lock-free
- read it under the lock in Stop()
- add ListenerAddress(), a synchronized accessor, and use it in the tests
  that previously reached into n.listener directly

Verified with 'go test -race ./p2p/': data races in this package drop from
65 to 8, and no remaining race involves the listener. The 8 that remain are
pre-existing and unrelated (Stream/MultiConn state in conn.go and
encrypt.go); they are left for separate changes.

Note for reviewers: this touches ListenForInboundPeers, which PR canopy-network#521 also
edits. The two changes are independent but will need a trivial rebase
depending on merge order.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant