From bdfe4158f8eb42af576f56cb2ff57fd70bb88a3b Mon Sep 17 00:00:00 2001 From: Anthony Romano Date: Wed, 13 Sep 2017 13:16:42 -0700 Subject: [PATCH] tx: load freelist on Check() Otherwise, nil dereference on ReadOnly DB Fixes #45 --- db.go | 43 +++++++++++++++++++++++++++++-------------- tx.go | 3 +++ 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/db.go b/db.go index 6559f41..08dfd0a 100644 --- a/db.go +++ b/db.go @@ -116,9 +116,11 @@ type DB struct { opened bool rwtx *Tx txs []*Tx - freelist *freelist stats Stats + freelist *freelist + freelistLoad sync.Once + pagePool sync.Pool batchMu sync.Mutex @@ -157,8 +159,9 @@ func (db *DB) String() string { // If the file does not exist then it will be created automatically. // Passing in nil options will cause Bolt to open the database with the default options. func Open(path string, mode os.FileMode, options *Options) (*DB, error) { - var db = &DB{opened: true} - + db := &DB{ + opened: true, + } // Set default options if no options are provided. if options == nil { options = DefaultOptions @@ -254,20 +257,11 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) { return db, nil } - db.freelist = newFreelist() - noFreeList := db.meta().freelist == pgidNoFreelist - if noFreeList { - // Reconstruct free list by scanning the DB. - db.freelist.readIDs(db.freepages()) - } else { - // Read free list from freelist page. - db.freelist.read(db.page(db.meta().freelist)) - } - db.stats.FreePageN = len(db.freelist.ids) + db.loadFreelist() // Flush freelist when transitioning from no sync to sync so // NoFreelistSync unaware boltdb can open the db later. - if !db.NoFreelistSync && noFreeList { + if !db.NoFreelistSync && !db.hasSyncedFreelist() { tx, err := db.Begin(true) if tx != nil { err = tx.Commit() @@ -282,6 +276,27 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) { return db, nil } +// loadFreelist reads the freelist if it is synced, or reconstructs it +// by scanning the DB if it is not synced. It assumes there are no +// concurrent accesses being made to the freelist. +func (db *DB) loadFreelist() { + db.freelistLoad.Do(func() { + db.freelist = newFreelist() + if !db.hasSyncedFreelist() { + // Reconstruct free list by scanning the DB. + db.freelist.readIDs(db.freepages()) + } else { + // Read free list from freelist page. + db.freelist.read(db.page(db.meta().freelist)) + } + db.stats.FreePageN = len(db.freelist.ids) + }) +} + +func (db *DB) hasSyncedFreelist() bool { + return db.meta().freelist != pgidNoFreelist +} + // mmap opens the underlying memory-mapped file and initializes the meta references. // minsz is the minimum size that the new mmap can be. func (db *DB) mmap(minsz int) error { diff --git a/tx.go b/tx.go index e6d95ca..aefcec0 100644 --- a/tx.go +++ b/tx.go @@ -395,6 +395,9 @@ func (tx *Tx) Check() <-chan error { } func (tx *Tx) check(ch chan error) { + // Force loading free list if opened in ReadOnly mode. + tx.db.loadFreelist() + // Check if any pages are double freed. freed := make(map[pgid]bool) all := make([]pgid, tx.db.freelist.count())