Skip to content

Commit

Permalink
introduce a freelist interface
Browse files Browse the repository at this point in the history
This introduces an interface for the freelist, splits it into two concrete
implementations.

fixes etcd-io#773

Signed-off-by: Thomas Jungblut <[email protected]>
Signed-off-by: samuelbartels20 <[email protected]>
  • Loading branch information
tjungblu authored and samuelbartels20 committed Nov 10, 2024
1 parent 07ee67a commit 5edced1
Show file tree
Hide file tree
Showing 17 changed files with 1,078 additions and 1,008 deletions.
44 changes: 24 additions & 20 deletions allocate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,36 @@ import (
"testing"

"go.etcd.io/bbolt/internal/common"
"go.etcd.io/bbolt/internal/freelist"
)

func TestTx_allocatePageStats(t *testing.T) {
f := newTestFreelist()
ids := []common.Pgid{2, 3}
f.readIDs(ids)
for n, f := range map[string]freelist.Interface{"hashmap": freelist.NewHashMapFreelist(), "array": freelist.NewArrayFreelist()} {
t.Run(n, func(t *testing.T) {
ids := []common.Pgid{2, 3}
f.Init(ids)

tx := &Tx{
db: &DB{
freelist: f,
pageSize: common.DefaultPageSize,
},
meta: &common.Meta{},
pages: make(map[common.Pgid]*common.Page),
}
tx := &Tx{
db: &DB{
freelist: f,
pageSize: common.DefaultPageSize,
},
meta: &common.Meta{},
pages: make(map[common.Pgid]*common.Page),
}

txStats := tx.Stats()
prePageCnt := txStats.GetPageCount()
allocateCnt := f.free_count()
txStats := tx.Stats()
prePageCnt := txStats.GetPageCount()
allocateCnt := f.FreeCount()

if _, err := tx.allocate(allocateCnt); err != nil {
t.Fatal(err)
}
if _, err := tx.allocate(allocateCnt); err != nil {
t.Fatal(err)
}

txStats = tx.Stats()
if txStats.GetPageCount() != prePageCnt+int64(allocateCnt) {
t.Errorf("Allocated %d but got %d page in stats", allocateCnt, txStats.GetPageCount())
txStats = tx.Stats()
if txStats.GetPageCount() != prePageCnt+int64(allocateCnt) {
t.Errorf("Allocated %d but got %d page in stats", allocateCnt, txStats.GetPageCount())
}
})
}
}
2 changes: 1 addition & 1 deletion bucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -903,7 +903,7 @@ func (b *Bucket) free() {
var tx = b.tx
b.forEachPageNode(func(p *common.Page, n *node, _ int) {
if p != nil {
tx.db.freelist.free(tx.meta.Txid(), p)
tx.db.freelist.Free(tx.meta.Txid(), p)
} else {
n.free()
}
Expand Down
3 changes: 3 additions & 0 deletions bucket_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,9 @@ func TestBucket_Delete_FreelistOverflow(t *testing.T) {
if reopenFreePages := db.Stats().FreePageN; freePages != reopenFreePages {
t.Fatalf("expected %d free pages, got %+v", freePages, db.Stats())
}
if reopenPendingPages := db.Stats().PendingPageN; reopenPendingPages != 0 {
t.Fatalf("expected no pending pages, got %+v", db.Stats())
}
}

// Ensure that deleting of non-existing key is a no-op.
Expand Down
1 change: 1 addition & 0 deletions cmd/bbolt/command_version.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"runtime"

"github.com/spf13/cobra"

"go.etcd.io/bbolt/version"
)

Expand Down
24 changes: 16 additions & 8 deletions db.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

berrors "go.etcd.io/bbolt/errors"
"go.etcd.io/bbolt/internal/common"
fl "go.etcd.io/bbolt/internal/freelist"
)

// The time elapsed between consecutive file locking attempts.
Expand Down Expand Up @@ -133,7 +134,7 @@ type DB struct {
rwtx *Tx
txs []*Tx

freelist *freelist
freelist fl.Interface
freelistLoad sync.Once

pagePool sync.Pool
Expand Down Expand Up @@ -418,12 +419,12 @@ func (db *DB) loadFreelist() {
db.freelist = newFreelist(db.FreelistType)
if !db.hasSyncedFreelist() {
// Reconstruct free list by scanning the DB.
db.freelist.readIDs(db.freepages())
db.freelist.Init(db.freepages())
} else {
// Read free list from freelist page.
db.freelist.read(db.page(db.meta().Freelist()))
db.freelist.Read(db.page(db.meta().Freelist()))
}
db.stats.FreePageN = db.freelist.free_count()
db.stats.FreePageN = db.freelist.FreeCount()
})
}

Expand Down Expand Up @@ -797,7 +798,7 @@ func (db *DB) beginTx() (*Tx, error) {
db.txs = append(db.txs, t)
n := len(db.txs)
if db.freelist != nil {
db.freelist.addReadonlyTXID(t.meta.Txid())
db.freelist.AddReadonlyTXID(t.meta.Txid())
}

// Unlock the meta pages.
Expand Down Expand Up @@ -843,7 +844,7 @@ func (db *DB) beginRWTx() (*Tx, error) {
t := &Tx{writable: true}
t.init(db)
db.rwtx = t
db.freelist.freePages()
db.freelist.ReleasePendingPages()
return t, nil
}

Expand All @@ -867,7 +868,7 @@ func (db *DB) removeTx(tx *Tx) {
}
n := len(db.txs)
if db.freelist != nil {
db.freelist.removeReadonlyTXID(tx.meta.Txid())
db.freelist.RemoveReadonlyTXID(tx.meta.Txid())
}

// Unlock the meta pages.
Expand Down Expand Up @@ -1155,7 +1156,7 @@ func (db *DB) allocate(txid common.Txid, count int) (*common.Page, error) {
p.SetOverflow(uint32(count - 1))

// Use pages from the freelist if they are available.
p.SetId(db.freelist.allocate(txid, count))
p.SetId(db.freelist.Allocate(txid, count))
if p.Id() != 0 {
return p, nil
}
Expand Down Expand Up @@ -1261,6 +1262,13 @@ func (db *DB) freepages() []common.Pgid {
return fids
}

func newFreelist(freelistType FreelistType) fl.Interface {
if freelistType == FreelistMapType {
return fl.NewHashMapFreelist()
}
return fl.NewArrayFreelist()
}

// Options represents the options that can be set when opening a database.
type Options struct {
// Timeout is the amount of time to wait to obtain a file lock.
Expand Down
Loading

0 comments on commit 5edced1

Please sign in to comment.