Skip to content

Commit 62e81c0

Browse files
committed
introduce a freelist interface
This introduces an interface for the freelist, splits it into two concrete implementations. fixes #773 Signed-off-by: Thomas Jungblut <[email protected]>
1 parent d537eff commit 62e81c0

17 files changed

+1078
-1008
lines changed

allocate_test.go

+24-20
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,36 @@ import (
44
"testing"
55

66
"go.etcd.io/bbolt/internal/common"
7+
"go.etcd.io/bbolt/internal/freelist"
78
)
89

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

14-
tx := &Tx{
15-
db: &DB{
16-
freelist: f,
17-
pageSize: common.DefaultPageSize,
18-
},
19-
meta: &common.Meta{},
20-
pages: make(map[common.Pgid]*common.Page),
21-
}
16+
tx := &Tx{
17+
db: &DB{
18+
freelist: f,
19+
pageSize: common.DefaultPageSize,
20+
},
21+
meta: &common.Meta{},
22+
pages: make(map[common.Pgid]*common.Page),
23+
}
2224

23-
txStats := tx.Stats()
24-
prePageCnt := txStats.GetPageCount()
25-
allocateCnt := f.free_count()
25+
txStats := tx.Stats()
26+
prePageCnt := txStats.GetPageCount()
27+
allocateCnt := f.FreeCount()
2628

27-
if _, err := tx.allocate(allocateCnt); err != nil {
28-
t.Fatal(err)
29-
}
29+
if _, err := tx.allocate(allocateCnt); err != nil {
30+
t.Fatal(err)
31+
}
3032

31-
txStats = tx.Stats()
32-
if txStats.GetPageCount() != prePageCnt+int64(allocateCnt) {
33-
t.Errorf("Allocated %d but got %d page in stats", allocateCnt, txStats.GetPageCount())
33+
txStats = tx.Stats()
34+
if txStats.GetPageCount() != prePageCnt+int64(allocateCnt) {
35+
t.Errorf("Allocated %d but got %d page in stats", allocateCnt, txStats.GetPageCount())
36+
}
37+
})
3438
}
3539
}

bucket.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -903,7 +903,7 @@ func (b *Bucket) free() {
903903
var tx = b.tx
904904
b.forEachPageNode(func(p *common.Page, n *node, _ int) {
905905
if p != nil {
906-
tx.db.freelist.free(tx.meta.Txid(), p)
906+
tx.db.freelist.Free(tx.meta.Txid(), p)
907907
} else {
908908
n.free()
909909
}

bucket_test.go

+3
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,9 @@ func TestBucket_Delete_FreelistOverflow(t *testing.T) {
430430
if reopenFreePages := db.Stats().FreePageN; freePages != reopenFreePages {
431431
t.Fatalf("expected %d free pages, got %+v", freePages, db.Stats())
432432
}
433+
if reopenPendingPages := db.Stats().PendingPageN; reopenPendingPages != 0 {
434+
t.Fatalf("expected no pending pages, got %+v", db.Stats())
435+
}
433436
}
434437

435438
// Ensure that deleting of non-existing key is a no-op.

cmd/bbolt/command_version.go

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"runtime"
66

77
"github.com/spf13/cobra"
8+
89
"go.etcd.io/bbolt/version"
910
)
1011

db.go

+16-8
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
berrors "go.etcd.io/bbolt/errors"
1414
"go.etcd.io/bbolt/internal/common"
15+
fl "go.etcd.io/bbolt/internal/freelist"
1516
)
1617

1718
// The time elapsed between consecutive file locking attempts.
@@ -133,7 +134,7 @@ type DB struct {
133134
rwtx *Tx
134135
txs []*Tx
135136

136-
freelist *freelist
137+
freelist fl.Interface
137138
freelistLoad sync.Once
138139

139140
pagePool sync.Pool
@@ -418,12 +419,12 @@ func (db *DB) loadFreelist() {
418419
db.freelist = newFreelist(db.FreelistType)
419420
if !db.hasSyncedFreelist() {
420421
// Reconstruct free list by scanning the DB.
421-
db.freelist.readIDs(db.freepages())
422+
db.freelist.Init(db.freepages())
422423
} else {
423424
// Read free list from freelist page.
424-
db.freelist.read(db.page(db.meta().Freelist()))
425+
db.freelist.Read(db.page(db.meta().Freelist()))
425426
}
426-
db.stats.FreePageN = db.freelist.free_count()
427+
db.stats.FreePageN = db.freelist.FreeCount()
427428
})
428429
}
429430

@@ -797,7 +798,7 @@ func (db *DB) beginTx() (*Tx, error) {
797798
db.txs = append(db.txs, t)
798799
n := len(db.txs)
799800
if db.freelist != nil {
800-
db.freelist.addReadonlyTXID(t.meta.Txid())
801+
db.freelist.AddReadonlyTXID(t.meta.Txid())
801802
}
802803

803804
// Unlock the meta pages.
@@ -843,7 +844,7 @@ func (db *DB) beginRWTx() (*Tx, error) {
843844
t := &Tx{writable: true}
844845
t.init(db)
845846
db.rwtx = t
846-
db.freelist.freePages()
847+
db.freelist.ReleasePendingPages()
847848
return t, nil
848849
}
849850

@@ -867,7 +868,7 @@ func (db *DB) removeTx(tx *Tx) {
867868
}
868869
n := len(db.txs)
869870
if db.freelist != nil {
870-
db.freelist.removeReadonlyTXID(tx.meta.Txid())
871+
db.freelist.RemoveReadonlyTXID(tx.meta.Txid())
871872
}
872873

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

11571158
// Use pages from the freelist if they are available.
1158-
p.SetId(db.freelist.allocate(txid, count))
1159+
p.SetId(db.freelist.Allocate(txid, count))
11591160
if p.Id() != 0 {
11601161
return p, nil
11611162
}
@@ -1261,6 +1262,13 @@ func (db *DB) freepages() []common.Pgid {
12611262
return fids
12621263
}
12631264

1265+
func newFreelist(freelistType FreelistType) fl.Interface {
1266+
if freelistType == FreelistMapType {
1267+
return fl.NewHashMapFreelist()
1268+
}
1269+
return fl.NewArrayFreelist()
1270+
}
1271+
12641272
// Options represents the options that can be set when opening a database.
12651273
type Options struct {
12661274
// Timeout is the amount of time to wait to obtain a file lock.

0 commit comments

Comments
 (0)