Skip to content

Commit a78f337

Browse files
committed
move serializer into Interface
Signed-off-by: Thomas Jungblut <[email protected]>
1 parent 7e8d09b commit a78f337

File tree

9 files changed

+149
-151
lines changed

9 files changed

+149
-151
lines changed

db.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,8 @@ type DB struct {
135135
rwtx *Tx
136136
txs []*Tx
137137

138-
freelist fl.Interface
139-
freelistSerializer fl.Serializable
140-
freelistLoad sync.Once
138+
freelist fl.Interface
139+
freelistLoad sync.Once
141140

142141
pagePool sync.Pool
143142

@@ -192,7 +191,6 @@ func Open(path string, mode os.FileMode, options *Options) (db *DB, err error) {
192191
db.NoFreelistSync = options.NoFreelistSync
193192
db.PreLoadFreelist = options.PreLoadFreelist
194193
db.FreelistType = options.FreelistType
195-
db.freelistSerializer = fl.Serializer{}
196194
db.Mlock = options.Mlock
197195

198196
// Set default values for later DB operations.
@@ -425,7 +423,7 @@ func (db *DB) loadFreelist() {
425423
db.freelist.Init(db.freepages())
426424
} else {
427425
// Read free list from freelist page.
428-
db.freelistSerializer.Read(db.freelist, db.page(db.meta().Freelist()))
426+
db.freelist.Read(db.page(db.meta().Freelist()))
429427
}
430428
db.stats.FreePageN = db.freelist.FreeCount()
431429
db.stats.PendingPageN = db.freelist.PendingCount()

internal/freelist/array.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,6 @@ func NewArrayFreelist() Interface {
111111
},
112112
}
113113
// this loopy reference allows us to share the span merging via interfaces
114-
a.shared.spanMerger = a
114+
a.shared.sharedInterface = a
115115
return a
116116
}

internal/freelist/freelist.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,21 @@ import (
66
"go.etcd.io/bbolt/internal/common"
77
)
88

9+
type ReadWriter interface {
10+
// Read calls Init with the page ids stored in te given page.
11+
Read(page *common.Page)
12+
13+
// Write writes the freelist into the given page.
14+
Write(page *common.Page)
15+
16+
// EstimatedWritePageSize returns the size of the freelist after serialization in Write.
17+
// This should never underestimate the size.
18+
EstimatedWritePageSize() int
19+
}
20+
921
type Interface interface {
22+
ReadWriter
23+
1024
// Init initializes this freelist with the given list of pages.
1125
Init(ids common.Pgids)
1226

@@ -58,8 +72,8 @@ func Copyall(f Interface, dst []common.Pgid) {
5872
}
5973

6074
// Reload reads the freelist from a page and filters out pending items.
61-
func Reload(s Serializable, f Interface, p *common.Page) {
62-
s.Read(f, p)
75+
func Reload(f Interface, p *common.Page) {
76+
f.Read(p)
6377
NoSyncReload(f, p.FreelistPageIds())
6478
}
6579

internal/freelist/freelist_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"reflect"
77
"sort"
88
"testing"
9+
"unsafe"
910

1011
"go.etcd.io/bbolt/internal/common"
1112
)
@@ -178,6 +179,52 @@ func TestFreelist_releaseRange(t *testing.T) {
178179
}
179180
}
180181

182+
// Ensure that a freelist can deserialize from a freelist page.
183+
func TestFreelist_read(t *testing.T) {
184+
// Create a page.
185+
var buf [4096]byte
186+
page := (*common.Page)(unsafe.Pointer(&buf[0]))
187+
page.SetFlags(common.FreelistPageFlag)
188+
page.SetCount(2)
189+
190+
// Insert 2 page ids.
191+
ids := (*[3]common.Pgid)(unsafe.Pointer(uintptr(unsafe.Pointer(page)) + unsafe.Sizeof(*page)))
192+
ids[0] = 23
193+
ids[1] = 50
194+
195+
// Deserialize page into a freelist.
196+
f := newTestFreelist()
197+
f.Read(page)
198+
199+
// Ensure that there are two page ids in the freelist.
200+
if exp := common.Pgids([]common.Pgid{23, 50}); !reflect.DeepEqual(exp, f.FreePageIds()) {
201+
t.Fatalf("exp=%v; got=%v", exp, f.FreePageIds())
202+
}
203+
}
204+
205+
// Ensure that a freelist can serialize into a freelist page.
206+
func TestFreelist_write(t *testing.T) {
207+
// Create a freelist and write it to a page.
208+
var buf [4096]byte
209+
f := newTestFreelist()
210+
211+
f.Init([]common.Pgid{12, 39})
212+
f.pendingPageIds()[100] = &txPending{ids: []common.Pgid{28, 11}}
213+
f.pendingPageIds()[101] = &txPending{ids: []common.Pgid{3}}
214+
p := (*common.Page)(unsafe.Pointer(&buf[0]))
215+
f.Write(p)
216+
217+
// Read the page back out.
218+
f2 := newTestFreelist()
219+
f2.Read(p)
220+
221+
// Ensure that the freelist is correct.
222+
// All pages should be present and in reverse order.
223+
if exp := common.Pgids([]common.Pgid{3, 11, 12, 28, 39}); !reflect.DeepEqual(exp, f2.FreePageIds()) {
224+
t.Fatalf("exp=%v; got=%v", exp, f2.FreePageIds())
225+
}
226+
}
227+
181228
func Benchmark_FreelistRelease10K(b *testing.B) { benchmark_FreelistRelease(b, 10000) }
182229
func Benchmark_FreelistRelease100K(b *testing.B) { benchmark_FreelistRelease(b, 100000) }
183230
func Benchmark_FreelistRelease1000K(b *testing.B) { benchmark_FreelistRelease(b, 1000000) }

