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 authored and jpbetz committed Sep 21, 2017
1 parent ba5a58d commit bdfe415
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 14 deletions.
43 changes: 29 additions & 14 deletions db.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand All @@ -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 {
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.loadFreelist()

// 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 bdfe415

Please sign in to comment.