Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions db.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ func Open(path string, mode os.FileMode, options *Options) (db *DB, err error) {
}

if db.PreLoadFreelist {
db.loadFreelist()
db.loadFreelist(nil)
}

if db.readOnly {
Expand Down Expand Up @@ -419,12 +419,23 @@ func (db *DB) getPageSizeFromSecondMeta() (int, bool, error) {
// 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() {
//
// When sharedReadTx is non-nil, an unsynced freelist is reconstructed by
// scanning using that transaction instead of opening a nested read-only
// transaction. Tx.check passes the active read transaction so freelist
// reconstruction does not call beginTx while another goroutine may still
// hold the outer read transaction from View, which previously could block
// indefinitely (see https://github.com/etcd-io/bbolt/issues/877).
func (db *DB) loadFreelist(sharedReadTx *Tx) {
db.freelistLoad.Do(func() {
db.freelist = newFreelist(db.FreelistType)
if !db.hasSyncedFreelist() {
// Reconstruct free list by scanning the DB.
db.freelist.Init(db.freepages())
if sharedReadTx != nil {
db.freelist.Init(db.freepagesWithTx(sharedReadTx))
} else {
db.freelist.Init(db.freepages())
}
} else {
// Read free list from freelist page.
db.freelist.Read(db.page(db.meta().Freelist()))
Expand Down Expand Up @@ -1250,6 +1261,13 @@ func (db *DB) freepages() []common.Pgid {
panic("freepages: failed to open read only tx")
}

return db.freepagesWithTx(tx)
}

// freepagesWithTx lists page IDs that are not reachable from the bucket tree
// for the given read-only transaction. The transaction must not be used
// concurrently while this function runs.
func (db *DB) freepagesWithTx(tx *Tx) []common.Pgid {
reachable := make(map[common.Pgid]*common.Page)
nofreed := make(map[common.Pgid]bool)
ech := make(chan error)
Expand All @@ -1267,7 +1285,7 @@ func (db *DB) freepages() []common.Pgid {
// TODO: If check bucket reported any corruptions (ech) we shouldn't proceed to freeing the pages.

var fids []common.Pgid
for i := common.Pgid(2); i < db.meta().Pgid(); i++ {
for i := common.Pgid(2); i < tx.meta.Pgid(); i++ {
if _, ok := reachable[i]; !ok {
fids = append(fids, i)
}
Expand Down
5 changes: 3 additions & 2 deletions tx_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ func (tx *Tx) check(cfg checkConfig, ch chan error) {
ch <- panicked{r}
}
}()
// Force loading free list if opened in ReadOnly mode.
tx.db.loadFreelist()
// Force loading free list if opened in ReadOnly mode. Pass this tx so
// freelist reconstruction does not open a nested read transaction.
tx.db.loadFreelist(tx)

// Check if any pages are double freed.
freed := make(map[common.Pgid]bool)
Expand Down
Loading