mirror of https://github.com/etcd-io/bbolt.git
623 lines
20 KiB
Go
623 lines
20 KiB
Go
package freelist
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"math/rand"
|
|
"os"
|
|
"reflect"
|
|
"slices"
|
|
"sort"
|
|
"testing"
|
|
"testing/quick"
|
|
"unsafe"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"go.etcd.io/bbolt/internal/common"
|
|
)
|
|
|
|
// TestFreelistType is used as a env variable for test to indicate the backend type
|
|
const TestFreelistType = "TEST_FREELIST_TYPE"
|
|
|
|
// Ensure that a page is added to a transaction's freelist.
|
|
func TestFreelist_free(t *testing.T) {
|
|
f := newTestFreelist()
|
|
f.Free(100, common.NewPage(12, 0, 0, 0))
|
|
if !reflect.DeepEqual([]common.Pgid{12}, f.pendingPageIds()[100].ids) {
|
|
t.Fatalf("exp=%v; got=%v", []common.Pgid{12}, f.pendingPageIds()[100].ids)
|
|
}
|
|
}
|
|
|
|
// Ensure that a page and its overflow is added to a transaction's freelist.
|
|
func TestFreelist_free_overflow(t *testing.T) {
|
|
f := newTestFreelist()
|
|
f.Free(100, common.NewPage(12, 0, 0, 3))
|
|
if exp := []common.Pgid{12, 13, 14, 15}; !reflect.DeepEqual(exp, f.pendingPageIds()[100].ids) {
|
|
t.Fatalf("exp=%v; got=%v", exp, f.pendingPageIds()[100].ids)
|
|
}
|
|
}
|
|
|
|
// Ensure that double freeing a page is causing a panic
|
|
func TestFreelist_free_double_free_panics(t *testing.T) {
|
|
f := newTestFreelist()
|
|
f.Free(100, common.NewPage(12, 0, 0, 3))
|
|
require.Panics(t, func() {
|
|
f.Free(100, common.NewPage(12, 0, 0, 3))
|
|
})
|
|
}
|
|
|
|
// Ensure that attempting to free the meta page panics
|
|
func TestFreelist_free_meta_panics(t *testing.T) {
|
|
f := newTestFreelist()
|
|
require.Panics(t, func() {
|
|
f.Free(100, common.NewPage(0, 0, 0, 0))
|
|
})
|
|
require.Panics(t, func() {
|
|
f.Free(100, common.NewPage(1, 0, 0, 0))
|
|
})
|
|
}
|
|
|
|
func TestFreelist_free_freelist(t *testing.T) {
|
|
f := newTestFreelist()
|
|
f.Free(100, common.NewPage(12, common.FreelistPageFlag, 0, 0))
|
|
pp := f.pendingPageIds()[100]
|
|
require.Equal(t, []common.Pgid{12}, pp.ids)
|
|
require.Equal(t, []common.Txid{0}, pp.alloctx)
|
|
}
|
|
|
|
func TestFreelist_free_freelist_alloctx(t *testing.T) {
|
|
f := newTestFreelist()
|
|
f.Free(100, common.NewPage(12, common.FreelistPageFlag, 0, 0))
|
|
f.Rollback(100)
|
|
require.Empty(t, f.freePageIds())
|
|
require.Empty(t, f.pendingPageIds())
|
|
require.False(t, f.Freed(12))
|
|
|
|
f.Free(101, common.NewPage(12, common.FreelistPageFlag, 0, 0))
|
|
require.True(t, f.Freed(12))
|
|
if exp := []common.Pgid{12}; !reflect.DeepEqual(exp, f.pendingPageIds()[101].ids) {
|
|
t.Fatalf("exp=%v; got=%v", exp, f.pendingPageIds()[101].ids)
|
|
}
|
|
f.ReleasePendingPages()
|
|
require.True(t, f.Freed(12))
|
|
require.Empty(t, f.pendingPageIds())
|
|
if exp := common.Pgids([]common.Pgid{12}); !reflect.DeepEqual(exp, f.freePageIds()) {
|
|
t.Fatalf("exp=%v; got=%v", exp, f.freePageIds())
|
|
}
|
|
}
|
|
|
|
// Ensure that a transaction's free pages can be released.
|
|
func TestFreelist_release(t *testing.T) {
|
|
f := newTestFreelist()
|
|
f.Free(100, common.NewPage(12, 0, 0, 1))
|
|
f.Free(100, common.NewPage(9, 0, 0, 0))
|
|
f.Free(102, common.NewPage(39, 0, 0, 0))
|
|
f.release(100)
|
|
f.release(101)
|
|
if exp := common.Pgids([]common.Pgid{9, 12, 13}); !reflect.DeepEqual(exp, f.freePageIds()) {
|
|
t.Fatalf("exp=%v; got=%v", exp, f.freePageIds())
|
|
}
|
|
|
|
f.release(102)
|
|
if exp := common.Pgids([]common.Pgid{9, 12, 13, 39}); !reflect.DeepEqual(exp, f.freePageIds()) {
|
|
t.Fatalf("exp=%v; got=%v", exp, f.freePageIds())
|
|
}
|
|
}
|
|
|
|
// Ensure that releaseRange handles boundary conditions correctly
|
|
func TestFreelist_releaseRange(t *testing.T) {
|
|
type testRange struct {
|
|
begin, end common.Txid
|
|
}
|
|
|
|
type testPage struct {
|
|
id common.Pgid
|
|
n int
|
|
allocTxn common.Txid
|
|
freeTxn common.Txid
|
|
}
|
|
|
|
var releaseRangeTests = []struct {
|
|
title string
|
|
pagesIn []testPage
|
|
releaseRanges []testRange
|
|
wantFree []common.Pgid
|
|
}{
|
|
{
|
|
title: "Single pending in range",
|
|
pagesIn: []testPage{{id: 3, n: 1, allocTxn: 100, freeTxn: 200}},
|
|
releaseRanges: []testRange{{1, 300}},
|
|
wantFree: []common.Pgid{3},
|
|
},
|
|
{
|
|
title: "Single pending with minimum end range",
|
|
pagesIn: []testPage{{id: 3, n: 1, allocTxn: 100, freeTxn: 200}},
|
|
releaseRanges: []testRange{{1, 200}},
|
|
wantFree: []common.Pgid{3},
|
|
},
|
|
{
|
|
title: "Single pending outsize minimum end range",
|
|
pagesIn: []testPage{{id: 3, n: 1, allocTxn: 100, freeTxn: 200}},
|
|
releaseRanges: []testRange{{1, 199}},
|
|
wantFree: []common.Pgid{},
|
|
},
|
|
{
|
|
title: "Single pending with minimum begin range",
|
|
pagesIn: []testPage{{id: 3, n: 1, allocTxn: 100, freeTxn: 200}},
|
|
releaseRanges: []testRange{{100, 300}},
|
|
wantFree: []common.Pgid{3},
|
|
},
|
|
{
|
|
title: "Single pending outside minimum begin range",
|
|
pagesIn: []testPage{{id: 3, n: 1, allocTxn: 100, freeTxn: 200}},
|
|
releaseRanges: []testRange{{101, 300}},
|
|
wantFree: []common.Pgid{},
|
|
},
|
|
{
|
|
title: "Single pending in minimum range",
|
|
pagesIn: []testPage{{id: 3, n: 1, allocTxn: 199, freeTxn: 200}},
|
|
releaseRanges: []testRange{{199, 200}},
|
|
wantFree: []common.Pgid{3},
|
|
},
|
|
{
|
|
title: "Single pending and read transaction at 199",
|
|
pagesIn: []testPage{{id: 3, n: 1, allocTxn: 199, freeTxn: 200}},
|
|
releaseRanges: []testRange{{100, 198}, {200, 300}},
|
|
wantFree: []common.Pgid{},
|
|
},
|
|
{
|
|
title: "Adjacent pending and read transactions at 199, 200",
|
|
pagesIn: []testPage{
|
|
{id: 3, n: 1, allocTxn: 199, freeTxn: 200},
|
|
{id: 4, n: 1, allocTxn: 200, freeTxn: 201},
|
|
},
|
|
releaseRanges: []testRange{
|
|
{100, 198},
|
|
{200, 199}, // Simulate the ranges db.freePages might produce.
|
|
{201, 300},
|
|
},
|
|
wantFree: []common.Pgid{},
|
|
},
|
|
{
|
|
title: "Out of order ranges",
|
|
pagesIn: []testPage{
|
|
{id: 3, n: 1, allocTxn: 199, freeTxn: 200},
|
|
{id: 4, n: 1, allocTxn: 200, freeTxn: 201},
|
|
},
|
|
releaseRanges: []testRange{
|
|
{201, 199},
|
|
{201, 200},
|
|
{200, 200},
|
|
},
|
|
wantFree: []common.Pgid{},
|
|
},
|
|
{
|
|
title: "Multiple pending, read transaction at 150",
|
|
pagesIn: []testPage{
|
|
{id: 3, n: 1, allocTxn: 100, freeTxn: 200},
|
|
{id: 4, n: 1, allocTxn: 100, freeTxn: 125},
|
|
{id: 5, n: 1, allocTxn: 125, freeTxn: 150},
|
|
{id: 6, n: 1, allocTxn: 125, freeTxn: 175},
|
|
{id: 7, n: 2, allocTxn: 150, freeTxn: 175},
|
|
{id: 9, n: 2, allocTxn: 175, freeTxn: 200},
|
|
},
|
|
releaseRanges: []testRange{{50, 149}, {151, 300}},
|
|
wantFree: []common.Pgid{4, 9, 10},
|
|
},
|
|
}
|
|
|
|
for _, c := range releaseRangeTests {
|
|
t.Run(c.title, func(t *testing.T) {
|
|
f := newTestFreelist()
|
|
var ids []common.Pgid
|
|
for _, p := range c.pagesIn {
|
|
for i := uint64(0); i < uint64(p.n); i++ {
|
|
ids = append(ids, common.Pgid(uint64(p.id)+i))
|
|
}
|
|
}
|
|
f.Init(ids)
|
|
for _, p := range c.pagesIn {
|
|
f.Allocate(p.allocTxn, p.n)
|
|
}
|
|
|
|
for _, p := range c.pagesIn {
|
|
f.Free(p.freeTxn, common.NewPage(p.id, 0, 0, uint32(p.n-1)))
|
|
}
|
|
|
|
for _, r := range c.releaseRanges {
|
|
f.releaseRange(r.begin, r.end)
|
|
}
|
|
|
|
require.Equal(t, common.Pgids(c.wantFree), f.freePageIds())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFreeList_init(t *testing.T) {
|
|
buf := make([]byte, 4096)
|
|
f := newTestFreelist()
|
|
f.Init(common.Pgids{5, 6, 8})
|
|
|
|
p := common.LoadPage(buf)
|
|
f.Write(p)
|
|
|
|
f2 := newTestFreelist()
|
|
f2.Read(p)
|
|
require.Equal(t, common.Pgids{5, 6, 8}, f2.freePageIds())
|
|
|
|
// When initializing the freelist with an empty list of page ID,
|
|
// it should reset the freelist page IDs.
|
|
f2.Init([]common.Pgid{})
|
|
require.Equal(t, common.Pgids{}, f2.freePageIds())
|
|
}
|
|
|
|
func TestFreeList_reload(t *testing.T) {
|
|
buf := make([]byte, 4096)
|
|
f := newTestFreelist()
|
|
f.Init(common.Pgids{5, 6, 8})
|
|
|
|
p := common.LoadPage(buf)
|
|
f.Write(p)
|
|
|
|
f2 := newTestFreelist()
|
|
f2.Read(p)
|
|
require.Equal(t, common.Pgids{5, 6, 8}, f2.freePageIds())
|
|
|
|
f2.Free(common.Txid(5), common.NewPage(10, common.LeafPageFlag, 0, 2))
|
|
|
|
// reload shouldn't affect the pending list
|
|
f2.Reload(p)
|
|
|
|
require.Equal(t, common.Pgids{5, 6, 8}, f2.freePageIds())
|
|
require.Equal(t, []common.Pgid{10, 11, 12}, f2.pendingPageIds()[5].ids)
|
|
}
|
|
|
|
// Ensure that the txIDx swap, less and len are properly implemented
|
|
func TestTxidSorting(t *testing.T) {
|
|
require.NoError(t, quick.Check(func(a []uint64) bool {
|
|
var txids []common.Txid
|
|
for _, txid := range a {
|
|
txids = append(txids, common.Txid(txid))
|
|
}
|
|
|
|
sort.Sort(txIDx(txids))
|
|
|
|
var r []uint64
|
|
for _, txid := range txids {
|
|
r = append(r, uint64(txid))
|
|
}
|
|
|
|
if !slices.IsSorted(r) {
|
|
t.Errorf("txids were not sorted correctly=%v", txids)
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}, nil))
|
|
}
|
|
|
|
// Ensure that a freelist can deserialize from a freelist page.
|
|
func TestFreelist_read(t *testing.T) {
|
|
// Create a page.
|
|
var buf [4096]byte
|
|
page := (*common.Page)(unsafe.Pointer(&buf[0]))
|
|
page.SetFlags(common.FreelistPageFlag)
|
|
page.SetCount(2)
|
|
|
|
// Insert 2 page ids.
|
|
ids := (*[3]common.Pgid)(unsafe.Pointer(uintptr(unsafe.Pointer(page)) + unsafe.Sizeof(*page)))
|
|
ids[0] = 23
|
|
ids[1] = 50
|
|
|
|
// Deserialize page into a freelist.
|
|
f := newTestFreelist()
|
|
f.Read(page)
|
|
|
|
// Ensure that there are two page ids in the freelist.
|
|
if exp := common.Pgids([]common.Pgid{23, 50}); !reflect.DeepEqual(exp, f.freePageIds()) {
|
|
t.Fatalf("exp=%v; got=%v", exp, f.freePageIds())
|
|
}
|
|
}
|
|
|
|
// Ensure that we never read a non-freelist page
|
|
func TestFreelist_read_panics(t *testing.T) {
|
|
buf := make([]byte, 4096)
|
|
page := common.LoadPage(buf)
|
|
page.SetFlags(common.BranchPageFlag)
|
|
page.SetCount(2)
|
|
f := newTestFreelist()
|
|
require.Panics(t, func() {
|
|
f.Read(page)
|
|
})
|
|
}
|
|
|
|
// 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 := newTestFreelist()
|
|
|
|
f.Init([]common.Pgid{12, 39})
|
|
f.pendingPageIds()[100] = &txPending{ids: []common.Pgid{28, 11}}
|
|
f.pendingPageIds()[101] = &txPending{ids: []common.Pgid{3}}
|
|
p := (*common.Page)(unsafe.Pointer(&buf[0]))
|
|
f.Write(p)
|
|
|
|
// Read the page back out.
|
|
f2 := newTestFreelist()
|
|
f2.Read(p)
|
|
|
|
// Ensure that the freelist is correct.
|
|
// All pages should be present and in reverse order.
|
|
if exp := common.Pgids([]common.Pgid{3, 11, 12, 28, 39}); !reflect.DeepEqual(exp, f2.freePageIds()) {
|
|
t.Fatalf("exp=%v; got=%v", exp, f2.freePageIds())
|
|
}
|
|
}
|
|
|
|
func TestFreelist_E2E_HappyPath(t *testing.T) {
|
|
f := newTestFreelist()
|
|
f.Init([]common.Pgid{})
|
|
requirePages(t, f, common.Pgids{}, common.Pgids{})
|
|
|
|
allocated := f.Allocate(common.Txid(1), 5)
|
|
require.Equal(t, common.Pgid(0), allocated)
|
|
// tx.go may now allocate more space, and eventually we need to delete a page again
|
|
f.Free(common.Txid(2), common.NewPage(5, common.LeafPageFlag, 0, 0))
|
|
f.Free(common.Txid(2), common.NewPage(3, common.LeafPageFlag, 0, 0))
|
|
f.Free(common.Txid(2), common.NewPage(8, common.LeafPageFlag, 0, 0))
|
|
// the above will only mark the pages as pending, so free pages should not return anything
|
|
requirePages(t, f, common.Pgids{}, common.Pgids{3, 5, 8})
|
|
|
|
// someone wants to do a read on top of the next tx id
|
|
f.AddReadonlyTXID(common.Txid(3))
|
|
// this should free the above pages for tx 2 entirely
|
|
f.ReleasePendingPages()
|
|
requirePages(t, f, common.Pgids{3, 5, 8}, common.Pgids{})
|
|
|
|
// no span of two pages available should yield a zero-page result
|
|
require.Equal(t, common.Pgid(0), f.Allocate(common.Txid(4), 2))
|
|
// we should be able to allocate those pages independently however,
|
|
// map and array differ in the order they return the pages
|
|
expectedPgids := map[common.Pgid]struct{}{3: {}, 5: {}, 8: {}}
|
|
for i := 0; i < 3; i++ {
|
|
allocated = f.Allocate(common.Txid(4), 1)
|
|
require.Contains(t, expectedPgids, allocated, "expected to find pgid %d", allocated)
|
|
require.False(t, f.Freed(allocated))
|
|
delete(expectedPgids, allocated)
|
|
}
|
|
require.Emptyf(t, expectedPgids, "unexpectedly more than one page was still found")
|
|
// no more free pages to allocate
|
|
require.Equal(t, common.Pgid(0), f.Allocate(common.Txid(4), 1))
|
|
}
|
|
|
|
func TestFreelist_E2E_MultiSpanOverflows(t *testing.T) {
|
|
f := newTestFreelist()
|
|
f.Init([]common.Pgid{})
|
|
f.Free(common.Txid(10), common.NewPage(20, common.LeafPageFlag, 0, 1))
|
|
f.Free(common.Txid(10), common.NewPage(25, common.LeafPageFlag, 0, 2))
|
|
f.Free(common.Txid(10), common.NewPage(35, common.LeafPageFlag, 0, 3))
|
|
f.Free(common.Txid(10), common.NewPage(39, common.LeafPageFlag, 0, 2))
|
|
f.Free(common.Txid(10), common.NewPage(45, common.LeafPageFlag, 0, 4))
|
|
requirePages(t, f, common.Pgids{}, common.Pgids{20, 21, 25, 26, 27, 35, 36, 37, 38, 39, 40, 41, 45, 46, 47, 48, 49})
|
|
f.ReleasePendingPages()
|
|
requirePages(t, f, common.Pgids{20, 21, 25, 26, 27, 35, 36, 37, 38, 39, 40, 41, 45, 46, 47, 48, 49}, common.Pgids{})
|
|
|
|
// that sequence, regardless of implementation, should always yield the same blocks of pages
|
|
allocSequence := []int{7, 5, 3, 2}
|
|
expectedSpanStarts := []common.Pgid{35, 45, 25, 20}
|
|
for i, pageNums := range allocSequence {
|
|
allocated := f.Allocate(common.Txid(11), pageNums)
|
|
require.Equal(t, expectedSpanStarts[i], allocated)
|
|
// ensure all pages in that span are not considered free anymore
|
|
for i := 0; i < pageNums; i++ {
|
|
require.False(t, f.Freed(allocated+common.Pgid(i)))
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFreelist_E2E_Rollbacks(t *testing.T) {
|
|
freelist := newTestFreelist()
|
|
freelist.Init([]common.Pgid{})
|
|
freelist.Free(common.Txid(2), common.NewPage(5, common.LeafPageFlag, 0, 1))
|
|
freelist.Free(common.Txid(2), common.NewPage(8, common.LeafPageFlag, 0, 0))
|
|
requirePages(t, freelist, common.Pgids{}, common.Pgids{5, 6, 8})
|
|
freelist.Rollback(common.Txid(2))
|
|
requirePages(t, freelist, common.Pgids{}, common.Pgids{})
|
|
|
|
// unknown transaction should not trigger anything
|
|
freelist.Free(common.Txid(4), common.NewPage(13, common.LeafPageFlag, 0, 3))
|
|
requirePages(t, freelist, common.Pgids{}, common.Pgids{13, 14, 15, 16})
|
|
freelist.ReleasePendingPages()
|
|
requirePages(t, freelist, common.Pgids{13, 14, 15, 16}, common.Pgids{})
|
|
freelist.Rollback(common.Txid(1337))
|
|
requirePages(t, freelist, common.Pgids{13, 14, 15, 16}, common.Pgids{})
|
|
}
|
|
|
|
func TestFreelist_E2E_RollbackPanics(t *testing.T) {
|
|
freelist := newTestFreelist()
|
|
freelist.Init([]common.Pgid{5})
|
|
requirePages(t, freelist, common.Pgids{5}, common.Pgids{})
|
|
|
|
_ = freelist.Allocate(common.Txid(5), 1)
|
|
require.Panics(t, func() {
|
|
// depending on the verification level, either should panic
|
|
freelist.Free(common.Txid(5), common.NewPage(5, common.LeafPageFlag, 0, 0))
|
|
freelist.Rollback(5)
|
|
})
|
|
}
|
|
|
|
// tests the reloading from another physical page
|
|
func TestFreelist_E2E_Reload(t *testing.T) {
|
|
freelist := newTestFreelist()
|
|
freelist.Init([]common.Pgid{})
|
|
freelist.Free(common.Txid(2), common.NewPage(5, common.LeafPageFlag, 0, 1))
|
|
freelist.Free(common.Txid(2), common.NewPage(8, common.LeafPageFlag, 0, 0))
|
|
freelist.ReleasePendingPages()
|
|
requirePages(t, freelist, common.Pgids{5, 6, 8}, common.Pgids{})
|
|
buf := make([]byte, 4096)
|
|
p := common.LoadPage(buf)
|
|
freelist.Write(p)
|
|
|
|
freelist.Free(common.Txid(3), common.NewPage(3, common.LeafPageFlag, 0, 1))
|
|
freelist.Free(common.Txid(3), common.NewPage(10, common.LeafPageFlag, 0, 2))
|
|
requirePages(t, freelist, common.Pgids{5, 6, 8}, common.Pgids{3, 4, 10, 11, 12})
|
|
|
|
otherBuf := make([]byte, 4096)
|
|
px := common.LoadPage(otherBuf)
|
|
freelist.Write(px)
|
|
|
|
loadFreeList := newTestFreelist()
|
|
loadFreeList.Init([]common.Pgid{})
|
|
loadFreeList.Read(px)
|
|
requirePages(t, loadFreeList, common.Pgids{3, 4, 5, 6, 8, 10, 11, 12}, common.Pgids{})
|
|
// restore the original freelist again
|
|
loadFreeList.Reload(p)
|
|
requirePages(t, loadFreeList, common.Pgids{5, 6, 8}, common.Pgids{})
|
|
|
|
// reload another page with different free pages to test we are deduplicating the free pages with the pending ones correctly
|
|
freelist = newTestFreelist()
|
|
freelist.Init([]common.Pgid{})
|
|
freelist.Free(common.Txid(5), common.NewPage(5, common.LeafPageFlag, 0, 4))
|
|
freelist.Reload(p)
|
|
requirePages(t, freelist, common.Pgids{}, common.Pgids{5, 6, 7, 8, 9})
|
|
}
|
|
|
|
// tests the loading and reloading from physical pages
|
|
func TestFreelist_E2E_SerDe_HappyPath(t *testing.T) {
|
|
freelist := newTestFreelist()
|
|
freelist.Init([]common.Pgid{})
|
|
freelist.Free(common.Txid(2), common.NewPage(5, common.LeafPageFlag, 0, 1))
|
|
freelist.Free(common.Txid(2), common.NewPage(8, common.LeafPageFlag, 0, 0))
|
|
freelist.ReleasePendingPages()
|
|
requirePages(t, freelist, common.Pgids{5, 6, 8}, common.Pgids{})
|
|
|
|
freelist.Free(common.Txid(3), common.NewPage(3, common.LeafPageFlag, 0, 1))
|
|
freelist.Free(common.Txid(3), common.NewPage(10, common.LeafPageFlag, 0, 2))
|
|
requirePages(t, freelist, common.Pgids{5, 6, 8}, common.Pgids{3, 4, 10, 11, 12})
|
|
|
|
buf := make([]byte, 4096)
|
|
p := common.LoadPage(buf)
|
|
require.Equal(t, 80, freelist.EstimatedWritePageSize())
|
|
freelist.Write(p)
|
|
|
|
loadFreeList := newTestFreelist()
|
|
loadFreeList.Init([]common.Pgid{})
|
|
loadFreeList.Read(p)
|
|
requirePages(t, loadFreeList, common.Pgids{3, 4, 5, 6, 8, 10, 11, 12}, common.Pgids{})
|
|
}
|
|
|
|
// tests the loading of a freelist against other implementations with various sizes
|
|
func TestFreelist_E2E_SerDe_AcrossImplementations(t *testing.T) {
|
|
testSizes := []int{0, 1, 10, 100, 1000, math.MaxUint16, math.MaxUint16 + 1, math.MaxUint16 * 2}
|
|
for _, size := range testSizes {
|
|
t.Run(fmt.Sprintf("n=%d", size), func(t *testing.T) {
|
|
freelist := newTestFreelist()
|
|
expectedFreePgids := common.Pgids{}
|
|
for i := 0; i < size; i++ {
|
|
pgid := common.Pgid(i + 2)
|
|
freelist.Free(common.Txid(1), common.NewPage(pgid, common.LeafPageFlag, 0, 0))
|
|
expectedFreePgids = append(expectedFreePgids, pgid)
|
|
}
|
|
freelist.ReleasePendingPages()
|
|
requirePages(t, freelist, expectedFreePgids, common.Pgids{})
|
|
buf := make([]byte, freelist.EstimatedWritePageSize())
|
|
p := common.LoadPage(buf)
|
|
freelist.Write(p)
|
|
|
|
for n, loadFreeList := range map[string]Interface{
|
|
"hashmap": NewHashMapFreelist(),
|
|
"array": NewArrayFreelist(),
|
|
} {
|
|
t.Run(n, func(t *testing.T) {
|
|
loadFreeList.Read(p)
|
|
requirePages(t, loadFreeList, expectedFreePgids, common.Pgids{})
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func requirePages(t *testing.T, f Interface, freePageIds common.Pgids, pendingPageIds common.Pgids) {
|
|
require.Equal(t, f.FreeCount()+f.PendingCount(), f.Count())
|
|
require.Equalf(t, freePageIds, f.freePageIds(), "unexpected free pages")
|
|
require.Equal(t, len(freePageIds), f.FreeCount())
|
|
|
|
pp := allPendingPages(f.pendingPageIds())
|
|
require.Equalf(t, pendingPageIds, pp, "unexpected pending pages")
|
|
require.Equal(t, len(pp), f.PendingCount())
|
|
|
|
for _, pgid := range f.freePageIds() {
|
|
require.Truef(t, f.Freed(pgid), "expected free page to return true on Freed")
|
|
}
|
|
|
|
for _, pgid := range pp {
|
|
require.Truef(t, f.Freed(pgid), "expected pending page to return true on Freed")
|
|
}
|
|
}
|
|
|
|
func allPendingPages(p map[common.Txid]*txPending) common.Pgids {
|
|
pgids := common.Pgids{}
|
|
for _, pending := range p {
|
|
pgids = append(pgids, pending.ids...)
|
|
}
|
|
sort.Sort(pgids)
|
|
return pgids
|
|
}
|
|
|
|
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 := newTestFreelist()
|
|
f.pendingPageIds()[1] = txp
|
|
f.Init(ids)
|
|
f.release(1)
|
|
}
|
|
}
|
|
|
|
func randomPgids(n int) []common.Pgid {
|
|
pgids := make(common.Pgids, n)
|
|
for i := range pgids {
|
|
pgids[i] = common.Pgid(rand.Int63())
|
|
}
|
|
sort.Sort(pgids)
|
|
return pgids
|
|
}
|
|
|
|
func Test_freelist_ReadIDs_and_getFreePageIDs(t *testing.T) {
|
|
f := newTestFreelist()
|
|
exp := common.Pgids([]common.Pgid{3, 4, 5, 6, 7, 9, 12, 13, 18})
|
|
|
|
f.Init(exp)
|
|
|
|
if got := f.freePageIds(); !reflect.DeepEqual(exp, got) {
|
|
t.Fatalf("exp=%v; got=%v", exp, got)
|
|
}
|
|
|
|
f2 := newTestFreelist()
|
|
exp2 := []common.Pgid{}
|
|
f2.Init(exp2)
|
|
|
|
if got2 := f2.freePageIds(); !reflect.DeepEqual(got2, common.Pgids(exp2)) {
|
|
t.Fatalf("exp2=%#v; got2=%#v", exp2, got2)
|
|
}
|
|
|
|
}
|
|
|
|
// newTestFreelist get the freelist type from env and initial the freelist
|
|
func newTestFreelist() Interface {
|
|
if env := os.Getenv(TestFreelistType); env == "hashmap" {
|
|
return NewHashMapFreelist()
|
|
}
|
|
|
|
return NewArrayFreelist()
|
|
}
|