perf(comm): replace per-peer goroutines with fixed worker pool#42
Open
simonzg wants to merge 2 commits into
Open
perf(comm): replace per-peer goroutines with fixed worker pool#42simonzg wants to merge 2 commits into
simonzg wants to merge 2 commits into
Conversation
…ck broadcast Previously BroadcastBlock spawned one goroutine per peer on every call, creating O(peers * blocks) goroutines under load. PeerSet used sync.Mutex even for read-only operations (Slice, Len, Find, DirectionCount), serialising all readers. This change: - Adds a fixed pool of 32 goroutines (broadcastWorkers) consuming tasks from a buffered channel (broadcastQueueSize=512). Tasks dropped when the pool is saturated are safe to discard — peers receive the block via the announce/re-request path. - Upgrades PeerSet.lock from sync.Mutex to sync.RWMutex so Slice, Len, Find and DirectionCount can all proceed concurrently instead of serialising. - Stop() closes broadcastCh to cleanly drain workers on shutdown. Impact: eliminates goroutine proliferation during high-throughput broadcast and reduces mutex contention on PeerSet reads. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
BroadcastBlockspawned one goroutine per peer per call. With 50+ connected peers and blocks arriving every few seconds, this creates O(peers × blocks/sec) goroutines continuously — hundreds per minute — each living only long enough to send one message. The runtime's goroutine scheduler and GC pay a non-trivial cost for this churn.Additionally,
PeerSetusedsync.Mutexfor every operation, including purely read-only methods (Slice,Len,Find,DirectionCount). This means concurrent broadcast and peer-count reads serialise against each other unnecessarily.Solution
Fixed-size goroutine worker pool (
comm/communicator.go)broadcastCh chan func()field (buffered at 512 slots)New()that drain the channelBroadcastBlocknow enqueues closures via a non-blocking select — if the pool is saturated the task is safely dropped (the peer will receive the block via the announce/sync path)Stop()closesbroadcastChto cleanly shut down all workersRead-write mutex for PeerSet (
comm/peer.go)PeerSet.lockfromsync.Mutextosync.RWMutexSlice(),Len(),Find(), andDirectionCount()now useRLock/RUnlock— they can all proceed concurrentlyAdd()andRemove()retain exclusiveLock/Unlock(write operations)Impact
Files changed
comm/communicator.go— worker pool init/shutdown, non-blocking broadcast enqueuecomm/peer.go—sync.Mutex→sync.RWMutex, read methods useRLock🤖 Generated with Claude Code