Skip to content

Commit 94db071

Browse files
committed
backport upstream hashdb locking changes (ethereum#28542)
1 parent 444c006 commit 94db071

File tree

1 file changed

+18
-51
lines changed

1 file changed

+18
-51
lines changed

trie/triedb/hashdb/database.go

+18-51
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,7 +178,7 @@ 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.
188183
func (db *Database) Node(hash common.Hash) ([]byte, error) {
189184
// It doesn't make sense to retrieve the metaroot
@@ -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,20 +221,6 @@ 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
238-
}
239-
240224
// Reference adds a new reference from a parent node to a child node.
241225
// This function is used to add reference between internal trie node
242226
// and external node(e.g. storage trie root), all internal trie nodes
@@ -344,33 +328,28 @@ func (db *Database) dereference(hash common.Hash) {
344328

345329
// Cap iteratively flushes old but still referenced trie nodes until the total
346330
// 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.
350331
func (db *Database) Cap(limit common.StorageSize) error {
332+
db.lock.Lock()
333+
defer db.lock.Unlock()
334+
351335
// Create a database batch to flush persistent data out. It is important that
352336
// outside code doesn't see an inconsistent state (referenced data removed from
353337
// memory cache during commit but not yet in persistent storage). This is ensured
354338
// by only uncaching existing data when the database write finalizes.
355-
start := time.Now()
356339
batch := db.diskdb.NewBatch()
357-
db.lock.RLock()
358-
nodes, storage := len(db.dirties), db.dirtiesSize
340+
nodes, storage, start := len(db.dirties), db.dirtiesSize, time.Now()
359341

360342
// db.dirtiesSize only contains the useful data in the cache, but when reporting
361343
// the total memory consumption, the maintenance metadata is also needed to be
362344
// counted.
363345
size := db.dirtiesSize + common.StorageSize(len(db.dirties)*cachedNodeSize)
364346
size += db.childrenSize
365-
db.lock.RUnlock()
366347

367348
// Keep committing nodes from the flush-list until we're below allowance
368349
oldest := db.oldest
369350
for size > limit && oldest != (common.Hash{}) {
370351
// Fetch the oldest referenced node and push into the batch
371-
db.lock.RLock()
372352
node := db.dirties[oldest]
373-
db.lock.RUnlock()
374353
rawdb.WriteLegacyTrieNode(batch, oldest, node.node)
375354

376355
// If we exceeded the ideal batch size, commit and reset
@@ -396,9 +375,6 @@ func (db *Database) Cap(limit common.StorageSize) error {
396375
return err
397376
}
398377
// Write successful, clear out the flushed data
399-
db.lock.Lock()
400-
defer db.lock.Unlock()
401-
402378
for db.oldest != oldest {
403379
node := db.dirties[db.oldest]
404380
delete(db.dirties, db.oldest)
@@ -429,14 +405,13 @@ func (db *Database) Cap(limit common.StorageSize) error {
429405
// Commit iterates over all the children of a particular node, writes them out
430406
// to disk, forcefully tearing down all references in both directions. As a side
431407
// 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.
435408
func (db *Database) Commit(node common.Hash, report bool) error {
436409
if node == (common.Hash{}) {
437410
// There's no data to commit in this node
438411
return nil
439412
}
413+
db.lock.Lock()
414+
defer db.lock.Unlock()
440415

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

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

453426
uncacher := &cleaner{db}
454427
if err := db.commit(node, batch, uncacher); err != nil {
@@ -461,8 +434,6 @@ func (db *Database) Commit(node common.Hash, report bool) error {
461434
return err
462435
}
463436
// Uncache any leftovers in the last batch
464-
db.lock.Lock()
465-
defer db.lock.Unlock()
466437
if err := batch.Replay(uncacher); err != nil {
467438
return err
468439
}
@@ -490,9 +461,7 @@ func (db *Database) Commit(node common.Hash, report bool) error {
490461
// commit is the private locked version of Commit.
491462
func (db *Database) commit(hash common.Hash, batch ethdb.Batch, uncacher *cleaner) error {
492463
// If the node does not exist, it's a previously committed node
493-
db.lock.RLock()
494464
node, ok := db.dirties[hash]
495-
db.lock.RUnlock()
496465
if !ok {
497466
return nil
498467
}
@@ -513,13 +482,11 @@ func (db *Database) commit(hash common.Hash, batch ethdb.Batch, uncacher *cleane
513482
if err := batch.Write(); err != nil {
514483
return err
515484
}
516-
db.lock.Lock()
517485
err := batch.Replay(uncacher)
518-
batch.Reset()
519-
db.lock.Unlock()
520486
if err != nil {
521487
return err
522488
}
489+
batch.Reset()
523490
}
524491
return nil
525492
}
@@ -680,8 +647,8 @@ type reader struct {
680647
db *Database
681648
}
682649

683-
// Node retrieves the trie node with the given node hash.
684-
// No error will be returned if the node is not found.
650+
// Node retrieves the trie node with the given node hash. No error will be
651+
// returned if the node is not found.
685652
func (reader *reader) Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error) {
686653
blob, _ := reader.db.Node(hash)
687654
return blob, nil

0 commit comments

Comments
 (0)