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 <tjungblu@redhat.com>
  • Loading branch information
tjungblu committed Jun 27, 2024
1 parent 53977ba commit 56634cd
Show file tree
Hide file tree
Showing 19 changed files with 909 additions and 965 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.Freelist{"hashmap": freelist.NewHashMap(), "array": freelist.NewArray()} {
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
29 changes: 20 additions & 9 deletions db.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,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 @@ -134,8 +135,9 @@ type DB struct {
rwtx *Tx
txs []*Tx

freelist *freelist
freelistLoad sync.Once
freelist fl.Freelist
freelistSerializer fl.Serializable
freelistLoad sync.Once

pagePool sync.Pool

Expand Down Expand Up @@ -190,6 +192,7 @@ func Open(path string, mode os.FileMode, options *Options) (db *DB, err error) {
db.NoFreelistSync = options.NoFreelistSync
db.PreLoadFreelist = options.PreLoadFreelist
db.FreelistType = options.FreelistType
db.freelistSerializer = fl.Serializer{}
db.Mlock = options.Mlock

// Set default values for later DB operations.
Expand Down Expand Up @@ -419,12 +422,13 @@ 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.freelistSerializer.Read(db.freelist, db.page(db.meta().Freelist()))
}
db.stats.FreePageN = db.freelist.free_count()
db.stats.FreePageN = db.freelist.FreeCount()
db.stats.PendingPageN = db.freelist.PendingCount()
})
}

Expand Down Expand Up @@ -854,14 +858,14 @@ func (db *DB) freePages() {
minid = db.txs[0].meta.Txid()
}
if minid > 0 {
db.freelist.release(minid - 1)
db.freelist.Release(minid - 1)
}
// Release unused txid extents.
for _, t := range db.txs {
db.freelist.releaseRange(minid, t.meta.Txid()-1)
db.freelist.ReleaseRange(minid, t.meta.Txid()-1)
minid = t.meta.Txid() + 1
}
db.freelist.releaseRange(minid, common.Txid(0xFFFFFFFFFFFFFFFF))
db.freelist.ReleaseRange(minid, common.Txid(0xFFFFFFFFFFFFFFFF))
// Any page both allocated and freed in an extent is safe to release.
}

Expand Down Expand Up @@ -1176,7 +1180,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 @@ -1282,6 +1286,13 @@ func (db *DB) freepages() []common.Pgid {
return fids
}

func newFreelist(freelistType FreelistType) fl.Freelist {
if freelistType == FreelistMapType {
return fl.NewHashMap()
}
return fl.NewArray()
}

// 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 56634cd

Please sign in to comment.