diff --git a/db.go b/db.go index 02d82112..4f79dbd6 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 @@ -455,7 +464,7 @@ func (db *DB) beginTx() (*Tx, error) { return t, nil } -func (db *DB) beginRWTx() (*Tx, error) { +func (db *DB) beginRWTx() (t *Tx, err error) { // If the database was opened with Options.ReadOnly, return an error. if db.readOnly { return nil, ErrDatabaseReadOnly @@ -465,10 +474,18 @@ func (db *DB) beginRWTx() (*Tx, error) { // This enforces only one writer transaction at a time. db.rwlock.Lock() + var minid txid = 0xFFFFFFFFFFFFFFFF + // 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() + defer func() { + db.metalock.Unlock() + db.ensureFreelist() + if err == nil && minid > 0 { + db.freelist.release(minid - 1) + } + }() // Exit if the database is not open yet. if !db.opened { @@ -477,20 +494,16 @@ func (db *DB) beginRWTx() (*Tx, error) { } // Create a transaction associated with the database. - t := &Tx{writable: true} + t = &Tx{writable: true} t.init(db) db.rwtx = t // Free any pages associated with closed read-only transactions. - var minid txid = 0xFFFFFFFFFFFFFFFF for _, t := range db.txs { if t.meta.txid < minid { minid = t.meta.txid } } - 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) }