Skip to content

Commit a104baf

Browse files
authored
Merge pull request #291 from OffchainLabs/backport-hashdb-locking
backport upstream hashdb locking changes
2 parents 086bfcc + 9a83389 commit a104baf

File tree

1 file changed

+25
-53
lines changed

1 file changed

+25
-53
lines changed

trie/triedb/hashdb/database.go

Lines changed: 25 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,6 @@ var Defaults = &Config{
8282
// Database is an intermediate write layer between the trie data structures and
8383
// the disk database. The aim is to accumulate trie writes in-memory and only
8484
// periodically flush a couple tries to disk, garbage collecting the remainder.
85-
//
86-
// Note, the trie Database is **not** thread safe in its mutations, but it **is**
87-
// thread safe in providing individual, independent node access. The rationale
88-
// behind this split design is to provide read access to RPC handlers and sync
89-
// servers even while the trie is executing expensive garbage collection.
9085
type Database struct {
9186
diskdb ethdb.Database // Persistent storage for matured trie nodes
9287
resolver ChildResolver // The handler to resolve children of nodes
@@ -113,7 +108,7 @@ type Database struct {
113108
// cachedNode is all the information we know about a single cached trie node
114109
// in the memory database write layer.
115110
type cachedNode struct {
116-
node []byte // Encoded node blob
111+
node []byte // Encoded node blob, immutable
117112
parents uint32 // Number of live nodes referencing this one
118113
external map[common.Hash]struct{} // The set of external children
119114
flushPrev common.Hash // Previous node in the flush-list
@@ -152,9 +147,9 @@ func New(diskdb ethdb.Database, config *Config, resolver ChildResolver) *Databas
152147
}
153148
}
154149

155-
// insert inserts a simplified trie node into the memory database.
156-
// All nodes inserted by this function will be reference tracked
157-
// and in theory should only used for **trie nodes** insertion.
150+
// insert inserts a trie node into the memory database. All nodes inserted by
151+
// this function will be reference tracked. This function assumes the lock is
152+
// already held.
158153
func (db *Database) insert(hash common.Hash, node []byte) {
159154
// If the node's already cached, skip
160155
if _, ok := db.dirties[hash]; ok {
@@ -183,9 +178,9 @@ func (db *Database) insert(hash common.Hash, node []byte) {
183178
db.dirtiesSize += common.StorageSize(common.HashLength + len(node))
184179
}
185180

186-
// Node retrieves an encoded cached trie node from memory. If it cannot be found
181+
// node retrieves an encoded cached trie node from memory. If it cannot be found
187182
// cached, the method queries the persistent database for the content.
188-
func (db *Database) Node(hash common.Hash) ([]byte, error) {
183+
func (db *Database) node(hash common.Hash) ([]byte, error) {
189184
// It doesn't make sense to retrieve the metaroot
190185
if hash == (common.Hash{}) {
191186
return nil, errors.New("not found")
@@ -198,11 +193,14 @@ func (db *Database) Node(hash common.Hash) ([]byte, error) {
198193
return enc, nil
199194
}
200195
}
201-
// Retrieve the node from the dirty cache if available
196+
// Retrieve the node from the dirty cache if available.
202197
db.lock.RLock()
203198
dirty := db.dirties[hash]
204199
db.lock.RUnlock()
205200

201+
// Return the cached node if it's found in the dirty set.
202+
// The dirty.node field is immutable and safe to read it
203+
// even without lock guard.
206204
if dirty != nil {
207205
memcacheDirtyHitMeter.Mark(1)
208206
memcacheDirtyReadMeter.Mark(int64(len(dirty.node)))
@@ -223,18 +221,9 @@ func (db *Database) Node(hash common.Hash) ([]byte, error) {
223221
return nil, errors.New("not found")
224222
}
225223

226-
// Nodes retrieves the hashes of all the nodes cached within the memory database.
227-
// This method is extremely expensive and should only be used to validate internal
228-
// states in test code.
229-
func (db *Database) Nodes() []common.Hash {
230-
db.lock.RLock()
231-
defer db.lock.RUnlock()
232-
233-
var hashes = make([]common.Hash, 0, len(db.dirties))
234-
for hash := range db.dirties {
235-
hashes = append(hashes, hash)
236-
}
237-
return hashes
224+
// arbitrum: exposing hashdb.Database.Node for triedb.Database.Node currently used by arbitrum.RecordingKV.Get
225+
func (db *Database) Node(hash common.Hash) ([]byte, error) {
226+
return db.node(hash)
238227
}
239228

240229
// Reference adds a new reference from a parent node to a child node.
@@ -344,33 +333,28 @@ func (db *Database) dereference(hash common.Hash) {
344333

345334
// Cap iteratively flushes old but still referenced trie nodes until the total
346335
// memory usage goes below the given threshold.
347-
//
348-
// Note, this method is a non-synchronized mutator. It is unsafe to call this
349-
// concurrently with other mutators.
350336
func (db *Database) Cap(limit common.StorageSize) error {
337+
db.lock.Lock()
338+
defer db.lock.Unlock()
339+
351340
// Create a database batch to flush persistent data out. It is important that
352341
// outside code doesn't see an inconsistent state (referenced data removed from
353342
// memory cache during commit but not yet in persistent storage). This is ensured
354343
// by only uncaching existing data when the database write finalizes.
355-
start := time.Now()
356344
batch := db.diskdb.NewBatch()
357-
db.lock.RLock()
358-
nodes, storage := len(db.dirties), db.dirtiesSize
345+
nodes, storage, start := len(db.dirties), db.dirtiesSize, time.Now()
359346

360347
// db.dirtiesSize only contains the useful data in the cache, but when reporting
361348
// the total memory consumption, the maintenance metadata is also needed to be
362349
// counted.
363350
size := db.dirtiesSize + common.StorageSize(len(db.dirties)*cachedNodeSize)
364351
size += db.childrenSize
365-
db.lock.RUnlock()
366352

367353
// Keep committing nodes from the flush-list until we're below allowance
368354
oldest := db.oldest
369355
for size > limit && oldest != (common.Hash{}) {
370356
// Fetch the oldest referenced node and push into the batch
371-
db.lock.RLock()
372357
node := db.dirties[oldest]
373-
db.lock.RUnlock()
374358
rawdb.WriteLegacyTrieNode(batch, oldest, node.node)
375359

376360
// If we exceeded the ideal batch size, commit and reset
@@ -396,9 +380,6 @@ func (db *Database) Cap(limit common.StorageSize) error {
396380
return err
397381
}
398382
// Write successful, clear out the flushed data
399-
db.lock.Lock()
400-
defer db.lock.Unlock()
401-
402383
for db.oldest != oldest {
403384
node := db.dirties[db.oldest]
404385
delete(db.dirties, db.oldest)
@@ -429,14 +410,13 @@ func (db *Database) Cap(limit common.StorageSize) error {
429410
// Commit iterates over all the children of a particular node, writes them out
430411
// to disk, forcefully tearing down all references in both directions. As a side
431412
// effect, all pre-images accumulated up to this point are also written.
432-
//
433-
// Note, this method is a non-synchronized mutator. It is unsafe to call this
434-
// concurrently with other mutators.
435413
func (db *Database) Commit(node common.Hash, report bool) error {
436414
if node == (common.Hash{}) {
437415
// There's no data to commit in this node
438416
return nil
439417
}
418+
db.lock.Lock()
419+
defer db.lock.Unlock()
440420

441421
// Create a database batch to flush persistent data out. It is important that
442422
// outside code doesn't see an inconsistent state (referenced data removed from
@@ -446,9 +426,7 @@ func (db *Database) Commit(node common.Hash, report bool) error {
446426
batch := db.diskdb.NewBatch()
447427

448428
// Move the trie itself into the batch, flushing if enough data is accumulated
449-
db.lock.RLock()
450429
nodes, storage := len(db.dirties), db.dirtiesSize
451-
db.lock.RUnlock()
452430

453431
uncacher := &cleaner{db}
454432
if err := db.commit(node, batch, uncacher); err != nil {
@@ -461,8 +439,6 @@ func (db *Database) Commit(node common.Hash, report bool) error {
461439
return err
462440
}
463441
// Uncache any leftovers in the last batch
464-
db.lock.Lock()
465-
defer db.lock.Unlock()
466442
if err := batch.Replay(uncacher); err != nil {
467443
return err
468444
}
@@ -490,9 +466,7 @@ func (db *Database) Commit(node common.Hash, report bool) error {
490466
// commit is the private locked version of Commit.
491467
func (db *Database) commit(hash common.Hash, batch ethdb.Batch, uncacher *cleaner) error {
492468
// If the node does not exist, it's a previously committed node
493-
db.lock.RLock()
494469
node, ok := db.dirties[hash]
495-
db.lock.RUnlock()
496470
if !ok {
497471
return nil
498472
}
@@ -513,13 +487,11 @@ func (db *Database) commit(hash common.Hash, batch ethdb.Batch, uncacher *cleane
513487
if err := batch.Write(); err != nil {
514488
return err
515489
}
516-
db.lock.Lock()
517490
err := batch.Replay(uncacher)
518-
batch.Reset()
519-
db.lock.Unlock()
520491
if err != nil {
521492
return err
522493
}
494+
batch.Reset()
523495
}
524496
return nil
525497
}
@@ -588,7 +560,7 @@ func (db *Database) Initialized(genesisRoot common.Hash) bool {
588560
func (db *Database) Update(root common.Hash, parent common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set) error {
589561
// Ensure the parent state is present and signal a warning if not.
590562
if parent != types.EmptyRootHash {
591-
if blob, _ := db.Node(parent); len(blob) == 0 {
563+
if blob, _ := db.node(parent); len(blob) == 0 {
592564
log.Error("parent state is not present")
593565
}
594566
}
@@ -669,7 +641,7 @@ func (db *Database) Scheme() string {
669641
// Reader retrieves a node reader belonging to the given state root.
670642
// An error will be returned if the requested state is not available.
671643
func (db *Database) Reader(root common.Hash) (*reader, error) {
672-
if _, err := db.Node(root); err != nil {
644+
if _, err := db.node(root); err != nil {
673645
return nil, fmt.Errorf("state %#x is not available, %v", root, err)
674646
}
675647
return &reader{db: db}, nil
@@ -680,9 +652,9 @@ type reader struct {
680652
db *Database
681653
}
682654

683-
// Node retrieves the trie node with the given node hash.
684-
// No error will be returned if the node is not found.
655+
// Node retrieves the trie node with the given node hash. No error will be
656+
// returned if the node is not found.
685657
func (reader *reader) Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error) {
686-
blob, _ := reader.db.Node(hash)
658+
blob, _ := reader.db.node(hash)
687659
return blob, nil
688660
}

0 commit comments

Comments
 (0)