Skip to content

Commit 99394ad

Browse files
authored
ethdb/pebble: prevent shutdown-panic (#27238)
One difference between pebble and leveldb is that the latter returns error when performing Get on a closed database, the former does a panic. This may be triggered during shutdown (see #27237) This PR changes the pebble driver so we check that the db is not closed already, for several operations. It also adds tests to the db test-suite, so the previously implicit assumption of "not panic:ing at ops on closed database" is covered by tests.
1 parent 85a4b82 commit 99394ad

File tree

3 files changed

+68
-10
lines changed

3 files changed

+68
-10
lines changed

ethdb/dbtest/testsuite.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,32 @@ func TestDatabaseSuite(t *testing.T, New func() ethdb.KeyValueStore) {
376376
}
377377
}
378378
})
379+
380+
t.Run("OperatonsAfterClose", func(t *testing.T) {
381+
db := New()
382+
db.Put([]byte("key"), []byte("value"))
383+
db.Close()
384+
if _, err := db.Get([]byte("key")); err == nil {
385+
t.Fatalf("expected error on Get after Close")
386+
}
387+
if _, err := db.Has([]byte("key")); err == nil {
388+
t.Fatalf("expected error on Get after Close")
389+
}
390+
if err := db.Put([]byte("key2"), []byte("value2")); err == nil {
391+
t.Fatalf("expected error on Put after Close")
392+
}
393+
if err := db.Delete([]byte("key")); err == nil {
394+
t.Fatalf("expected error on Delete after Close")
395+
}
396+
397+
b := db.NewBatch()
398+
if err := b.Put([]byte("batchkey"), []byte("batchval")); err != nil {
399+
t.Fatalf("expected no error on batch.Put after Close, got %v", err)
400+
}
401+
if err := b.Write(); err == nil {
402+
t.Fatalf("expected error on batch.Write after Close")
403+
}
404+
})
379405
}
380406

381407
// BenchDatabaseSuite runs a suite of benchmarks against a KeyValueStore database

ethdb/memorydb/memorydb.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,9 @@ func (b *batch) Write() error {
244244
b.db.lock.Lock()
245245
defer b.db.lock.Unlock()
246246

247+
if b.db.db == nil {
248+
return errMemorydbClosed
249+
}
247250
for _, keyvalue := range b.writes {
248251
if keyvalue.delete {
249252
delete(b.db.db, string(keyvalue.key))

ethdb/pebble/pebble.go

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,9 @@ type Database struct {
7070
seekCompGauge metrics.Gauge // Gauge for tracking the number of table compaction caused by read opt
7171
manualMemAllocGauge metrics.Gauge // Gauge for tracking amount of non-managed memory currently allocated
7272

73-
quitLock sync.Mutex // Mutex protecting the quit channel access
73+
quitLock sync.RWMutex // Mutex protecting the quit channel and the closed flag
7474
quitChan chan chan error // Quit channel to stop the metrics collection before closing the database
75+
closed bool // keep track of whether we're Closed
7576

7677
log log.Logger // Contextual logger tracking the database path
7778

@@ -221,23 +222,29 @@ func New(file string, cache int, handles int, namespace string, readonly bool) (
221222
func (d *Database) Close() error {
222223
d.quitLock.Lock()
223224
defer d.quitLock.Unlock()
224-
225225
// Allow double closing, simplifies things
226-
if d.quitChan == nil {
226+
if d.closed {
227227
return nil
228228
}
229-
errc := make(chan error)
230-
d.quitChan <- errc
231-
if err := <-errc; err != nil {
232-
d.log.Error("Metrics collection failed", "err", err)
229+
d.closed = true
230+
if d.quitChan != nil {
231+
errc := make(chan error)
232+
d.quitChan <- errc
233+
if err := <-errc; err != nil {
234+
d.log.Error("Metrics collection failed", "err", err)
235+
}
236+
d.quitChan = nil
233237
}
234-
d.quitChan = nil
235-
236238
return d.db.Close()
237239
}
238240

239241
// Has retrieves if a key is present in the key-value store.
240242
func (d *Database) Has(key []byte) (bool, error) {
243+
d.quitLock.RLock()
244+
defer d.quitLock.RUnlock()
245+
if d.closed {
246+
return false, pebble.ErrClosed
247+
}
241248
_, closer, err := d.db.Get(key)
242249
if err == pebble.ErrNotFound {
243250
return false, nil
@@ -250,6 +257,11 @@ func (d *Database) Has(key []byte) (bool, error) {
250257

251258
// Get retrieves the given key if it's present in the key-value store.
252259
func (d *Database) Get(key []byte) ([]byte, error) {
260+
d.quitLock.RLock()
261+
defer d.quitLock.RUnlock()
262+
if d.closed {
263+
return nil, pebble.ErrClosed
264+
}
253265
dat, closer, err := d.db.Get(key)
254266
if err != nil {
255267
return nil, err
@@ -262,19 +274,30 @@ func (d *Database) Get(key []byte) ([]byte, error) {
262274

263275
// Put inserts the given value into the key-value store.
264276
func (d *Database) Put(key []byte, value []byte) error {
277+
d.quitLock.RLock()
278+
defer d.quitLock.RUnlock()
279+
if d.closed {
280+
return pebble.ErrClosed
281+
}
265282
return d.db.Set(key, value, pebble.NoSync)
266283
}
267284

268285
// Delete removes the key from the key-value store.
269286
func (d *Database) Delete(key []byte) error {
287+
d.quitLock.RLock()
288+
defer d.quitLock.RUnlock()
289+
if d.closed {
290+
return pebble.ErrClosed
291+
}
270292
return d.db.Delete(key, nil)
271293
}
272294

273295
// NewBatch creates a write-only key-value store that buffers changes to its host
274296
// database until a final write is called.
275297
func (d *Database) NewBatch() ethdb.Batch {
276298
return &batch{
277-
b: d.db.NewBatch(),
299+
b: d.db.NewBatch(),
300+
db: d,
278301
}
279302
}
280303

@@ -481,6 +504,7 @@ func (d *Database) meter(refresh time.Duration) {
481504
// when Write is called. A batch cannot be used concurrently.
482505
type batch struct {
483506
b *pebble.Batch
507+
db *Database
484508
size int
485509
}
486510

@@ -505,6 +529,11 @@ func (b *batch) ValueSize() int {
505529

506530
// Write flushes any accumulated data to disk.
507531
func (b *batch) Write() error {
532+
b.db.quitLock.RLock()
533+
defer b.db.quitLock.RUnlock()
534+
if b.db.closed {
535+
return pebble.ErrClosed
536+
}
508537
return b.b.Commit(pebble.NoSync)
509538
}
510539

0 commit comments

Comments
 (0)