Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
default: patch

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be minor considering we're extending the exported Store and DB interfaces? Anyone implementing those will have to add Snapshot for it to compile.

---

# Change Chain Manager Mutex to RWMutex to enable parallel reads
30 changes: 30 additions & 0 deletions chain/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ type DB interface {
CreateBucket(name []byte) (DBBucket, error)
Flush() error
Cancel()

// View calls fn with a read-only DB. Implementations must guarantee
// that the DB passed to fn observes a consistent snapshot for the
// duration of fn, and that fn may run concurrently with other View
// calls. Buckets obtained via the DB inside fn must not be used
// after fn returns. Writes via the DB inside fn are not durable
// and may panic.
View(fn func(DB) error) error

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this makes me sad

maybe we could add a writeable flag on Bucket() instead?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure you'd like that more but if you want to be able to do multiple operations within a single View we could also do something like

snap := m.store.Snapshot()
defer snap.Close()
for range 10 {
  _, _ = snap.Block(...)
}

Where Snapshot starts a readonly transaction which returns a ReadonlyDB interface only containing a subset of the DB interface. So it's impossible to call CreateBucket in the first place.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented Chris's suggestion

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do like the idea of making it impossible to call mutating methods, but to make it airtight, we need a read-only DBBucket as well that has no Put or Delete methods.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added this in 38d9717

}

// A DBBucket is a set of key-value pairs.
Expand Down Expand Up @@ -105,6 +113,12 @@ func (db *MemDB) Flush() error {
return nil
}

// View implements DB. MemDB has no MVCC; callers must ensure that no writes
// occur concurrently with the View call.
func (db *MemDB) View(fn func(DB) error) error {
return fn(db)
}

// Cancel implements DB.
func (db *MemDB) Cancel() {
for k := range db.puts {
Expand Down Expand Up @@ -336,6 +350,12 @@ func (db *CacheDB) Cancel() {
db.db.Cancel()
}

// View implements DB. CacheDB has no MVCC; callers must ensure that no writes
// occur concurrently with the View call.
func (db *CacheDB) View(fn func(DB) error) error {
return fn(db)
}

// NewCacheDB returns a new CacheDB that wraps the given DB.
func NewCacheDB(db DB) DB {
return &CacheDB{
Expand Down Expand Up @@ -948,6 +968,16 @@ func (db *DBStore) Flush() error {
return err
}

// View implements Store. It calls fn with a read-only Store backed by a
// consistent snapshot of the underlying DB. The Store passed to fn must not be
// used after fn returns. Writes performed via the Store will fail.
func (db *DBStore) View(fn func(Store)) error {
return db.db.View(func(rdb DB) error {
fn(&DBStore{db: rdb, n: db.n})
return nil
})
}

// NewDBStore creates a new DBStore using the provided database. The tip state
// is also returned. The DB will be automatically migrated if necessary. The
// provided logger may be nil.
Expand Down
Loading
Loading