go-ethereum/core/state/trie_prefetcher.libevm_test.go
Arran Schlosberg 4feb960086
feat(core/state): async trie prefetching (#76)
## Why this should be merged

Performs trie prefetching concurrently, required for equivalent
performance with `coreth` / `subnet-evm` implementations.

## How this works

`StateDB.StartPrefetcher()` accepts variadic options (for backwards
compatibility of function signatures). An option to specify a
`WorkerPool` is provided which, if present, is used to call
`Trie.Get{Account,Storage}()`; the pool is responsible for concurrency
but does not need to be able to wait on the work as that is handled by
this change.

## How this was tested

Unit test demonstrating hand-off of work to a `WorkerPool` as well as
API-guaranteed ordering of events.
2024-11-26 08:01:47 -08:00

80 lines
2.3 KiB
Go

// Copyright 2024 the libevm authors.
//
// The libevm additions to go-ethereum are free software: you can redistribute
// them and/or modify them under the terms of the GNU Lesser General Public License
// as published by the Free Software Foundation, either version 3 of the License,
// or (at your option) any later version.
//
// The libevm additions are distributed in the hope that they will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
// General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see
// <http://www.gnu.org/licenses/>.
package state
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/ava-labs/libevm/common"
)
type synchronisingWorkerPool struct {
t *testing.T
executed, unblock chan struct{}
done bool
preconditionsToStopPrefetcher int
}
var _ WorkerPool = (*synchronisingWorkerPool)(nil)
func (p *synchronisingWorkerPool) Execute(fn func()) {
fn()
select {
case <-p.executed:
default:
close(p.executed)
}
<-p.unblock
assert.False(p.t, p.done, "Done() called before Execute() returns")
p.preconditionsToStopPrefetcher++
}
func (p *synchronisingWorkerPool) Done() {
p.done = true
p.preconditionsToStopPrefetcher++
}
func TestStopPrefetcherWaitsOnWorkers(t *testing.T) {
pool := &synchronisingWorkerPool{
t: t,
executed: make(chan struct{}),
unblock: make(chan struct{}),
}
opt := WithWorkerPools(func() WorkerPool { return pool })
db := filledStateDB()
db.prefetcher = newTriePrefetcher(db.db, db.originalRoot, "", opt)
db.prefetcher.prefetch(common.Hash{}, common.Hash{}, common.Address{}, [][]byte{{}})
go func() {
<-pool.executed
// Sleep otherwise there is a small chance that we close pool.unblock
// between db.StopPrefetcher() returning and the assertion.
time.Sleep(time.Second)
close(pool.unblock)
}()
<-pool.executed
db.StopPrefetcher()
// If this errors then either Execute() hadn't returned or Done() wasn't
// called.
assert.Equalf(t, 2, pool.preconditionsToStopPrefetcher, "%T.StopPrefetcher() returned early", db)
}