Skip to content

core/txpool: change lock in Pending method of legacy pool to read lock - #32924

Merged
fjl merged 2 commits into
ethereum:masterfrom
pythonberg1997:txpool
May 7, 2026
Merged

fjl merged 2 commits into
ethereum:masterfrom
pythonberg1997:txpool

Conversation

@pythonberg1997

Copy link
Copy Markdown
Contributor

This PR makes a small update to the Pending() method in the legacy pool. By changing the lock from exclusive to read-only, it aims to improve concurrency performance.

Comment thread core/txpool/legacypool/legacypool.go
@will-2012

Copy link
Copy Markdown
Contributor

#31523. I tried this before, but it seems to have some issues. FYI.

@will-2012

Copy link
Copy Markdown
Contributor

#31523. I tried this before, but it seems to have some issues. FYI.

SortedMap seems to have complex concurrent semantics. It seems that it is dangerous in concurrent situations.

Pls check and pay more attenttion :D.

@rjl493456442

Copy link
Copy Markdown
Member

@will-2012 Thanks for pointing it out, you are right.

func TestSortedMapConcurrency(t *testing.T) {
	m := NewSortedMap()

	// Generate a list of transactions to insert
	key, _ := crypto.GenerateKey()
	txs := make(types.Transactions, 1000)
	for i := 0; i < len(txs); i++ {
		txs[i] = transaction(uint64(i), 0, key)
	}
	for i := 0; i < len(txs)/2; i++ {
		m.Put(txs[i])
	}
	
	var wg sync.WaitGroup
	wg.Add(8)
	for i := 0; i < 8; i++ {
		go func() {
			defer wg.Done()

			m.Flatten()
		}()
	}

	wg.Add(1)
	go func() {
		defer wg.Done()

		for i := len(txs) / 2; i < len(txs); i++ {
			m.Put(txs[i])
		}
	}()

	wg.Add(1)
	go func() {
		defer wg.Done()

		for i := 0; i < len(txs)/2; i++ {
			m.Forward(uint64(i))
		}
	}()

	wg.Wait()
}

I can reproduce the concurrent map read/write panic with this script.

@pythonberg1997

Copy link
Copy Markdown
Contributor Author

@will-2012 Thanks for pointing it out, you are right.

func TestSortedMapConcurrency(t *testing.T) {
	m := NewSortedMap()

	// Generate a list of transactions to insert
	key, _ := crypto.GenerateKey()
	txs := make(types.Transactions, 1000)
	for i := 0; i < len(txs); i++ {
		txs[i] = transaction(uint64(i), 0, key)
	}
	for i := 0; i < len(txs)/2; i++ {
		m.Put(txs[i])
	}
	
	var wg sync.WaitGroup
	wg.Add(8)
	for i := 0; i < 8; i++ {
		go func() {
			defer wg.Done()

			m.Flatten()
		}()
	}

	wg.Add(1)
	go func() {
		defer wg.Done()

		for i := len(txs) / 2; i < len(txs); i++ {
			m.Put(txs[i])
		}
	}()

	wg.Add(1)
	go func() {
		defer wg.Done()

		for i := 0; i < len(txs)/2; i++ {
			m.Forward(uint64(i))
		}
	}()

	wg.Wait()
}

I can reproduce the concurrent map read/write panic with this script.

Cool. I see.

@buddh0

buddh0 commented Oct 17, 2025

Copy link
Copy Markdown
Contributor

I checked the code — both Content and Pending can safely use RLock.
The Put and Forward methods of SortedMap are already protected by Lock in their actual usage scenarios,
and Flatten is also guarded by RLock.
Therefore, the test case mentioned in the reply wouldn’t occur in practice.

@rjl493456442

@will-2012

Copy link
Copy Markdown
Contributor

I checked the code — both Content and Pending can safely use RLock. The Put and Forward methods of SortedMap are already protected by Lock in their actual usage scenarios, and Flatten is also guarded by RLock. Therefore, the test case mentioned in the reply wouldn’t occur in practice.

@rjl493456442

Got it, SortedMap is not thread-safe, and the upper caller is responsible for concurrent access.

@fjl fjl added this to the 1.17.3 milestone Apr 9, 2026
@fjl
fjl merged commit c5598fe into ethereum:master May 7, 2026
9 of 12 checks passed
gzliudan added a commit to gzliudan/XDPoSChain that referenced this pull request Aug 12, 2026
…2924

Content and Pending only read pool state, but took the exclusive lock. Since
handle() calls syncTransactions -> Pending before a peer enters its message
loop, any concurrent read-lock holder wedges peer registration: the peer never
reads a message, the downloader times out its header request, and every eth
peer goroutine piles up behind the txpool mutex. Mainnet and testnet nodes
froze this way for ~24h with a healthy p2p server and responsive RPC.

Switch both accessors to RLock/RUnlock, matching upstream geth c5598fe.

Add TestPendingDoesNotBlockBehindPoolReaders, which holds the pool read lock
and asserts Pending and Content still return, and
BenchmarkPendingConcurrentPeerSyncs, which quantifies the serialisation: with
the write lock the benchmark does not improve from -cpu=1 to -cpu=8, with the
read lock it does.
gzliudan added a commit to gzliudan/XDPoSChain that referenced this pull request Aug 12, 2026
gzliudan added a commit to gzliudan/XDPoSChain that referenced this pull request Aug 14, 2026
gzliudan added a commit to gzliudan/XDPoSChain that referenced this pull request Aug 16, 2026
gzliudan added a commit to gzliudan/XDPoSChain that referenced this pull request Aug 22, 2026
gzliudan added a commit to XinFinOrg/XDPoSChain that referenced this pull request Aug 23, 2026
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.

5 participants