mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-24 21:56:43 +00:00
triedb/pathdb, eth: use double-buffer mechanism in pathdb
This commit is contained in:
parent
ac50181b74
commit
761ac5d45f
12 changed files with 302 additions and 122 deletions
|
|
@ -242,6 +242,18 @@ func (t *testHelper) Commit() common.Hash {
|
||||||
}
|
}
|
||||||
t.triedb.Update(root, types.EmptyRootHash, 0, t.nodes, t.states)
|
t.triedb.Update(root, types.EmptyRootHash, 0, t.nodes, t.states)
|
||||||
t.triedb.Commit(root, false)
|
t.triedb.Commit(root, false)
|
||||||
|
|
||||||
|
// re-open the trie database to ensure the frozen buffer
|
||||||
|
// is not referenced
|
||||||
|
//config := &triedb.Config{}
|
||||||
|
//if t.triedb.Scheme() == rawdb.PathScheme {
|
||||||
|
// config.PathDB = &pathdb.Config{
|
||||||
|
// SnapshotNoBuild: true,
|
||||||
|
// } // disable caching
|
||||||
|
//} else {
|
||||||
|
// config.HashDB = &hashdb.Config{} // disable caching
|
||||||
|
//}
|
||||||
|
//t.triedb = triedb.NewDatabase(t.triedb.Disk(), config)
|
||||||
return root
|
return root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -974,20 +974,23 @@ func TestMissingTrieNodes(t *testing.T) {
|
||||||
func testMissingTrieNodes(t *testing.T, scheme string) {
|
func testMissingTrieNodes(t *testing.T, scheme string) {
|
||||||
// Create an initial state with a few accounts
|
// Create an initial state with a few accounts
|
||||||
var (
|
var (
|
||||||
tdb *triedb.Database
|
tdb *triedb.Database
|
||||||
memDb = rawdb.NewMemoryDatabase()
|
memDb = rawdb.NewMemoryDatabase()
|
||||||
|
openDb = func() *triedb.Database {
|
||||||
|
if scheme == rawdb.PathScheme {
|
||||||
|
return triedb.NewDatabase(memDb, &triedb.Config{PathDB: &pathdb.Config{
|
||||||
|
TrieCleanSize: 0,
|
||||||
|
StateCleanSize: 0,
|
||||||
|
WriteBufferSize: 0,
|
||||||
|
}}) // disable caching
|
||||||
|
} else {
|
||||||
|
return triedb.NewDatabase(memDb, &triedb.Config{HashDB: &hashdb.Config{
|
||||||
|
CleanCacheSize: 0,
|
||||||
|
}}) // disable caching
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
if scheme == rawdb.PathScheme {
|
tdb = openDb()
|
||||||
tdb = triedb.NewDatabase(memDb, &triedb.Config{PathDB: &pathdb.Config{
|
|
||||||
TrieCleanSize: 0,
|
|
||||||
StateCleanSize: 0,
|
|
||||||
WriteBufferSize: 0,
|
|
||||||
}}) // disable caching
|
|
||||||
} else {
|
|
||||||
tdb = triedb.NewDatabase(memDb, &triedb.Config{HashDB: &hashdb.Config{
|
|
||||||
CleanCacheSize: 0,
|
|
||||||
}}) // disable caching
|
|
||||||
}
|
|
||||||
db := NewDatabase(tdb, nil)
|
db := NewDatabase(tdb, nil)
|
||||||
|
|
||||||
var root common.Hash
|
var root common.Hash
|
||||||
|
|
@ -1005,17 +1008,27 @@ func testMissingTrieNodes(t *testing.T, scheme string) {
|
||||||
tdb.Commit(root, false)
|
tdb.Commit(root, false)
|
||||||
}
|
}
|
||||||
// Create a new state on the old root
|
// Create a new state on the old root
|
||||||
state, _ = New(root, db)
|
|
||||||
// Now we clear out the memdb
|
// Now we clear out the memdb
|
||||||
it := memDb.NewIterator(nil, nil)
|
it := memDb.NewIterator(nil, nil)
|
||||||
for it.Next() {
|
for it.Next() {
|
||||||
k := it.Key()
|
k := it.Key()
|
||||||
// Leave the root intact
|
if scheme == rawdb.HashScheme {
|
||||||
if !bytes.Equal(k, root[:]) {
|
if !bytes.Equal(k, root[:]) {
|
||||||
t.Logf("key: %x", k)
|
t.Logf("key: %x", k)
|
||||||
memDb.Delete(k)
|
memDb.Delete(k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if scheme == rawdb.PathScheme {
|
||||||
|
rk := k[len(rawdb.TrieNodeAccountPrefix):]
|
||||||
|
if len(rk) != 0 {
|
||||||
|
t.Logf("key: %x", k)
|
||||||
|
memDb.Delete(k)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
tdb = openDb()
|
||||||
|
db = NewDatabase(tdb, nil)
|
||||||
|
state, _ = New(root, db)
|
||||||
balance := state.GetBalance(addr)
|
balance := state.GetBalance(addr)
|
||||||
// The removed elem should lead to it returning zero balance
|
// The removed elem should lead to it returning zero balance
|
||||||
if exp, got := uint64(0), balance.Uint64(); got != exp {
|
if exp, got := uint64(0), balance.Uint64(); got != exp {
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package pathdb
|
package pathdb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -37,6 +38,9 @@ type buffer struct {
|
||||||
limit uint64 // The maximum memory allowance in bytes
|
limit uint64 // The maximum memory allowance in bytes
|
||||||
nodes *nodeSet // Aggregated trie node set
|
nodes *nodeSet // Aggregated trie node set
|
||||||
states *stateSet // Aggregated state set
|
states *stateSet // Aggregated state set
|
||||||
|
|
||||||
|
done chan struct{} // notifier whether the content in buffer has been flushed or not
|
||||||
|
flushErr error // error if any exception occurs during flushing
|
||||||
}
|
}
|
||||||
|
|
||||||
// newBuffer initializes the buffer with the provided states and trie nodes.
|
// newBuffer initializes the buffer with the provided states and trie nodes.
|
||||||
|
|
@ -124,43 +128,74 @@ func (b *buffer) size() uint64 {
|
||||||
|
|
||||||
// flush persists the in-memory dirty trie node into the disk if the configured
|
// flush persists the in-memory dirty trie node into the disk if the configured
|
||||||
// memory threshold is reached. Note, all data must be written atomically.
|
// memory threshold is reached. Note, all data must be written atomically.
|
||||||
func (b *buffer) flush(root common.Hash, db ethdb.KeyValueStore, freezer ethdb.AncientWriter, progress []byte, nodesCache, statesCache *fastcache.Cache, id uint64) error {
|
func (b *buffer) flush(root common.Hash, db ethdb.KeyValueStore, freezer ethdb.AncientWriter, progress []byte, nodesCache, statesCache *fastcache.Cache, id uint64, postFlush func()) {
|
||||||
// Ensure the target state id is aligned with the internal counter.
|
if b.done != nil {
|
||||||
head := rawdb.ReadPersistentStateID(db)
|
panic("duplicated flush operation")
|
||||||
if head+b.layers != id {
|
|
||||||
return fmt.Errorf("buffer layers (%d) cannot be applied on top of persisted state id (%d) to reach requested state id (%d)", b.layers, head, id)
|
|
||||||
}
|
}
|
||||||
// Terminate the state snapshot generation if it's active
|
b.done = make(chan struct{}) // allocate the channel for notification
|
||||||
var (
|
|
||||||
start = time.Now()
|
|
||||||
batch = db.NewBatchWithSize((b.nodes.dbsize() + b.states.dbsize()) * 11 / 10) // extra 10% for potential pebble internal stuff
|
|
||||||
)
|
|
||||||
// Explicitly sync the state freezer to ensure all written data is persisted to disk
|
|
||||||
// before updating the key-value store.
|
|
||||||
//
|
|
||||||
// This step is crucial to guarantee that the corresponding state history remains
|
|
||||||
// available for state rollback.
|
|
||||||
if freezer != nil {
|
|
||||||
if err := freezer.SyncAncient(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
nodes := b.nodes.write(batch, nodesCache)
|
|
||||||
accounts, slots := b.states.write(batch, progress, statesCache)
|
|
||||||
rawdb.WritePersistentStateID(batch, id)
|
|
||||||
rawdb.WriteSnapshotRoot(batch, root)
|
|
||||||
|
|
||||||
// Flush all mutations in a single batch
|
go func() {
|
||||||
size := batch.ValueSize()
|
defer func() {
|
||||||
if err := batch.Write(); err != nil {
|
if postFlush != nil {
|
||||||
return err
|
postFlush()
|
||||||
}
|
}
|
||||||
commitBytesMeter.Mark(int64(size))
|
close(b.done)
|
||||||
commitNodesMeter.Mark(int64(nodes))
|
}()
|
||||||
commitAccountsMeter.Mark(int64(accounts))
|
|
||||||
commitStoragesMeter.Mark(int64(slots))
|
// Ensure the target state id is aligned with the internal counter.
|
||||||
commitTimeTimer.UpdateSince(start)
|
head := rawdb.ReadPersistentStateID(db)
|
||||||
b.reset()
|
if head+b.layers != id {
|
||||||
log.Debug("Persisted buffer content", "nodes", nodes, "accounts", accounts, "slots", slots, "bytes", common.StorageSize(size), "elapsed", common.PrettyDuration(time.Since(start)))
|
b.flushErr = fmt.Errorf("buffer layers (%d) cannot be applied on top of persisted state id (%d) to reach requested state id (%d)", b.layers, head, id)
|
||||||
return nil
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Terminate the state snapshot generation if it's active
|
||||||
|
var (
|
||||||
|
start = time.Now()
|
||||||
|
batch = db.NewBatchWithSize((b.nodes.dbsize() + b.states.dbsize()) * 11 / 10) // extra 10% for potential pebble internal stuff
|
||||||
|
)
|
||||||
|
// Explicitly sync the state freezer to ensure all written data is persisted to disk
|
||||||
|
// before updating the key-value store.
|
||||||
|
//
|
||||||
|
// This step is crucial to guarantee that the corresponding state history remains
|
||||||
|
// available for state rollback.
|
||||||
|
if freezer != nil {
|
||||||
|
if err := freezer.SyncAncient(); err != nil {
|
||||||
|
b.flushErr = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nodes := b.nodes.write(batch, nodesCache)
|
||||||
|
accounts, slots := b.states.write(batch, progress, statesCache)
|
||||||
|
rawdb.WritePersistentStateID(batch, id)
|
||||||
|
rawdb.WriteSnapshotRoot(batch, root)
|
||||||
|
|
||||||
|
// Flush all mutations in a single batch
|
||||||
|
size := batch.ValueSize()
|
||||||
|
if err := batch.Write(); err != nil {
|
||||||
|
b.flushErr = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
commitBytesMeter.Mark(int64(size))
|
||||||
|
commitNodesMeter.Mark(int64(nodes))
|
||||||
|
commitAccountsMeter.Mark(int64(accounts))
|
||||||
|
commitStoragesMeter.Mark(int64(slots))
|
||||||
|
commitTimeTimer.UpdateSince(start)
|
||||||
|
|
||||||
|
// The content in the frozen buffer is kept for consequent state access,
|
||||||
|
// TODO (rjl493456442) measure the gc overhead for holding this struct.
|
||||||
|
// TODO (rjl493456442) can we somehow get rid of it after flushing??
|
||||||
|
b.reset()
|
||||||
|
log.Debug("Persisted buffer content", "nodes", nodes, "accounts", accounts, "slots", slots, "bytes", common.StorageSize(size), "elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// waitFlush blocks until the buffer has been fully flushed and returns any
|
||||||
|
// stored errors that occurred during the process.
|
||||||
|
func (b *buffer) waitFlush() error {
|
||||||
|
if b.done == nil {
|
||||||
|
return errors.New("the buffer is not frozen")
|
||||||
|
}
|
||||||
|
<-b.done
|
||||||
|
return b.flushErr
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -119,7 +119,10 @@ type Config struct {
|
||||||
StateCleanSize int // Maximum memory allowance (in bytes) for caching clean state data
|
StateCleanSize int // Maximum memory allowance (in bytes) for caching clean state data
|
||||||
WriteBufferSize int // Maximum memory allowance (in bytes) for write buffer
|
WriteBufferSize int // Maximum memory allowance (in bytes) for write buffer
|
||||||
ReadOnly bool // Flag whether the database is opened in read only mode
|
ReadOnly bool // Flag whether the database is opened in read only mode
|
||||||
SnapshotNoBuild bool // Flag Whether the background generation is allowed
|
|
||||||
|
// Testing configurations
|
||||||
|
SnapshotNoBuild bool // Flag Whether the background generation is allowed
|
||||||
|
NoAsyncFlush bool // Flag whether the background generation is allowed
|
||||||
}
|
}
|
||||||
|
|
||||||
// sanitize checks the provided user configurations and changes anything that's
|
// sanitize checks the provided user configurations and changes anything that's
|
||||||
|
|
@ -434,6 +437,9 @@ func (db *Database) Disable() error {
|
||||||
// Terminate the state generator if it's active and mark the disk layer
|
// Terminate the state generator if it's active and mark the disk layer
|
||||||
// as stale to prevent access to persistent state.
|
// as stale to prevent access to persistent state.
|
||||||
disk := db.tree.bottom()
|
disk := db.tree.bottom()
|
||||||
|
if err := disk.waitFlush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if disk.generator != nil {
|
if disk.generator != nil {
|
||||||
disk.generator.stop()
|
disk.generator.stop()
|
||||||
}
|
}
|
||||||
|
|
@ -592,12 +598,18 @@ func (db *Database) Close() error {
|
||||||
// following mutations.
|
// following mutations.
|
||||||
db.readOnly = true
|
db.readOnly = true
|
||||||
|
|
||||||
// Terminate the background generation if it's active
|
// Block until the background flushing is finished. It must
|
||||||
disk := db.tree.bottom()
|
// be done before terminating the potential background snapshot
|
||||||
if disk.generator != nil {
|
// generator.
|
||||||
disk.generator.stop()
|
dl := db.tree.bottom()
|
||||||
|
if err := dl.waitFlush(); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
disk.resetCache() // release the memory held by clean cache
|
// Terminate the background generation if it's active
|
||||||
|
if dl.generator != nil {
|
||||||
|
dl.generator.stop()
|
||||||
|
}
|
||||||
|
dl.resetCache() // release the memory held by clean cache
|
||||||
|
|
||||||
// Close the attached state history freezer.
|
// Close the attached state history freezer.
|
||||||
if db.freezer == nil {
|
if db.freezer == nil {
|
||||||
|
|
|
||||||
|
|
@ -129,6 +129,7 @@ func newTester(t *testing.T, historyLimit uint64, isVerkle bool, layers int) *te
|
||||||
TrieCleanSize: 256 * 1024,
|
TrieCleanSize: 256 * 1024,
|
||||||
StateCleanSize: 256 * 1024,
|
StateCleanSize: 256 * 1024,
|
||||||
WriteBufferSize: 256 * 1024,
|
WriteBufferSize: 256 * 1024,
|
||||||
|
NoAsyncFlush: true,
|
||||||
}, isVerkle)
|
}, isVerkle)
|
||||||
|
|
||||||
obj = &tester{
|
obj = &tester{
|
||||||
|
|
|
||||||
|
|
@ -41,9 +41,11 @@ type diskLayer struct {
|
||||||
nodes *fastcache.Cache // GC friendly memory cache of clean nodes
|
nodes *fastcache.Cache // GC friendly memory cache of clean nodes
|
||||||
states *fastcache.Cache // GC friendly memory cache of clean states
|
states *fastcache.Cache // GC friendly memory cache of clean states
|
||||||
|
|
||||||
buffer *buffer // Dirty buffer to aggregate writes of nodes and states
|
buffer *buffer // Live buffer to aggregate writes
|
||||||
stale bool // Signals that the layer became stale (state progressed)
|
frozen *buffer // Frozen node buffer waiting for flushing
|
||||||
lock sync.RWMutex // Lock used to protect stale flag and genMarker
|
|
||||||
|
stale bool // Signals that the layer became stale (state progressed)
|
||||||
|
lock sync.RWMutex // Lock used to protect stale flag and genMarker
|
||||||
|
|
||||||
// The generator is set if the state snapshot was not fully completed,
|
// The generator is set if the state snapshot was not fully completed,
|
||||||
// regardless of whether the background generation is running or not.
|
// regardless of whether the background generation is running or not.
|
||||||
|
|
@ -51,7 +53,7 @@ type diskLayer struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// newDiskLayer creates a new disk layer based on the passing arguments.
|
// newDiskLayer creates a new disk layer based on the passing arguments.
|
||||||
func newDiskLayer(root common.Hash, id uint64, db *Database, nodes *fastcache.Cache, states *fastcache.Cache, buffer *buffer) *diskLayer {
|
func newDiskLayer(root common.Hash, id uint64, db *Database, nodes *fastcache.Cache, states *fastcache.Cache, buffer *buffer, frozen *buffer) *diskLayer {
|
||||||
// Initialize the clean caches if the memory allowance is not zero
|
// Initialize the clean caches if the memory allowance is not zero
|
||||||
// or reuse the provided caches if they are not nil (inherited from
|
// or reuse the provided caches if they are not nil (inherited from
|
||||||
// the original disk layer).
|
// the original disk layer).
|
||||||
|
|
@ -68,6 +70,7 @@ func newDiskLayer(root common.Hash, id uint64, db *Database, nodes *fastcache.Ca
|
||||||
nodes: nodes,
|
nodes: nodes,
|
||||||
states: states,
|
states: states,
|
||||||
buffer: buffer,
|
buffer: buffer,
|
||||||
|
frozen: frozen,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -114,16 +117,19 @@ func (dl *diskLayer) node(owner common.Hash, path []byte, depth int) ([]byte, co
|
||||||
if dl.stale {
|
if dl.stale {
|
||||||
return nil, common.Hash{}, nil, errSnapshotStale
|
return nil, common.Hash{}, nil, errSnapshotStale
|
||||||
}
|
}
|
||||||
// Try to retrieve the trie node from the not-yet-written
|
// Try to retrieve the trie node from the not-yet-written node buffer first
|
||||||
// node buffer first. Note the buffer is lock free since
|
// (both the live one and the frozen one). Note the buffer is lock free since
|
||||||
// it's impossible to mutate the buffer before tagging the
|
// it's impossible to mutate the buffer before tagging the layer as stale.
|
||||||
// layer as stale.
|
for _, buffer := range []*buffer{dl.buffer, dl.frozen} {
|
||||||
n, found := dl.buffer.node(owner, path)
|
if buffer != nil && !buffer.empty() {
|
||||||
if found {
|
n, found := buffer.node(owner, path)
|
||||||
dirtyNodeHitMeter.Mark(1)
|
if found {
|
||||||
dirtyNodeReadMeter.Mark(int64(len(n.Blob)))
|
dirtyNodeHitMeter.Mark(1)
|
||||||
dirtyNodeHitDepthHist.Update(int64(depth))
|
dirtyNodeReadMeter.Mark(int64(len(n.Blob)))
|
||||||
return n.Blob, n.Hash, &nodeLoc{loc: locDirtyCache, depth: depth}, nil
|
dirtyNodeHitDepthHist.Update(int64(depth))
|
||||||
|
return n.Blob, n.Hash, &nodeLoc{loc: locDirtyCache, depth: depth}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
dirtyNodeMissMeter.Mark(1)
|
dirtyNodeMissMeter.Mark(1)
|
||||||
|
|
||||||
|
|
@ -144,6 +150,11 @@ func (dl *diskLayer) node(owner common.Hash, path []byte, depth int) ([]byte, co
|
||||||
} else {
|
} else {
|
||||||
blob = rawdb.ReadStorageTrieNode(dl.db.diskdb, owner, path)
|
blob = rawdb.ReadStorageTrieNode(dl.db.diskdb, owner, path)
|
||||||
}
|
}
|
||||||
|
// Store the resolved data in the clean cache. The background buffer flusher
|
||||||
|
// may also write to the clean cache concurrently, but two writers cannot
|
||||||
|
// write the same item with different content. If the item already exists,
|
||||||
|
// it will be found in the frozen buffer, eliminating the need to check the
|
||||||
|
// database.
|
||||||
if dl.nodes != nil && len(blob) > 0 {
|
if dl.nodes != nil && len(blob) > 0 {
|
||||||
dl.nodes.Set(key, blob)
|
dl.nodes.Set(key, blob)
|
||||||
cleanNodeWriteMeter.Mark(int64(len(blob)))
|
cleanNodeWriteMeter.Mark(int64(len(blob)))
|
||||||
|
|
@ -162,22 +173,25 @@ func (dl *diskLayer) account(hash common.Hash, depth int) ([]byte, error) {
|
||||||
if dl.stale {
|
if dl.stale {
|
||||||
return nil, errSnapshotStale
|
return nil, errSnapshotStale
|
||||||
}
|
}
|
||||||
// Try to retrieve the account from the not-yet-written
|
// Try to retrieve the trie node from the not-yet-written node buffer first
|
||||||
// node buffer first. Note the buffer is lock free since
|
// (both the live one and the frozen one). Note the buffer is lock free since
|
||||||
// it's impossible to mutate the buffer before tagging the
|
// it's impossible to mutate the buffer before tagging the layer as stale.
|
||||||
// layer as stale.
|
for _, buffer := range []*buffer{dl.buffer, dl.frozen} {
|
||||||
blob, found := dl.buffer.account(hash)
|
if buffer != nil && !buffer.empty() {
|
||||||
if found {
|
blob, found := buffer.account(hash)
|
||||||
dirtyStateHitMeter.Mark(1)
|
if found {
|
||||||
dirtyStateReadMeter.Mark(int64(len(blob)))
|
dirtyStateHitMeter.Mark(1)
|
||||||
dirtyStateHitDepthHist.Update(int64(depth))
|
dirtyStateReadMeter.Mark(int64(len(blob)))
|
||||||
|
dirtyStateHitDepthHist.Update(int64(depth))
|
||||||
|
|
||||||
if len(blob) == 0 {
|
if len(blob) == 0 {
|
||||||
stateAccountInexMeter.Mark(1)
|
stateAccountInexMeter.Mark(1)
|
||||||
} else {
|
} else {
|
||||||
stateAccountExistMeter.Mark(1)
|
stateAccountExistMeter.Mark(1)
|
||||||
|
}
|
||||||
|
return blob, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return blob, nil
|
|
||||||
}
|
}
|
||||||
dirtyStateMissMeter.Mark(1)
|
dirtyStateMissMeter.Mark(1)
|
||||||
|
|
||||||
|
|
@ -203,7 +217,13 @@ func (dl *diskLayer) account(hash common.Hash, depth int) ([]byte, error) {
|
||||||
cleanStateMissMeter.Mark(1)
|
cleanStateMissMeter.Mark(1)
|
||||||
}
|
}
|
||||||
// Try to retrieve the account from the disk.
|
// Try to retrieve the account from the disk.
|
||||||
blob = rawdb.ReadAccountSnapshot(dl.db.diskdb, hash)
|
blob := rawdb.ReadAccountSnapshot(dl.db.diskdb, hash)
|
||||||
|
|
||||||
|
// Store the resolved data in the clean cache. The background buffer flusher
|
||||||
|
// may also write to the clean cache concurrently, but two writers cannot
|
||||||
|
// write the same item with different content. If the item already exists,
|
||||||
|
// it will be found in the frozen buffer, eliminating the need to check the
|
||||||
|
// database.
|
||||||
if dl.states != nil {
|
if dl.states != nil {
|
||||||
dl.states.Set(hash[:], blob)
|
dl.states.Set(hash[:], blob)
|
||||||
cleanStateWriteMeter.Mark(int64(len(blob)))
|
cleanStateWriteMeter.Mark(int64(len(blob)))
|
||||||
|
|
@ -231,21 +251,24 @@ func (dl *diskLayer) storage(accountHash, storageHash common.Hash, depth int) ([
|
||||||
if dl.stale {
|
if dl.stale {
|
||||||
return nil, errSnapshotStale
|
return nil, errSnapshotStale
|
||||||
}
|
}
|
||||||
// Try to retrieve the storage slot from the not-yet-written
|
// Try to retrieve the trie node from the not-yet-written node buffer first
|
||||||
// node buffer first. Note the buffer is lock free since
|
// (both the live one and the frozen one). Note the buffer is lock free since
|
||||||
// it's impossible to mutate the buffer before tagging the
|
// it's impossible to mutate the buffer before tagging the layer as stale.
|
||||||
// layer as stale.
|
for _, buffer := range []*buffer{dl.buffer, dl.frozen} {
|
||||||
if blob, found := dl.buffer.storage(accountHash, storageHash); found {
|
if buffer != nil && !buffer.empty() {
|
||||||
dirtyStateHitMeter.Mark(1)
|
if blob, found := buffer.storage(accountHash, storageHash); found {
|
||||||
dirtyStateReadMeter.Mark(int64(len(blob)))
|
dirtyStateHitMeter.Mark(1)
|
||||||
dirtyStateHitDepthHist.Update(int64(depth))
|
dirtyStateReadMeter.Mark(int64(len(blob)))
|
||||||
|
dirtyStateHitDepthHist.Update(int64(depth))
|
||||||
|
|
||||||
if len(blob) == 0 {
|
if len(blob) == 0 {
|
||||||
stateStorageInexMeter.Mark(1)
|
stateStorageInexMeter.Mark(1)
|
||||||
} else {
|
} else {
|
||||||
stateStorageExistMeter.Mark(1)
|
stateStorageExistMeter.Mark(1)
|
||||||
|
}
|
||||||
|
return blob, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return blob, nil
|
|
||||||
}
|
}
|
||||||
dirtyStateMissMeter.Mark(1)
|
dirtyStateMissMeter.Mark(1)
|
||||||
|
|
||||||
|
|
@ -273,6 +296,12 @@ func (dl *diskLayer) storage(accountHash, storageHash common.Hash, depth int) ([
|
||||||
}
|
}
|
||||||
// Try to retrieve the account from the disk
|
// Try to retrieve the account from the disk
|
||||||
blob := rawdb.ReadStorageSnapshot(dl.db.diskdb, accountHash, storageHash)
|
blob := rawdb.ReadStorageSnapshot(dl.db.diskdb, accountHash, storageHash)
|
||||||
|
|
||||||
|
// Store the resolved data in the clean cache. The background buffer flusher
|
||||||
|
// may also write to the clean cache concurrently, but two writers cannot
|
||||||
|
// write the same item with different content. If the item already exists,
|
||||||
|
// it will be found in the frozen buffer, eliminating the need to check the
|
||||||
|
// database.
|
||||||
if dl.states != nil {
|
if dl.states != nil {
|
||||||
dl.states.Set(key, blob)
|
dl.states.Set(key, blob)
|
||||||
cleanStateWriteMeter.Mark(int64(len(blob)))
|
cleanStateWriteMeter.Mark(int64(len(blob)))
|
||||||
|
|
@ -341,7 +370,8 @@ func (dl *diskLayer) commit(bottom *diffLayer, force bool) (*diskLayer, error) {
|
||||||
// truncation) surpasses the persisted state ID, we take the necessary action
|
// truncation) surpasses the persisted state ID, we take the necessary action
|
||||||
// of forcibly committing the cached dirty states to ensure that the persisted
|
// of forcibly committing the cached dirty states to ensure that the persisted
|
||||||
// state ID remains higher.
|
// state ID remains higher.
|
||||||
if !force && rawdb.ReadPersistentStateID(dl.db.diskdb) < oldest {
|
persistedID := rawdb.ReadPersistentStateID(dl.db.diskdb)
|
||||||
|
if !force && persistedID < oldest {
|
||||||
force = true
|
force = true
|
||||||
}
|
}
|
||||||
// Merge the trie nodes and flat states of the bottom-most diff layer into the
|
// Merge the trie nodes and flat states of the bottom-most diff layer into the
|
||||||
|
|
@ -351,32 +381,67 @@ func (dl *diskLayer) commit(bottom *diffLayer, force bool) (*diskLayer, error) {
|
||||||
// Terminate the background state snapshot generation before mutating the
|
// Terminate the background state snapshot generation before mutating the
|
||||||
// persistent state.
|
// persistent state.
|
||||||
if combined.full() || force {
|
if combined.full() || force {
|
||||||
|
// Wait until the previous frozen buffer is fully flushed
|
||||||
|
if dl.frozen != nil {
|
||||||
|
if err := dl.frozen.waitFlush(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Release the frozen buffer and the internally referenced maps will
|
||||||
|
// be reclaimed by GC.
|
||||||
|
dl.frozen = nil
|
||||||
|
|
||||||
// Terminate the background state snapshot generator before flushing
|
// Terminate the background state snapshot generator before flushing
|
||||||
// to prevent data race.
|
// to prevent data race.
|
||||||
var progress []byte
|
var (
|
||||||
if dl.generator != nil {
|
progress []byte
|
||||||
dl.generator.stop()
|
gen = dl.generator
|
||||||
progress = dl.generator.progressMarker()
|
)
|
||||||
|
if gen != nil {
|
||||||
|
gen.stop()
|
||||||
|
progress = gen.progressMarker()
|
||||||
|
|
||||||
// If the snapshot has been fully generated, unset the generator
|
// If the snapshot has been fully generated, unset the generator
|
||||||
if progress == nil {
|
if progress == nil {
|
||||||
|
gen = nil
|
||||||
dl.setGenerator(nil)
|
dl.setGenerator(nil)
|
||||||
} else {
|
} else {
|
||||||
log.Info("Paused snapshot generation")
|
log.Info("Paused snapshot generation")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Flush the content in combined buffer. Any state data after the progress
|
|
||||||
// marker will be ignored, as the generator will pick it up later.
|
// Freeze the live buffer and schedule background flushing
|
||||||
if err := combined.flush(bottom.root, dl.db.diskdb, dl.db.freezer, progress, dl.nodes, dl.states, bottom.stateID()); err != nil {
|
dl.frozen = combined
|
||||||
return nil, err
|
dl.frozen.flush(bottom.root, dl.db.diskdb, dl.db.freezer, progress, dl.nodes, dl.states, bottom.stateID(), func() {
|
||||||
|
// Resume the background generation if it's not completed yet.
|
||||||
|
// The generator is assumed to be available if the progress is
|
||||||
|
// not nil.
|
||||||
|
//
|
||||||
|
// Notably, the generator will be shared and linked by all the
|
||||||
|
// disk layer instances, regardless of the generation is terminated
|
||||||
|
// or not.
|
||||||
|
if progress != nil {
|
||||||
|
gen.run(bottom.root)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// Block until the frozen buffer is fully flushed out if the async flushing
|
||||||
|
// is not allowed.
|
||||||
|
if dl.db.config.NoAsyncFlush {
|
||||||
|
if err := dl.frozen.waitFlush(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Resume the background generation if it's not completed yet
|
// Block until the frozen buffer is fully flushed out if the oldest history
|
||||||
if progress != nil {
|
// surpasses the persisted state ID.
|
||||||
dl.generator.run(bottom.root)
|
if persistedID < oldest {
|
||||||
|
if err := dl.frozen.waitFlush(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
combined = newBuffer(dl.db.config.WriteBufferSize, nil, nil, 0)
|
||||||
}
|
}
|
||||||
// Link the generator if snapshot is not yet completed
|
// Link the generator if snapshot is not yet completed
|
||||||
ndl := newDiskLayer(bottom.root, bottom.stateID(), dl.db, dl.nodes, dl.states, combined)
|
ndl := newDiskLayer(bottom.root, bottom.stateID(), dl.db, dl.nodes, dl.states, combined, dl.frozen)
|
||||||
if dl.generator != nil {
|
if dl.generator != nil {
|
||||||
ndl.setGenerator(dl.generator)
|
ndl.setGenerator(dl.generator)
|
||||||
}
|
}
|
||||||
|
|
@ -428,7 +493,7 @@ func (dl *diskLayer) revert(h *history) (*diskLayer, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ndl := newDiskLayer(h.meta.parent, dl.id-1, dl.db, dl.nodes, dl.states, dl.buffer)
|
ndl := newDiskLayer(h.meta.parent, dl.id-1, dl.db, dl.nodes, dl.states, dl.buffer, dl.frozen)
|
||||||
|
|
||||||
// Link the generator if it exists
|
// Link the generator if it exists
|
||||||
if dl.generator != nil {
|
if dl.generator != nil {
|
||||||
|
|
@ -437,6 +502,16 @@ func (dl *diskLayer) revert(h *history) (*diskLayer, error) {
|
||||||
log.Debug("Reverted data in write buffer", "oldroot", h.meta.root, "newroot", h.meta.parent, "elapsed", common.PrettyDuration(time.Since(start)))
|
log.Debug("Reverted data in write buffer", "oldroot", h.meta.root, "newroot", h.meta.parent, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
return ndl, nil
|
return ndl, nil
|
||||||
}
|
}
|
||||||
|
// Block until the frozen buffer is fully flushed
|
||||||
|
if dl.frozen != nil {
|
||||||
|
if err := dl.frozen.waitFlush(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Unset the frozen buffer if it exists, otherwise these "reverted"
|
||||||
|
// states will still be accessible after revert in frozen buffer.
|
||||||
|
dl.frozen = nil
|
||||||
|
}
|
||||||
|
|
||||||
// Terminate the generation before writing any data into database
|
// Terminate the generation before writing any data into database
|
||||||
var progress []byte
|
var progress []byte
|
||||||
if dl.generator != nil {
|
if dl.generator != nil {
|
||||||
|
|
@ -455,7 +530,7 @@ func (dl *diskLayer) revert(h *history) (*diskLayer, error) {
|
||||||
}
|
}
|
||||||
// Link the generator and resume generation if the snapshot is not yet
|
// Link the generator and resume generation if the snapshot is not yet
|
||||||
// fully completed.
|
// fully completed.
|
||||||
ndl := newDiskLayer(h.meta.parent, dl.id-1, dl.db, dl.nodes, dl.states, dl.buffer)
|
ndl := newDiskLayer(h.meta.parent, dl.id-1, dl.db, dl.nodes, dl.states, dl.buffer, dl.frozen)
|
||||||
if dl.generator != nil && !dl.generator.completed() {
|
if dl.generator != nil && !dl.generator.completed() {
|
||||||
ndl.generator = dl.generator
|
ndl.generator = dl.generator
|
||||||
ndl.generator.run(h.meta.parent)
|
ndl.generator.run(h.meta.parent)
|
||||||
|
|
@ -500,3 +575,12 @@ func (dl *diskLayer) genMarker() []byte {
|
||||||
}
|
}
|
||||||
return dl.generator.progressMarker()
|
return dl.generator.progressMarker()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// waitFlush blocks until the buffer has been fully flushed and returns any
|
||||||
|
// stored errors that occurred during the process.
|
||||||
|
func (dl *diskLayer) waitFlush() error {
|
||||||
|
if dl.frozen == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return dl.frozen.waitFlush()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -186,7 +186,7 @@ func generateSnapshot(triedb *Database, root common.Hash, noBuild bool) *diskLay
|
||||||
stats = &generatorStats{start: time.Now()}
|
stats = &generatorStats{start: time.Now()}
|
||||||
genMarker = []byte{} // Initialized but empty!
|
genMarker = []byte{} // Initialized but empty!
|
||||||
)
|
)
|
||||||
dl := newDiskLayer(root, 0, triedb, nil, nil, newBuffer(triedb.config.WriteBufferSize, nil, nil, 0))
|
dl := newDiskLayer(root, 0, triedb, nil, nil, newBuffer(triedb.config.WriteBufferSize, nil, nil, 0), nil)
|
||||||
dl.setGenerator(newGenerator(triedb.diskdb, noBuild, genMarker, stats))
|
dl.setGenerator(newGenerator(triedb.diskdb, noBuild, genMarker, stats))
|
||||||
|
|
||||||
if !noBuild {
|
if !noBuild {
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ func newGenTester() *genTester {
|
||||||
disk := rawdb.NewMemoryDatabase()
|
disk := rawdb.NewMemoryDatabase()
|
||||||
config := *Defaults
|
config := *Defaults
|
||||||
config.SnapshotNoBuild = true // no background generation
|
config.SnapshotNoBuild = true // no background generation
|
||||||
|
config.NoAsyncFlush = true // no async flush
|
||||||
db := New(disk, &config, false)
|
db := New(disk, &config, false)
|
||||||
tr, _ := trie.New(trie.StateTrieID(types.EmptyRootHash), db)
|
tr, _ := trie.New(trie.StateTrieID(types.EmptyRootHash), db)
|
||||||
return &genTester{
|
return &genTester{
|
||||||
|
|
|
||||||
|
|
@ -255,6 +255,7 @@ func TestFastIteratorBasics(t *testing.T) {
|
||||||
func TestAccountIteratorTraversal(t *testing.T) {
|
func TestAccountIteratorTraversal(t *testing.T) {
|
||||||
config := &Config{
|
config := &Config{
|
||||||
WriteBufferSize: 0,
|
WriteBufferSize: 0,
|
||||||
|
NoAsyncFlush: true,
|
||||||
}
|
}
|
||||||
db := New(rawdb.NewMemoryDatabase(), config, false)
|
db := New(rawdb.NewMemoryDatabase(), config, false)
|
||||||
db.waitGeneration()
|
db.waitGeneration()
|
||||||
|
|
@ -299,6 +300,7 @@ func TestAccountIteratorTraversal(t *testing.T) {
|
||||||
func TestStorageIteratorTraversal(t *testing.T) {
|
func TestStorageIteratorTraversal(t *testing.T) {
|
||||||
config := &Config{
|
config := &Config{
|
||||||
WriteBufferSize: 0,
|
WriteBufferSize: 0,
|
||||||
|
NoAsyncFlush: true,
|
||||||
}
|
}
|
||||||
db := New(rawdb.NewMemoryDatabase(), config, false)
|
db := New(rawdb.NewMemoryDatabase(), config, false)
|
||||||
db.waitGeneration()
|
db.waitGeneration()
|
||||||
|
|
@ -343,6 +345,7 @@ func TestStorageIteratorTraversal(t *testing.T) {
|
||||||
func TestAccountIteratorTraversalValues(t *testing.T) {
|
func TestAccountIteratorTraversalValues(t *testing.T) {
|
||||||
config := &Config{
|
config := &Config{
|
||||||
WriteBufferSize: 0,
|
WriteBufferSize: 0,
|
||||||
|
NoAsyncFlush: true,
|
||||||
}
|
}
|
||||||
db := New(rawdb.NewMemoryDatabase(), config, false)
|
db := New(rawdb.NewMemoryDatabase(), config, false)
|
||||||
db.waitGeneration()
|
db.waitGeneration()
|
||||||
|
|
@ -459,6 +462,7 @@ func TestAccountIteratorTraversalValues(t *testing.T) {
|
||||||
func TestStorageIteratorTraversalValues(t *testing.T) {
|
func TestStorageIteratorTraversalValues(t *testing.T) {
|
||||||
config := &Config{
|
config := &Config{
|
||||||
WriteBufferSize: 0,
|
WriteBufferSize: 0,
|
||||||
|
NoAsyncFlush: true,
|
||||||
}
|
}
|
||||||
db := New(rawdb.NewMemoryDatabase(), config, false)
|
db := New(rawdb.NewMemoryDatabase(), config, false)
|
||||||
db.waitGeneration()
|
db.waitGeneration()
|
||||||
|
|
@ -592,6 +596,7 @@ func TestAccountIteratorLargeTraversal(t *testing.T) {
|
||||||
// Build up a large stack of snapshots
|
// Build up a large stack of snapshots
|
||||||
config := &Config{
|
config := &Config{
|
||||||
WriteBufferSize: 0,
|
WriteBufferSize: 0,
|
||||||
|
NoAsyncFlush: true,
|
||||||
}
|
}
|
||||||
db := New(rawdb.NewMemoryDatabase(), config, false)
|
db := New(rawdb.NewMemoryDatabase(), config, false)
|
||||||
db.waitGeneration()
|
db.waitGeneration()
|
||||||
|
|
@ -664,6 +669,7 @@ func TestAccountIteratorFlattening(t *testing.T) {
|
||||||
func TestAccountIteratorSeek(t *testing.T) {
|
func TestAccountIteratorSeek(t *testing.T) {
|
||||||
config := &Config{
|
config := &Config{
|
||||||
WriteBufferSize: 0,
|
WriteBufferSize: 0,
|
||||||
|
NoAsyncFlush: true,
|
||||||
}
|
}
|
||||||
db := New(rawdb.NewMemoryDatabase(), config, false)
|
db := New(rawdb.NewMemoryDatabase(), config, false)
|
||||||
db.waitGeneration()
|
db.waitGeneration()
|
||||||
|
|
@ -736,6 +742,7 @@ func TestStorageIteratorSeek(t *testing.T) {
|
||||||
func testStorageIteratorSeek(t *testing.T, newIterator func(db *Database, root, account, seek common.Hash) StorageIterator) {
|
func testStorageIteratorSeek(t *testing.T, newIterator func(db *Database, root, account, seek common.Hash) StorageIterator) {
|
||||||
config := &Config{
|
config := &Config{
|
||||||
WriteBufferSize: 0,
|
WriteBufferSize: 0,
|
||||||
|
NoAsyncFlush: true,
|
||||||
}
|
}
|
||||||
db := New(rawdb.NewMemoryDatabase(), config, false)
|
db := New(rawdb.NewMemoryDatabase(), config, false)
|
||||||
db.waitGeneration()
|
db.waitGeneration()
|
||||||
|
|
@ -808,6 +815,7 @@ func TestAccountIteratorDeletions(t *testing.T) {
|
||||||
func testAccountIteratorDeletions(t *testing.T, newIterator func(db *Database, root, seek common.Hash) AccountIterator) {
|
func testAccountIteratorDeletions(t *testing.T, newIterator func(db *Database, root, seek common.Hash) AccountIterator) {
|
||||||
config := &Config{
|
config := &Config{
|
||||||
WriteBufferSize: 0,
|
WriteBufferSize: 0,
|
||||||
|
NoAsyncFlush: true,
|
||||||
}
|
}
|
||||||
db := New(rawdb.NewMemoryDatabase(), config, false)
|
db := New(rawdb.NewMemoryDatabase(), config, false)
|
||||||
db.waitGeneration()
|
db.waitGeneration()
|
||||||
|
|
@ -848,6 +856,7 @@ func testAccountIteratorDeletions(t *testing.T, newIterator func(db *Database, r
|
||||||
func TestStorageIteratorDeletions(t *testing.T) {
|
func TestStorageIteratorDeletions(t *testing.T) {
|
||||||
config := &Config{
|
config := &Config{
|
||||||
WriteBufferSize: 0,
|
WriteBufferSize: 0,
|
||||||
|
NoAsyncFlush: true,
|
||||||
}
|
}
|
||||||
db := New(rawdb.NewMemoryDatabase(), config, false)
|
db := New(rawdb.NewMemoryDatabase(), config, false)
|
||||||
db.waitGeneration()
|
db.waitGeneration()
|
||||||
|
|
@ -916,6 +925,7 @@ func TestStaleIterator(t *testing.T) {
|
||||||
func testStaleIterator(t *testing.T, newIter func(db *Database, hash common.Hash) Iterator) {
|
func testStaleIterator(t *testing.T, newIter func(db *Database, hash common.Hash) Iterator) {
|
||||||
config := &Config{
|
config := &Config{
|
||||||
WriteBufferSize: 16 * 1024 * 1024,
|
WriteBufferSize: 16 * 1024 * 1024,
|
||||||
|
NoAsyncFlush: true,
|
||||||
}
|
}
|
||||||
db := New(rawdb.NewMemoryDatabase(), config, false)
|
db := New(rawdb.NewMemoryDatabase(), config, false)
|
||||||
db.waitGeneration()
|
db.waitGeneration()
|
||||||
|
|
@ -971,6 +981,7 @@ func BenchmarkAccountIteratorTraversal(b *testing.B) {
|
||||||
}
|
}
|
||||||
config := &Config{
|
config := &Config{
|
||||||
WriteBufferSize: 0,
|
WriteBufferSize: 0,
|
||||||
|
NoAsyncFlush: true,
|
||||||
}
|
}
|
||||||
db := New(rawdb.NewMemoryDatabase(), config, false)
|
db := New(rawdb.NewMemoryDatabase(), config, false)
|
||||||
db.waitGeneration()
|
db.waitGeneration()
|
||||||
|
|
@ -1066,6 +1077,7 @@ func BenchmarkAccountIteratorLargeBaselayer(b *testing.B) {
|
||||||
}
|
}
|
||||||
config := &Config{
|
config := &Config{
|
||||||
WriteBufferSize: 0,
|
WriteBufferSize: 0,
|
||||||
|
NoAsyncFlush: true,
|
||||||
}
|
}
|
||||||
db := New(rawdb.NewMemoryDatabase(), config, false)
|
db := New(rawdb.NewMemoryDatabase(), config, false)
|
||||||
db.waitGeneration()
|
db.waitGeneration()
|
||||||
|
|
|
||||||
|
|
@ -160,7 +160,7 @@ func (db *Database) loadLayers() layer {
|
||||||
log.Info("Failed to load journal, discard it", "err", err)
|
log.Info("Failed to load journal, discard it", "err", err)
|
||||||
}
|
}
|
||||||
// Return single layer with persistent state.
|
// Return single layer with persistent state.
|
||||||
return newDiskLayer(root, rawdb.ReadPersistentStateID(db.diskdb), db, nil, nil, newBuffer(db.config.WriteBufferSize, nil, nil, 0))
|
return newDiskLayer(root, rawdb.ReadPersistentStateID(db.diskdb), db, nil, nil, newBuffer(db.config.WriteBufferSize, nil, nil, 0), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadDiskLayer reads the binary blob from the layer journal, reconstructing
|
// loadDiskLayer reads the binary blob from the layer journal, reconstructing
|
||||||
|
|
@ -192,7 +192,7 @@ func (db *Database) loadDiskLayer(r *rlp.Stream) (layer, error) {
|
||||||
if err := states.decode(r); err != nil {
|
if err := states.decode(r); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return newDiskLayer(root, id, db, nil, nil, newBuffer(db.config.WriteBufferSize, &nodes, &states, id-stored)), nil
|
return newDiskLayer(root, id, db, nil, nil, newBuffer(db.config.WriteBufferSize, &nodes, &states, id-stored), nil), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadDiffLayer reads the next sections of a layer journal, reconstructing a new
|
// loadDiffLayer reads the next sections of a layer journal, reconstructing a new
|
||||||
|
|
@ -301,6 +301,12 @@ func (db *Database) Journal(root common.Hash) error {
|
||||||
} else { // disk layer only on noop runs (likely) or deep reorgs (unlikely)
|
} else { // disk layer only on noop runs (likely) or deep reorgs (unlikely)
|
||||||
log.Info("Persisting dirty state to disk", "root", root, "layers", disk.buffer.layers)
|
log.Info("Persisting dirty state to disk", "root", root, "layers", disk.buffer.layers)
|
||||||
}
|
}
|
||||||
|
// Block until the background flushing is finished. It must
|
||||||
|
// be done before terminating the potential background snapshot
|
||||||
|
// generator.
|
||||||
|
if err := disk.waitFlush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
// Terminate the background state generation if it's active
|
// Terminate the background state generation if it's active
|
||||||
if disk.generator != nil {
|
if disk.generator != nil {
|
||||||
disk.generator.stop()
|
disk.generator.stop()
|
||||||
|
|
|
||||||
|
|
@ -195,6 +195,10 @@ func (tree *layerTree) cap(root common.Hash, layers int) error {
|
||||||
}
|
}
|
||||||
tree.base = base
|
tree.base = base
|
||||||
|
|
||||||
|
// Block until the frozen buffer is fully flushed
|
||||||
|
if err := base.waitFlush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
// Reset the layer tree with the single new disk layer
|
// Reset the layer tree with the single new disk layer
|
||||||
tree.layers = map[common.Hash]layer{
|
tree.layers = map[common.Hash]layer{
|
||||||
base.rootHash(): base,
|
base.rootHash(): base,
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ import (
|
||||||
|
|
||||||
func newTestLayerTree() *layerTree {
|
func newTestLayerTree() *layerTree {
|
||||||
db := New(rawdb.NewMemoryDatabase(), nil, false)
|
db := New(rawdb.NewMemoryDatabase(), nil, false)
|
||||||
l := newDiskLayer(common.Hash{0x1}, 0, db, nil, nil, newBuffer(0, nil, nil, 0))
|
l := newDiskLayer(common.Hash{0x1}, 0, db, nil, nil, newBuffer(0, nil, nil, 0), nil)
|
||||||
t := newLayerTree(l)
|
t := newLayerTree(l)
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue