boltdd: return error on use-after-Close

Return the same error as boltdb instead of panic'ing.
This commit is contained in:
Michael Schurter 2018-11-13 14:09:33 -08:00
parent 6f3712ed48
commit 4d92603340
2 changed files with 44 additions and 0 deletions

View File

@ -69,6 +69,10 @@ func (db *DB) bucket(btx *bolt.Tx, name []byte) *Bucket {
db.rootBucketsLock.Lock()
defer db.rootBucketsLock.Unlock()
if db.isClosed() {
return nil
}
b, ok := db.rootBuckets[string(name)]
if !ok {
b = newBucketMeta()
@ -87,6 +91,12 @@ func (db *DB) createBucket(btx *bolt.Tx, name []byte) (*Bucket, error) {
db.rootBucketsLock.Lock()
defer db.rootBucketsLock.Unlock()
// While creating a bucket on a closed db would error, we must recheck
// after acquiring the lock to avoid races.
if db.isClosed() {
return nil, bolt.ErrDatabaseNotOpen
}
// Always create a new Bucket since CreateBucket above fails if the
// bucket already exists.
b := newBucketMeta()
@ -104,6 +114,12 @@ func (db *DB) createBucketIfNotExists(btx *bolt.Tx, name []byte) (*Bucket, error
db.rootBucketsLock.Lock()
defer db.rootBucketsLock.Unlock()
// While creating a bucket on a closed db would error, we must recheck
// after acquiring the lock to avoid races.
if db.isClosed() {
return nil, bolt.ErrDatabaseNotOpen
}
b, ok := db.rootBuckets[string(name)]
if !ok {
b = newBucketMeta()
@ -127,10 +143,18 @@ func (db *DB) View(fn func(*Tx) error) error {
})
}
// isClosed returns true if the database is closed and must be called while
// db.rootBucketsLock is acquired.
func (db *DB) isClosed() bool {
return db.rootBuckets == nil
}
// Close closes the underlying bolt.DB and clears all bucket hashes. DB is
// unusable after closing.
func (db *DB) Close() error {
db.rootBucketsLock.Lock()
db.rootBuckets = nil
db.rootBucketsLock.Unlock()
return db.bdb.Close()
}

View File

@ -8,6 +8,7 @@ import (
"path/filepath"
"testing"
"github.com/boltdb/bolt"
"github.com/hashicorp/nomad/nomad/mock"
"github.com/hashicorp/nomad/nomad/structs"
"github.com/stretchr/testify/require"
@ -54,6 +55,25 @@ func TestDB_Open(t *testing.T) {
require.Equal(0, db.BoltDB().Stats().TxStats.Write)
}
func TestDB_Close(t *testing.T) {
t.Parallel()
db, cleanup := setupBoltDB(t)
defer cleanup()
db.Close()
require.Equal(t, db.Update(func(tx *Tx) error {
_, err := tx.CreateBucketIfNotExists([]byte("foo"))
return err
}), bolt.ErrDatabaseNotOpen)
require.Equal(t, db.Update(func(tx *Tx) error {
_, err := tx.CreateBucket([]byte("foo"))
return err
}), bolt.ErrDatabaseNotOpen)
}
func TestBucket_Create(t *testing.T) {
t.Parallel()
require := require.New(t)