diff --git a/db.go b/db.go index 02d82112..f0c9cb62 100644 --- a/db.go +++ b/db.go @@ -8,6 +8,7 @@ import ( "runtime/debug" "strings" "sync" + "sync/atomic" "time" "unsafe" ) @@ -96,10 +97,11 @@ type DB struct { batchMu sync.Mutex batch *batch - rwlock sync.Mutex // Allows only one writer at a time. - metalock sync.Mutex // Protects meta page access. - mmaplock sync.RWMutex // Protects mmap access during remapping. - statlock sync.RWMutex // Protects stats access. + rwlock sync.Mutex // Allows only one writer at a time. + metalock sync.Mutex // Protects meta page access. + mmaplock sync.RWMutex // Protects mmap access during remapping. + statlock sync.RWMutex // Protects stats access. + freelistonce sync.Once // Protects reading freelist from file. ops struct { writeAt func(b []byte, off int64) (n int, err error) @@ -196,12 +198,6 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) { return nil, err } - if !db.readOnly { - // Read in the freelist. - db.freelist = newFreelist() - db.freelist.read(db.page(db.meta().freelist)) - } - // Mark the database as opened and return. return db, nil } @@ -397,6 +393,19 @@ func (db *DB) close() error { return nil } +func (db *DB) ensureFreelist() *freelist { + flp := (*unsafe.Pointer)(unsafe.Pointer(&db.freelist)) + if fl := atomic.LoadPointer(flp); fl != nil { + return (*freelist)(fl) + } + db.freelistonce.Do(func() { + fl := newFreelist() + fl.read(db.page(db.meta().freelist)) + atomic.StorePointer(flp, unsafe.Pointer(fl)) + }) + return db.freelist +} + // Begin starts a new transaction. // Multiple read-only transactions can be used concurrently but only one // write transaction can be used at a time. Starting multiple write transactions @@ -468,11 +477,11 @@ func (db *DB) beginRWTx() (*Tx, error) { // Once we have the writer lock then we can lock the meta pages so that // we can set up the transaction. db.metalock.Lock() - defer db.metalock.Unlock() // Exit if the database is not open yet. if !db.opened { db.rwlock.Unlock() + db.metalock.Unlock() return nil, ErrDatabaseNotOpen } @@ -488,10 +497,14 @@ func (db *DB) beginRWTx() (*Tx, error) { minid = t.meta.txid } } + + // Release meta here cause ensureFreelist can take a long time + db.metalock.Unlock() + + db.ensureFreelist() if minid > 0 { db.freelist.release(minid - 1) } - return t, nil } diff --git a/tx.go b/tx.go index 6b52b2c8..eb9e4c2e 100644 --- a/tx.go +++ b/tx.go @@ -326,7 +326,7 @@ func (tx *Tx) Check() <-chan error { func (tx *Tx) check(ch chan error) { // Check if any pages are double freed. freed := make(map[pgid]bool) - for _, id := range tx.db.freelist.all() { + for _, id := range tx.db.ensureFreelist().all() { if freed[id] { ch <- fmt.Errorf("page %d: already freed", id) }