Skip to content

Commit 953d46a

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 953d46a

21 files changed

+924
-977
lines changed

allocate_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ 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+
f := freelist.NewFreelist(freelist.FreelistArrayType)
1112
ids := []common.Pgid{2, 3}
12-
f.readIDs(ids)
13+
f.Init(ids)
1314

1415
tx := &Tx{
1516
db: &DB{
@@ -22,7 +23,7 @@ func TestTx_allocatePageStats(t *testing.T) {
2223

2324
txStats := tx.Stats()
2425
prePageCnt := txStats.GetPageCount()
25-
allocateCnt := f.free_count()
26+
allocateCnt := f.FreeCount()
2627

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

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

concurrent_test.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import (
1717
"time"
1818
"unicode/utf8"
1919

20+
"go.etcd.io/bbolt/internal/freelist"
21+
2022
"github.com/stretchr/testify/require"
2123
"golang.org/x/sync/errgroup"
2224

@@ -237,9 +239,9 @@ func mustOpenDB(t *testing.T, dbPath string, o *bolt.Options) *bolt.DB {
237239
o = bolt.DefaultOptions
238240
}
239241

240-
freelistType := bolt.FreelistArrayType
241-
if env := os.Getenv("TEST_FREELIST_TYPE"); env == string(bolt.FreelistMapType) {
242-
freelistType = bolt.FreelistMapType
242+
freelistType := freelist.FreelistArrayType
243+
if env := os.Getenv("TEST_FREELIST_TYPE"); env == string(freelist.FreelistMapType) {
244+
freelistType = freelist.FreelistMapType
243245
}
244246

245247
o.FreelistType = freelistType
@@ -769,29 +771,29 @@ func TestConcurrentRepeatableRead(t *testing.T) {
769771
testCases := []struct {
770772
name string
771773
noFreelistSync bool
772-
freelistType bolt.FreelistType
774+
freelistType freelist.FreelistType
773775
}{
774776
// [array] freelist
775777
{
776778
name: "sync array freelist",
777779
noFreelistSync: false,
778-
freelistType: bolt.FreelistArrayType,
780+
freelistType: freelist.FreelistArrayType,
779781
},
780782
{
781783
name: "not sync array freelist",
782784
noFreelistSync: true,
783-
freelistType: bolt.FreelistArrayType,
785+
freelistType: freelist.FreelistArrayType,
784786
},
785787
// [map] freelist
786788
{
787789
name: "sync map freelist",
788790
noFreelistSync: false,
789-
freelistType: bolt.FreelistMapType,
791+
freelistType: freelist.FreelistMapType,
790792
},
791793
{
792794
name: "not sync map freelist",
793795
noFreelistSync: true,
794-
freelistType: bolt.FreelistMapType,
796+
freelistType: freelist.FreelistMapType,
795797
},
796798
}
797799

db.go

Lines changed: 17 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,12 @@ 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.
1920
const flockRetryTimeout = 50 * time.Millisecond
2021

21-
// FreelistType is the type of the freelist backend
22-
type FreelistType string
23-
24-
// TODO(ahrtr): eventually we should (step by step)
25-
// 1. default to `FreelistMapType`;
26-
// 2. remove the `FreelistArrayType`, do not export `FreelistMapType`
27-
// and remove field `FreelistType' from both `DB` and `Options`;
28-
const (
29-
// FreelistArrayType indicates backend freelist type is array
30-
FreelistArrayType = FreelistType("array")
31-
// FreelistMapType indicates backend freelist type is hashmap
32-
FreelistMapType = FreelistType("hashmap")
33-
)
34-
3522
// DB represents a collection of buckets persisted to a file on disk.
3623
// All data access is performed through transactions which can be obtained through the DB.
3724
// All the functions on DB will return a ErrDatabaseNotOpen if accessed before Open() is called.
@@ -70,7 +57,7 @@ type DB struct {
7057
// The alternative one is using hashmap, it is faster in almost all circumstances
7158
// but it doesn't guarantee that it offers the smallest page id available. In normal case it is safe.
7259
// The default type is array
73-
FreelistType FreelistType
60+
FreelistType fl.FreelistType
7461

7562
// When true, skips the truncate call when growing the database.
7663
// Setting this to true is only safe on non-ext3/ext4 systems.
@@ -134,8 +121,9 @@ type DB struct {
134121
rwtx *Tx
135122
txs []*Tx
136123

137-
freelist *freelist
138-
freelistLoad sync.Once
124+
freelist fl.Freelist
125+
freelistSerializer fl.Serializable
126+
freelistLoad sync.Once
139127

140128
pagePool sync.Pool
141129

@@ -190,6 +178,7 @@ func Open(path string, mode os.FileMode, options *Options) (db *DB, err error) {
190178
db.NoFreelistSync = options.NoFreelistSync
191179
db.PreLoadFreelist = options.PreLoadFreelist
192180
db.FreelistType = options.FreelistType
181+
db.freelistSerializer = fl.Serializer{}
193182
db.Mlock = options.Mlock
194183

195184
// Set default values for later DB operations.
@@ -416,15 +405,16 @@ func (db *DB) getPageSizeFromSecondMeta() (int, bool, error) {
416405
// concurrent accesses being made to the freelist.
417406
func (db *DB) loadFreelist() {
418407
db.freelistLoad.Do(func() {
419-
db.freelist = newFreelist(db.FreelistType)
408+
db.freelist = fl.NewFreelist(db.FreelistType)
420409
if !db.hasSyncedFreelist() {
421410
// Reconstruct free list by scanning the DB.
422-
db.freelist.readIDs(db.freepages())
411+
db.freelist.Init(db.freepages())
423412
} else {
424413
// Read free list from freelist page.
425-
db.freelist.read(db.page(db.meta().Freelist()))
414+
db.freelistSerializer.Read(db.freelist, db.page(db.meta().Freelist()))
426415
}
427-
db.stats.FreePageN = db.freelist.free_count()
416+
db.stats.FreePageN = db.freelist.FreeCount()
417+
db.stats.PendingPageN = db.freelist.PendingCount()
428418
})
429419
}
430420

@@ -854,14 +844,14 @@ func (db *DB) freePages() {
854844
minid = db.txs[0].meta.Txid()
855845
}
856846
if minid > 0 {
857-
db.freelist.release(minid - 1)
847+
db.freelist.Release(minid - 1)
858848
}
859849
// Release unused txid extents.
860850
for _, t := range db.txs {
861-
db.freelist.releaseRange(minid, t.meta.Txid()-1)
851+
db.freelist.ReleaseRange(minid, t.meta.Txid()-1)
862852
minid = t.meta.Txid() + 1
863853
}
864-
db.freelist.releaseRange(minid, common.Txid(0xFFFFFFFFFFFFFFFF))
854+
db.freelist.ReleaseRange(minid, common.Txid(0xFFFFFFFFFFFFFFFF))
865855
// Any page both allocated and freed in an extent is safe to release.
866856
}
867857

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

11781168
// Use pages from the freelist if they are available.
1179-
p.SetId(db.freelist.allocate(txid, count))
1169+
p.SetId(db.freelist.Allocate(txid, count))
11801170
if p.Id() != 0 {
11811171
return p, nil
11821172
}
@@ -1305,7 +1295,7 @@ type Options struct {
13051295
// The alternative one is using hashmap, it is faster in almost all circumstances
13061296
// but it doesn't guarantee that it offers the smallest page id available. In normal case it is safe.
13071297
// The default type is array
1308-
FreelistType FreelistType
1298+
FreelistType fl.FreelistType
13091299

13101300
// Open database in read-only mode. Uses flock(..., LOCK_SH |LOCK_NB) to
13111301
// grab a shared lock (UNIX).
@@ -1360,7 +1350,7 @@ func (o *Options) String() string {
13601350
var DefaultOptions = &Options{
13611351
Timeout: 0,
13621352
NoGrowSync: false,
1363-
FreelistType: FreelistArrayType,
1353+
FreelistType: fl.FreelistArrayType,
13641354
}
13651355

13661356
// Stats represents statistics about the database.

0 commit comments

Comments
 (0)