Skip to content

Commit

Permalink
tx: load freelist on Check()
Browse files Browse the repository at this point in the history
Otherwise, nil dereference on ReadOnly DB

Fixes #45
  • Loading branch information
Anthony Romano committed Sep 18, 2017
1 parent ba5a58d commit 6f3dcc3
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 14 deletions.
44 changes: 30 additions & 14 deletions db.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,12 @@ type DB struct {
opened bool
rwtx *Tx
txs []*Tx
freelist *freelist
stats Stats

freelist *freelist
freelistLoad sync.Once
freelistReady chan struct{} // closed once freelist is loaded

pagePool sync.Pool

batchMu sync.Mutex
Expand Down Expand Up @@ -157,8 +160,10 @@ 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,
freelistReady: make(chan struct{}),
}
// Set default options if no options are provided.
if options == nil {
options = DefaultOptions
Expand Down Expand Up @@ -254,20 +259,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)
hasFreeList := db.readFreelist()

// 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 && !hasFreeList {
tx, err := db.Begin(true)
if tx != nil {
err = tx.Commit()
Expand All @@ -282,6 +278,26 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
return db, nil
}

// readFreelist is called on Open and tx.Check. It assumes there are no
// concurrenct accesses are being made to the freelist.
func (db *DB) readFreelist() bool {
hasFreeList := db.meta().freelist != pgidNoFreelist
db.freelistLoad.Do(func() {
db.freelist = newFreelist()
if !hasFreeList {
// 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)
close(db.freelistReady)
})
<-db.freelistReady
return hasFreeList
}

// 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 {
Expand Down
3 changes: 3 additions & 0 deletions tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.readFreelist()

// Check if any pages are double freed.
freed := make(map[pgid]bool)
all := make([]pgid, tx.db.freelist.count())
Expand Down

0 comments on commit 6f3dcc3

Please sign in to comment.