mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-17 18:30:45 +00:00
Improve txpool loop synchronization around background resets. This change: - adds an explicit termination channel to signal pool shutdown - tracks forced-reset intent and a waiter channel inside the reset loop - ensures reset waiters are notified on completion or on pool termination - allows an explicit sync request path to trigger an additional reset round when needed Scope is limited to internal txpool concurrency control in core/txpool/txpool.go, with no protocol or RPC behavior change.
This commit is contained in:
parent
2137fb3e8d
commit
a9be217e6e
1 changed files with 79 additions and 2 deletions
|
|
@ -17,6 +17,7 @@
|
||||||
package txpool
|
package txpool
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"maps"
|
"maps"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
@ -57,6 +58,9 @@ type TxPool struct {
|
||||||
|
|
||||||
subs event.SubscriptionScope // Subscription scope to unsubscribe all on shutdown
|
subs event.SubscriptionScope // Subscription scope to unsubscribe all on shutdown
|
||||||
quit chan chan error // Quit channel to tear down the head updater
|
quit chan chan error // Quit channel to tear down the head updater
|
||||||
|
term chan struct{} // Termination channel to detect a closed pool
|
||||||
|
|
||||||
|
sync chan chan error // Testing / simulator channel to block until internal reset is done
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new transaction pool to gather, sort and filter inbound
|
// New creates a new transaction pool to gather, sort and filter inbound
|
||||||
|
|
@ -70,6 +74,8 @@ func New(gasTip uint64, chain BlockChain, subpools []SubPool) (*TxPool, error) {
|
||||||
pool := &TxPool{
|
pool := &TxPool{
|
||||||
subpools: subpools,
|
subpools: subpools,
|
||||||
quit: make(chan chan error),
|
quit: make(chan chan error),
|
||||||
|
term: make(chan struct{}),
|
||||||
|
sync: make(chan chan error),
|
||||||
}
|
}
|
||||||
reserver := NewReservationTracker()
|
reserver := NewReservationTracker()
|
||||||
for i, subpool := range subpools {
|
for i, subpool := range subpools {
|
||||||
|
|
@ -113,6 +119,9 @@ func (p *TxPool) Close() error {
|
||||||
// outside blockchain events as well as for various reporting and transaction
|
// outside blockchain events as well as for various reporting and transaction
|
||||||
// eviction events.
|
// eviction events.
|
||||||
func (p *TxPool) loop(head *types.Header, chain BlockChain) {
|
func (p *TxPool) loop(head *types.Header, chain BlockChain) {
|
||||||
|
// Close the termination marker when the pool stops
|
||||||
|
defer close(p.term)
|
||||||
|
|
||||||
// Subscribe to chain head events to trigger subpool resets
|
// Subscribe to chain head events to trigger subpool resets
|
||||||
var (
|
var (
|
||||||
newHeadCh = make(chan core.ChainHeadEvent)
|
newHeadCh = make(chan core.ChainHeadEvent)
|
||||||
|
|
@ -129,13 +138,26 @@ func (p *TxPool) loop(head *types.Header, chain BlockChain) {
|
||||||
var (
|
var (
|
||||||
resetBusy = make(chan struct{}, 1) // Allow 1 reset to run concurrently
|
resetBusy = make(chan struct{}, 1) // Allow 1 reset to run concurrently
|
||||||
resetDone = make(chan *types.Header)
|
resetDone = make(chan *types.Header)
|
||||||
|
|
||||||
|
resetForced bool // Whether a forced reset was requested, only used in simulator mode
|
||||||
|
resetWaiter chan error // Channel waiting on a forced reset, only used in simulator mode
|
||||||
)
|
)
|
||||||
|
// Notify the live reset waiter without blocking if the txpool is closed.
|
||||||
|
defer func() {
|
||||||
|
if resetWaiter != nil {
|
||||||
|
select {
|
||||||
|
case resetWaiter <- errors.New("pool already terminated"):
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
resetWaiter = nil
|
||||||
|
}
|
||||||
|
}()
|
||||||
var errc chan error
|
var errc chan error
|
||||||
for errc == nil {
|
for errc == nil {
|
||||||
// Something interesting might have happened, run a reset if there is
|
// Something interesting might have happened, run a reset if there is
|
||||||
// one needed but none is running. The resetter will run on its own
|
// one needed but none is running. The resetter will run on its own
|
||||||
// goroutine to allow chain head events to be consumed contiguously.
|
// goroutine to allow chain head events to be consumed contiguously.
|
||||||
if newHead != oldHead {
|
if newHead != oldHead || resetForced {
|
||||||
// Try to inject a busy marker and start a reset if successful
|
// Try to inject a busy marker and start a reset if successful
|
||||||
select {
|
select {
|
||||||
case resetBusy <- struct{}{}:
|
case resetBusy <- struct{}{}:
|
||||||
|
|
@ -147,8 +169,17 @@ func (p *TxPool) loop(head *types.Header, chain BlockChain) {
|
||||||
resetDone <- newHead
|
resetDone <- newHead
|
||||||
}(oldHead, newHead)
|
}(oldHead, newHead)
|
||||||
|
|
||||||
|
// If the reset operation was explicitly requested, consider it
|
||||||
|
// being fulfilled and drop the request marker. If it was not,
|
||||||
|
// this is a noop.
|
||||||
|
resetForced = false
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Reset already running, wait until it finishes
|
// Reset already running, wait until it finishes.
|
||||||
|
//
|
||||||
|
// Note, this will not drop any forced reset request. If a forced
|
||||||
|
// reset was requested, but we were busy, then when the currently
|
||||||
|
// running reset finishes, a new one will be spun up.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Wait for the next chain head event or a previous reset finish
|
// Wait for the next chain head event or a previous reset finish
|
||||||
|
|
@ -162,8 +193,37 @@ func (p *TxPool) loop(head *types.Header, chain BlockChain) {
|
||||||
oldHead = head
|
oldHead = head
|
||||||
<-resetBusy
|
<-resetBusy
|
||||||
|
|
||||||
|
// If someone is waiting for a reset to finish, notify them, unless
|
||||||
|
// the forced op is still pending. In that case, wait another round
|
||||||
|
// of resets.
|
||||||
|
if resetWaiter != nil && !resetForced {
|
||||||
|
select {
|
||||||
|
case resetWaiter <- nil:
|
||||||
|
// notification delivered
|
||||||
|
default:
|
||||||
|
// no active listener; avoid blocking the event loop
|
||||||
|
}
|
||||||
|
resetWaiter = nil
|
||||||
|
}
|
||||||
|
|
||||||
case errc = <-p.quit:
|
case errc = <-p.quit:
|
||||||
// Termination requested, break out on the next loop round
|
// Termination requested, break out on the next loop round
|
||||||
|
|
||||||
|
case syncc := <-p.sync:
|
||||||
|
// Transaction pool is running inside a simulator, and we are about
|
||||||
|
// to create a new block. Request a forced sync operation to ensure
|
||||||
|
// that any running reset operation finishes to make block imports
|
||||||
|
// deterministic. On top of that, run a new reset operation to make
|
||||||
|
// transaction insertions deterministic instead of being stuck in a
|
||||||
|
// queue waiting for a reset.
|
||||||
|
if resetWaiter != nil {
|
||||||
|
// A previous sync waiter is still pending; notify it to avoid
|
||||||
|
// leaking a goroutine waiting on the old channel.
|
||||||
|
resetWaiter <- errors.New("sync request superseded by a new request")
|
||||||
|
resetWaiter = nil
|
||||||
|
}
|
||||||
|
resetForced = true
|
||||||
|
resetWaiter = syncc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Notify the closer of termination (no error possible for now)
|
// Notify the closer of termination (no error possible for now)
|
||||||
|
|
@ -375,3 +435,20 @@ func (pool *TxPool) IsSigner(addr common.Address) bool {
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sync is a helper method for unit tests or simulator runs where the chain events
|
||||||
|
// are arriving in quick succession, without any time in between them to run the
|
||||||
|
// internal background reset operations. This method will run an explicit reset
|
||||||
|
// operation to ensure the pool stabilises, thus avoiding flakey behavior.
|
||||||
|
//
|
||||||
|
// Note, do not use this in production / live code. In live code, the pool is
|
||||||
|
// meant to reset on a separate thread to avoid DoS vectors.
|
||||||
|
func (p *TxPool) Sync() error {
|
||||||
|
sync := make(chan error)
|
||||||
|
select {
|
||||||
|
case p.sync <- sync:
|
||||||
|
return <-sync
|
||||||
|
case <-p.term:
|
||||||
|
return errors.New("pool already terminated")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue