Skip to content

Commit 7f65806

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

File tree

9 files changed

+149
-150
lines changed

9 files changed

+149
-150
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 & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,19 @@ package freelist
22

33
import (
44
"fmt"
5+
"sort"
6+
"unsafe"
57

68
"go.etcd.io/bbolt/internal/common"
79
)
810

9-
type spanMerger interface {
11+
type sharedInterface interface {
12+
// Init initializes this freelist with the given list of pages.
13+
Init(ids common.Pgids)
14+
// Count returns the number of free and pending pages.
15+
Count() int
16+
// FreePageIds returns all free pages.
17+
FreePageIds() common.Pgids
1018
// mergeSpans is merging the given pages into the freelist
1119
mergeSpans(ids common.Pgids)
1220
}
@@ -18,7 +26,7 @@ type txPending struct {
1826
}
1927

2028
type shared struct {
21-
spanMerger
29+
sharedInterface
2230

2331
allocs map[common.Pgid]common.Txid // mapping of Txid that allocated a pgid.
2432
cache map[common.Pgid]struct{} // fast lookup of all free and pending page ids.
@@ -96,7 +104,7 @@ func (t *shared) Rollback(txid common.Txid) {
96104
}
97105
// Remove pages from pending list and mark as free if allocated by txid.
98106
delete(t.pending, txid)
99-
t.spanMerger.mergeSpans(m)
107+
t.sharedInterface.mergeSpans(m)
100108
}
101109

102110
func (t *shared) Release(txid common.Txid) {
@@ -144,6 +152,72 @@ func (t *shared) ReleaseRange(begin, end common.Txid) {
144152
t.mergeSpans(m)
145153
}
146154

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

0 commit comments

Comments
 (0)