mirror of
https://github.com/etcd-io/bbolt.git
synced 2025-05-02 13:39:45 +00:00
Read txns would lock pages allocated after the txn, keeping those pages off the free list until closing the read txn. Instead, track allocating txid to compute page lifetime, freeing pages if all txns between page allocation and page free are closed.
161 lines
4.3 KiB
Go
161 lines
4.3 KiB
Go
package bolt
|
|
|
|
import (
|
|
"math/rand"
|
|
"reflect"
|
|
"sort"
|
|
"testing"
|
|
"unsafe"
|
|
)
|
|
|
|
// Ensure that a page is added to a transaction's freelist.
|
|
func TestFreelist_free(t *testing.T) {
|
|
f := newFreelist()
|
|
f.free(100, &page{id: 12})
|
|
if !reflect.DeepEqual([]pgid{12}, f.pending[100].ids) {
|
|
t.Fatalf("exp=%v; got=%v", []pgid{12}, f.pending[100])
|
|
}
|
|
}
|
|
|
|
// Ensure that a page and its overflow is added to a transaction's freelist.
|
|
func TestFreelist_free_overflow(t *testing.T) {
|
|
f := newFreelist()
|
|
f.free(100, &page{id: 12, overflow: 3})
|
|
if exp := []pgid{12, 13, 14, 15}; !reflect.DeepEqual(exp, f.pending[100].ids) {
|
|
t.Fatalf("exp=%v; got=%v", exp, f.pending[100])
|
|
}
|
|
}
|
|
|
|
// Ensure that a transaction's free pages can be released.
|
|
func TestFreelist_release(t *testing.T) {
|
|
f := newFreelist()
|
|
f.free(100, &page{id: 12, overflow: 1})
|
|
f.free(100, &page{id: 9})
|
|
f.free(102, &page{id: 39})
|
|
f.release(100)
|
|
f.release(101)
|
|
if exp := []pgid{9, 12, 13}; !reflect.DeepEqual(exp, f.ids) {
|
|
t.Fatalf("exp=%v; got=%v", exp, f.ids)
|
|
}
|
|
|
|
f.release(102)
|
|
if exp := []pgid{9, 12, 13, 39}; !reflect.DeepEqual(exp, f.ids) {
|
|
t.Fatalf("exp=%v; got=%v", exp, f.ids)
|
|
}
|
|
}
|
|
|
|
// Ensure that a freelist can find contiguous blocks of pages.
|
|
func TestFreelist_allocate(t *testing.T) {
|
|
f := newFreelist()
|
|
f.ids = []pgid{3, 4, 5, 6, 7, 9, 12, 13, 18}
|
|
if id := int(f.allocate(1, 3)); id != 3 {
|
|
t.Fatalf("exp=3; got=%v", id)
|
|
}
|
|
if id := int(f.allocate(1, 1)); id != 6 {
|
|
t.Fatalf("exp=6; got=%v", id)
|
|
}
|
|
if id := int(f.allocate(1, 3)); id != 0 {
|
|
t.Fatalf("exp=0; got=%v", id)
|
|
}
|
|
if id := int(f.allocate(1, 2)); id != 12 {
|
|
t.Fatalf("exp=12; got=%v", id)
|
|
}
|
|
if id := int(f.allocate(1, 1)); id != 7 {
|
|
t.Fatalf("exp=7; got=%v", id)
|
|
}
|
|
if id := int(f.allocate(1, 0)); id != 0 {
|
|
t.Fatalf("exp=0; got=%v", id)
|
|
}
|
|
if id := int(f.allocate(1, 0)); id != 0 {
|
|
t.Fatalf("exp=0; got=%v", id)
|
|
}
|
|
if exp := []pgid{9, 18}; !reflect.DeepEqual(exp, f.ids) {
|
|
t.Fatalf("exp=%v; got=%v", exp, f.ids)
|
|
}
|
|
|
|
if id := int(f.allocate(1, 1)); id != 9 {
|
|
t.Fatalf("exp=9; got=%v", id)
|
|
}
|
|
if id := int(f.allocate(1, 1)); id != 18 {
|
|
t.Fatalf("exp=18; got=%v", id)
|
|
}
|
|
if id := int(f.allocate(1, 1)); id != 0 {
|
|
t.Fatalf("exp=0; got=%v", id)
|
|
}
|
|
if exp := []pgid{}; !reflect.DeepEqual(exp, f.ids) {
|
|
t.Fatalf("exp=%v; got=%v", exp, f.ids)
|
|
}
|
|
}
|
|
|
|
// Ensure that a freelist can deserialize from a freelist page.
|
|
func TestFreelist_read(t *testing.T) {
|
|
// Create a page.
|
|
var buf [4096]byte
|
|
page := (*page)(unsafe.Pointer(&buf[0]))
|
|
page.flags = freelistPageFlag
|
|
page.count = 2
|
|
|
|
// Insert 2 page ids.
|
|
ids := (*[3]pgid)(unsafe.Pointer(&page.ptr))
|
|
ids[0] = 23
|
|
ids[1] = 50
|
|
|
|
// Deserialize page into a freelist.
|
|
f := newFreelist()
|
|
f.read(page)
|
|
|
|
// Ensure that there are two page ids in the freelist.
|
|
if exp := []pgid{23, 50}; !reflect.DeepEqual(exp, f.ids) {
|
|
t.Fatalf("exp=%v; got=%v", exp, f.ids)
|
|
}
|
|
}
|
|
|
|
// Ensure that a freelist can serialize into a freelist page.
|
|
func TestFreelist_write(t *testing.T) {
|
|
// Create a freelist and write it to a page.
|
|
var buf [4096]byte
|
|
f := &freelist{ids: []pgid{12, 39}, pending: make(map[txid]*txPending)}
|
|
f.pending[100] = &txPending{ids: []pgid{28, 11}}
|
|
f.pending[101] = &txPending{ids: []pgid{3}}
|
|
p := (*page)(unsafe.Pointer(&buf[0]))
|
|
if err := f.write(p); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Read the page back out.
|
|
f2 := newFreelist()
|
|
f2.read(p)
|
|
|
|
// Ensure that the freelist is correct.
|
|
// All pages should be present and in reverse order.
|
|
if exp := []pgid{3, 11, 12, 28, 39}; !reflect.DeepEqual(exp, f2.ids) {
|
|
t.Fatalf("exp=%v; got=%v", exp, f2.ids)
|
|
}
|
|
}
|
|
|
|
func Benchmark_FreelistRelease10K(b *testing.B) { benchmark_FreelistRelease(b, 10000) }
|
|
func Benchmark_FreelistRelease100K(b *testing.B) { benchmark_FreelistRelease(b, 100000) }
|
|
func Benchmark_FreelistRelease1000K(b *testing.B) { benchmark_FreelistRelease(b, 1000000) }
|
|
func Benchmark_FreelistRelease10000K(b *testing.B) { benchmark_FreelistRelease(b, 10000000) }
|
|
|
|
func benchmark_FreelistRelease(b *testing.B, size int) {
|
|
ids := randomPgids(size)
|
|
pending := randomPgids(len(ids) / 400)
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
txp := &txPending{ids: pending}
|
|
f := &freelist{ids: ids, pending: map[txid]*txPending{1: txp}}
|
|
f.release(1)
|
|
}
|
|
}
|
|
|
|
func randomPgids(n int) []pgid {
|
|
rand.Seed(42)
|
|
pgids := make(pgids, n)
|
|
for i := range pgids {
|
|
pgids[i] = pgid(rand.Int63())
|
|
}
|
|
sort.Sort(pgids)
|
|
return pgids
|
|
}
|