internal/freelist/hashmap.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,6 @@ func NewHashMapFreelist() Interface {
296296
backwardMap: make(map[common.Pgid]uint64),
297297
}
298298
// this loopy reference allows us to share the span merging via interfaces
299-
hm.shared.spanMerger = hm
299+
hm.shared.sharedInterface = hm
300300
return hm
301301
}

internal/freelist/serde.go

Lines changed: 0 additions & 79 deletions
This file was deleted.

internal/freelist/serde_test.go

Lines changed: 0 additions & 55 deletions
This file was deleted.

internal/freelist/shared.go

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,18 @@ package freelist
22

33
import (
44
"fmt"
5-
65
"go.etcd.io/bbolt/internal/common"
6+
"sort"
7+
"unsafe"
78
)
89

9-
type spanMerger interface {
10+
type sharedInterface interface {
11+
// Init initializes this freelist with the given list of pages.
12+
Init(ids common.Pgids)
13+
// Count returns the number of free and pending pages.
14+
Count() int
15+
// FreePageIds returns all free pages.
16+
FreePageIds() common.Pgids
1017
// mergeSpans is merging the given pages into the freelist
1118
mergeSpans(ids common.Pgids)
1219
}
@@ -18,7 +25,7 @@ type txPending struct {
1825
}
1926

2027
type shared struct {
21-
spanMerger
28+
sharedInterface
2229

2330
allocs map[common.Pgid]common.Txid // mapping of Txid that allocated a pgid.
2431
cache map[common.Pgid]struct{} // fast lookup of all free and pending page ids.
@@ -96,7 +103,7 @@ func (t *shared) Rollback(txid common.Txid) {
96103
}
97104
// Remove pages from pending list and mark as free if allocated by txid.
98105
delete(t.pending, txid)
99-
t.spanMerger.mergeSpans(m)
106+
t.sharedInterface.mergeSpans(m)
100107
}
101108

102109
func (t *shared) Release(txid common.Txid) {
@@ -144,6 +151,72 @@ func (t *shared) ReleaseRange(begin, end common.Txid) {
144151
t.mergeSpans(m)
145152
}
146153

154+
func (t *shared) Read(p *common.Page) {
155+
if !p.IsFreelistPage() {
156+
panic(fmt.Sprintf("invalid freelist page: %d, page type is %s", p.Id(), p.Typ()))
157+
}
158+
159+
ids := p.FreelistPageIds()
160+
161+
// Copy the list of page ids from the freelist.
162+
if len(ids) == 0 {
163+
t.Init(nil)
164+
} else {
165+
// copy the ids, so we don't modify on the freelist page directly
166+
idsCopy := make([]common.Pgid, len(ids))
167+
copy(idsCopy, ids)
168+
// Make sure they're sorted.
169+
sort.Sort(common.Pgids(idsCopy))
170+
171+
t.Init(idsCopy)
172+
}
173+
}
174+
175+
func (t *shared) EstimatedWritePageSize() int {
176+
n := t.Count()
177+
if n >= 0xFFFF {
178+
// The first element will be used to store the count. See freelist.write.
179+
n++
180+
}
181+
return int(common.PageHeaderSize) + (int(unsafe.Sizeof(common.Pgid(0))) * n)
182+
}
183+
184+
func (t *shared) Write(p *common.Page) {
185+
// Combine the old free pgids and pgids waiting on an open transaction.
186+
187+
// Update the header flag.
188+
p.SetFlags(common.FreelistPageFlag)
189+
190+
// The page.count can only hold up to 64k elements so if we overflow that
191+
// number then we handle it by putting the size in the first element.
192+
l := t.Count()
193+
if l == 0 {
194+
p.SetCount(uint16(l))
195+
} else if l < 0xFFFF {
196+
p.SetCount(uint16(l))
197+
data := common.UnsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p))
198+
ids := unsafe.Slice((*common.Pgid)(data), l)
199+
t.copyall(ids)
200+
} else {
201+
p.SetCount(0xFFFF)
202+
data := common.UnsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p))
203+
ids := unsafe.Slice((*common.Pgid)(data), l+1)
204+
ids[0] = common.Pgid(l)
205+
t.copyall(ids[1:])
206+
}
207+
}
208+
209+
// Copyall copies a list of all free ids and all pending ids in one sorted list.
210+
// f.count returns the minimum length required for dst.
211+
func (t *shared) copyall(dst []common.Pgid) {
212+
m := make(common.Pgids, 0, t.PendingCount())
213+
for _, txp := range t.pendingPageIds() {
214+
m = append(m, txp.ids...)
215+
}
216+
sort.Sort(m)
217+
common.Mergepgids(dst, t.FreePageIds(), m)
218+
}
219+
147220
// reindex rebuilds the free cache based on available and pending free lists.
148221
func (t *shared) reindex(free common.Pgids, pending map[common.Txid]*txPending) {
149222
t.cache = make(map[common.Pgid]struct{}, len(free))

0 commit comments

Comments
 (0)