Skip to content

Commit c6e5e89

Browse files
committed
change synchronisation in hashdb commit to allow concurrent reads
* delay nodes removal from dirites until all writes are performed * use read lock for the fist part of commit operation and acquire hashdb write lock only when removing nodes from dirites
1 parent d98903b commit c6e5e89

File tree

2 files changed

+76
-6
lines changed

2 files changed

+76
-6
lines changed

triedb/hashdb/cleaner_arbitrum.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package hashdb
2+
3+
import (
4+
"github.com/ethereum/go-ethereum/common"
5+
"github.com/ethereum/go-ethereum/core/rawdb"
6+
)
7+
8+
type delayedCleaner struct {
9+
db *Database
10+
cleaner *cleaner
11+
12+
hashes []common.Hash // to-be-deleted hashes
13+
}
14+
15+
func newDelayedCleaner(db *Database) *delayedCleaner {
16+
return &delayedCleaner{
17+
db: db,
18+
cleaner: &cleaner{db: db},
19+
}
20+
}
21+
22+
// Put can be called holding read lock
23+
// Write lock is not required here as Put doesn't modify dirties
24+
// The value put is ignored as it can be read later on from dirties
25+
func (c *delayedCleaner) Put(key []byte, _ []byte) error {
26+
hash := common.BytesToHash(key)
27+
// If the node does not exist, we're done on this path
28+
_, ok := c.db.dirties[hash]
29+
if !ok {
30+
return nil
31+
}
32+
// add key to to-be-deleted keys
33+
c.hashes = append(c.hashes, hash)
34+
return nil
35+
}
36+
37+
func (c *delayedCleaner) Delete(key []byte) error {
38+
panic("not implemented")
39+
}
40+
41+
// Clean removes the buffered to-be-deleted keys from dirties
42+
// Write lock must be held by the caller
43+
func (c *delayedCleaner) Clean() {
44+
for _, hash := range c.hashes {
45+
node, ok := c.db.dirties[hash]
46+
if !ok {
47+
// node no longer in dirties
48+
continue
49+
}
50+
rawdb.WriteLegacyTrieNode(c.cleaner, hash, node.node)
51+
}
52+
}

triedb/hashdb/database.go

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -407,8 +407,15 @@ func (db *Database) Commit(node common.Hash, report bool) error {
407407
// There's no data to commit in this node
408408
return nil
409409
}
410-
db.lock.Lock()
411-
defer db.lock.Unlock()
410+
// acquire read lock only, as commit doesn't modify dirties
411+
// that allows concurrent node reads e.g. performed by rpc handlers
412+
db.lock.RLock()
413+
rLocked := true
414+
defer func() {
415+
if rLocked {
416+
db.lock.RUnlock()
417+
}
418+
}()
412419

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

420427
// Move the trie itself into the batch, flushing if enough data is accumulated
421-
nodes, storage := len(db.dirties), db.dirtiesSize
422-
423-
uncacher := &cleaner{db}
428+
uncacher := newDelayedCleaner(db)
424429
if err := db.commit(node, batch, uncacher); err != nil {
425430
log.Error("Failed to commit trie from trie database", "err", err)
426431
return err
@@ -436,6 +441,18 @@ func (db *Database) Commit(node common.Hash, report bool) error {
436441
}
437442
batch.Reset()
438443

444+
// prevent RUnlock in defer and RUnlock the lock
445+
rLocked = false
446+
db.lock.RUnlock()
447+
448+
// acquire write lock for the uncacher to remove commited nodes from dirties
449+
db.lock.Lock()
450+
defer db.lock.Unlock()
451+
452+
nodes, storage := len(db.dirties), db.dirtiesSize
453+
454+
uncacher.Clean()
455+
439456
// Reset the storage counters and bumped metrics
440457
memcacheCommitTimeTimer.Update(time.Since(start))
441458
memcacheCommitBytesMeter.Mark(int64(storage - db.dirtiesSize))
@@ -456,7 +473,8 @@ func (db *Database) Commit(node common.Hash, report bool) error {
456473
}
457474

458475
// commit is the private locked version of Commit.
459-
func (db *Database) commit(hash common.Hash, batch ethdb.Batch, uncacher *cleaner) error {
476+
// note: commit must not modify dirties as it should only require read lock held
477+
func (db *Database) commit(hash common.Hash, batch ethdb.Batch, uncacher *delayedCleaner) error {
460478
// If the node does not exist, it's a previously committed node
461479
node, ok := db.dirties[hash]
462480
if !ok {

0 commit comments

Comments
 (0)