Skip to content

Commit 56634cd

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 53977ba commit 56634cd

19 files changed

+909
-965
lines changed

allocate_test.go

Lines changed: 24 additions & 20 deletions
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.Freelist{"hashmap": freelist.NewHashMap(), "array": freelist.NewArray()} {
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 3 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
berrors "go.etcd.io/bbolt/errors"
1515
"go.etcd.io/bbolt/internal/common"
16+
fl "go.etcd.io/bbolt/internal/freelist"
1617
)
1718

1819
// The time elapsed between consecutive file locking attempts.
@@ -134,8 +135,9 @@ type DB struct {
134135
rwtx *Tx
135136
txs []*Tx
136137

137-
freelist *freelist
138-
freelistLoad sync.Once
138+
freelist fl.Freelist
139+
freelistSerializer fl.Serializable
140+
freelistLoad sync.Once
139141

140142
pagePool sync.Pool
141143

@@ -190,6 +192,7 @@ func Open(path string, mode os.FileMode, options *Options) (db *DB, err error) {
190192
db.NoFreelistSync = options.NoFreelistSync
191193
db.PreLoadFreelist = options.PreLoadFreelist
192194
db.FreelistType = options.FreelistType
195+
db.freelistSerializer = fl.Serializer{}
193196
db.Mlock = options.Mlock
194197

195198
// Set default values for later DB operations.
@@ -419,12 +422,13 @@ func (db *DB) loadFreelist() {
419422
db.freelist = newFreelist(db.FreelistType)
420423
if !db.hasSyncedFreelist() {
421424
// Reconstruct free list by scanning the DB.
422-
db.freelist.readIDs(db.freepages())
425+
db.freelist.Init(db.freepages())
423426
} else {
424427
// Read free list from freelist page.
425-
db.freelist.read(db.page(db.meta().Freelist()))
428+
db.freelistSerializer.Read(db.freelist, db.page(db.meta().Freelist()))
426429
}
427-
db.stats.FreePageN = db.freelist.free_count()
430+
db.stats.FreePageN = db.freelist.FreeCount()
431+
db.stats.PendingPageN = db.freelist.PendingCount()
428432
})
429433
}
430434

@@ -854,14 +858,14 @@ func (db *DB) freePages() {
854858
minid = db.txs[0].meta.Txid()
855859
}
856860
if minid > 0 {
857-
db.freelist.release(minid - 1)
861+
db.freelist.Release(minid - 1)
858862
}
859863
// Release unused txid extents.
860864
for _, t := range db.txs {
861-
db.freelist.releaseRange(minid, t.meta.Txid()-1)
865+
db.freelist.ReleaseRange(minid, t.meta.Txid()-1)
862866
minid = t.meta.Txid() + 1
863867
}
864-
db.freelist.releaseRange(minid, common.Txid(0xFFFFFFFFFFFFFFFF))
868+
db.freelist.ReleaseRange(minid, common.Txid(0xFFFFFFFFFFFFFFFF))
865869
// Any page both allocated and freed in an extent is safe to release.
866870
}
867871

@@ -1176,7 +1180,7 @@ func (db *DB) allocate(txid common.Txid, count int) (*common.Page, error) {
11761180
p.SetOverflow(uint32(count - 1))
11771181

11781182
// Use pages from the freelist if they are available.
1179-
p.SetId(db.freelist.allocate(txid, count))
1183+
p.SetId(db.freelist.Allocate(txid, count))
11801184
if p.Id() != 0 {
11811185
return p, nil
11821186
}
@@ -1282,6 +1286,13 @@ func (db *DB) freepages() []common.Pgid {
12821286
return fids
12831287
}
12841288

1289+
func newFreelist(freelistType FreelistType) fl.Freelist {
1290+
if freelistType == FreelistMapType {
1291+
return fl.NewHashMap()
1292+
}
1293+
return fl.NewArray()
1294+
}
1295+
12851296
// Options represents the options that can be set when opening a database.
12861297
type Options struct {
12871298
// Timeout is the amount of time to wait to obtain a file lock.

0 commit comments

Comments
 (0)