github.com/bhojpur/cache@v0.0.4/pkg/memory/freelist_test.go (about)

     1  package memory
     2  
     3  // Copyright (c) 2018 Bhojpur Consulting Private Limited, India. All rights reserved.
     4  
     5  // Permission is hereby granted, free of charge, to any person obtaining a copy
     6  // of this software and associated documentation files (the "Software"), to deal
     7  // in the Software without restriction, including without limitation the rights
     8  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     9  // copies of the Software, and to permit persons to whom the Software is
    10  // furnished to do so, subject to the following conditions:
    11  
    12  // The above copyright notice and this permission notice shall be included in
    13  // all copies or substantial portions of the Software.
    14  
    15  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    16  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    17  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    18  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    19  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    20  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    21  // THE SOFTWARE.
    22  
    23  import (
    24  	"math/rand"
    25  	"os"
    26  	"reflect"
    27  	"sort"
    28  	"testing"
    29  	"unsafe"
    30  )
    31  
    32  // TestFreelistType is used as a env variable for test to indicate the backend type
    33  const TestFreelistType = "TEST_FREELIST_TYPE"
    34  
    35  // Ensure that a page is added to a transaction's freelist.
    36  func TestFreelist_free(t *testing.T) {
    37  	f := newTestFreelist()
    38  	f.free(100, &page{id: 12})
    39  	if !reflect.DeepEqual([]pgid{12}, f.pending[100].ids) {
    40  		t.Fatalf("exp=%v; got=%v", []pgid{12}, f.pending[100].ids)
    41  	}
    42  }
    43  
    44  // Ensure that a page and its overflow is added to a transaction's freelist.
    45  func TestFreelist_free_overflow(t *testing.T) {
    46  	f := newTestFreelist()
    47  	f.free(100, &page{id: 12, overflow: 3})
    48  	if exp := []pgid{12, 13, 14, 15}; !reflect.DeepEqual(exp, f.pending[100].ids) {
    49  		t.Fatalf("exp=%v; got=%v", exp, f.pending[100].ids)
    50  	}
    51  }
    52  
    53  // Ensure that a transaction's free pages can be released.
    54  func TestFreelist_release(t *testing.T) {
    55  	f := newTestFreelist()
    56  	f.free(100, &page{id: 12, overflow: 1})
    57  	f.free(100, &page{id: 9})
    58  	f.free(102, &page{id: 39})
    59  	f.release(100)
    60  	f.release(101)
    61  	if exp := []pgid{9, 12, 13}; !reflect.DeepEqual(exp, f.getFreePageIDs()) {
    62  		t.Fatalf("exp=%v; got=%v", exp, f.getFreePageIDs())
    63  	}
    64  
    65  	f.release(102)
    66  	if exp := []pgid{9, 12, 13, 39}; !reflect.DeepEqual(exp, f.getFreePageIDs()) {
    67  		t.Fatalf("exp=%v; got=%v", exp, f.getFreePageIDs())
    68  	}
    69  }
    70  
    71  // Ensure that releaseRange handles boundary conditions correctly
    72  func TestFreelist_releaseRange(t *testing.T) {
    73  	type testRange struct {
    74  		begin, end txid
    75  	}
    76  
    77  	type testPage struct {
    78  		id       pgid
    79  		n        int
    80  		allocTxn txid
    81  		freeTxn  txid
    82  	}
    83  
    84  	var releaseRangeTests = []struct {
    85  		title         string
    86  		pagesIn       []testPage
    87  		releaseRanges []testRange
    88  		wantFree      []pgid
    89  	}{
    90  		{
    91  			title:         "Single pending in range",
    92  			pagesIn:       []testPage{{id: 3, n: 1, allocTxn: 100, freeTxn: 200}},
    93  			releaseRanges: []testRange{{1, 300}},
    94  			wantFree:      []pgid{3},
    95  		},
    96  		{
    97  			title:         "Single pending with minimum end range",
    98  			pagesIn:       []testPage{{id: 3, n: 1, allocTxn: 100, freeTxn: 200}},
    99  			releaseRanges: []testRange{{1, 200}},
   100  			wantFree:      []pgid{3},
   101  		},
   102  		{
   103  			title:         "Single pending outsize minimum end range",
   104  			pagesIn:       []testPage{{id: 3, n: 1, allocTxn: 100, freeTxn: 200}},
   105  			releaseRanges: []testRange{{1, 199}},
   106  			wantFree:      nil,
   107  		},
   108  		{
   109  			title:         "Single pending with minimum begin range",
   110  			pagesIn:       []testPage{{id: 3, n: 1, allocTxn: 100, freeTxn: 200}},
   111  			releaseRanges: []testRange{{100, 300}},
   112  			wantFree:      []pgid{3},
   113  		},
   114  		{
   115  			title:         "Single pending outside minimum begin range",
   116  			pagesIn:       []testPage{{id: 3, n: 1, allocTxn: 100, freeTxn: 200}},
   117  			releaseRanges: []testRange{{101, 300}},
   118  			wantFree:      nil,
   119  		},
   120  		{
   121  			title:         "Single pending in minimum range",
   122  			pagesIn:       []testPage{{id: 3, n: 1, allocTxn: 199, freeTxn: 200}},
   123  			releaseRanges: []testRange{{199, 200}},
   124  			wantFree:      []pgid{3},
   125  		},
   126  		{
   127  			title:         "Single pending and read transaction at 199",
   128  			pagesIn:       []testPage{{id: 3, n: 1, allocTxn: 199, freeTxn: 200}},
   129  			releaseRanges: []testRange{{100, 198}, {200, 300}},
   130  			wantFree:      nil,
   131  		},
   132  		{
   133  			title: "Adjacent pending and read transactions at 199, 200",
   134  			pagesIn: []testPage{
   135  				{id: 3, n: 1, allocTxn: 199, freeTxn: 200},
   136  				{id: 4, n: 1, allocTxn: 200, freeTxn: 201},
   137  			},
   138  			releaseRanges: []testRange{
   139  				{100, 198},
   140  				{200, 199}, // Simulate the ranges db.freePages might produce.
   141  				{201, 300},
   142  			},
   143  			wantFree: nil,
   144  		},
   145  		{
   146  			title: "Out of order ranges",
   147  			pagesIn: []testPage{
   148  				{id: 3, n: 1, allocTxn: 199, freeTxn: 200},
   149  				{id: 4, n: 1, allocTxn: 200, freeTxn: 201},
   150  			},
   151  			releaseRanges: []testRange{
   152  				{201, 199},
   153  				{201, 200},
   154  				{200, 200},
   155  			},
   156  			wantFree: nil,
   157  		},
   158  		{
   159  			title: "Multiple pending, read transaction at 150",
   160  			pagesIn: []testPage{
   161  				{id: 3, n: 1, allocTxn: 100, freeTxn: 200},
   162  				{id: 4, n: 1, allocTxn: 100, freeTxn: 125},
   163  				{id: 5, n: 1, allocTxn: 125, freeTxn: 150},
   164  				{id: 6, n: 1, allocTxn: 125, freeTxn: 175},
   165  				{id: 7, n: 2, allocTxn: 150, freeTxn: 175},
   166  				{id: 9, n: 2, allocTxn: 175, freeTxn: 200},
   167  			},
   168  			releaseRanges: []testRange{{50, 149}, {151, 300}},
   169  			wantFree:      []pgid{4, 9, 10},
   170  		},
   171  	}
   172  
   173  	for _, c := range releaseRangeTests {
   174  		f := newTestFreelist()
   175  		var ids []pgid
   176  		for _, p := range c.pagesIn {
   177  			for i := uint64(0); i < uint64(p.n); i++ {
   178  				ids = append(ids, pgid(uint64(p.id)+i))
   179  			}
   180  		}
   181  		f.readIDs(ids)
   182  		for _, p := range c.pagesIn {
   183  			f.allocate(p.allocTxn, p.n)
   184  		}
   185  
   186  		for _, p := range c.pagesIn {
   187  			f.free(p.freeTxn, &page{id: p.id, overflow: uint32(p.n - 1)})
   188  		}
   189  
   190  		for _, r := range c.releaseRanges {
   191  			f.releaseRange(r.begin, r.end)
   192  		}
   193  
   194  		if exp := c.wantFree; !reflect.DeepEqual(exp, f.getFreePageIDs()) {
   195  			t.Errorf("exp=%v; got=%v for %s", exp, f.getFreePageIDs(), c.title)
   196  		}
   197  	}
   198  }
   199  
   200  func TestFreelistHashmap_allocate(t *testing.T) {
   201  	f := newTestFreelist()
   202  	if f.freelistType != FreelistMapType {
   203  		t.Skip()
   204  	}
   205  
   206  	ids := []pgid{3, 4, 5, 6, 7, 9, 12, 13, 18}
   207  	f.readIDs(ids)
   208  
   209  	f.allocate(1, 3)
   210  	if x := f.free_count(); x != 6 {
   211  		t.Fatalf("exp=6; got=%v", x)
   212  	}
   213  
   214  	f.allocate(1, 2)
   215  	if x := f.free_count(); x != 4 {
   216  		t.Fatalf("exp=4; got=%v", x)
   217  	}
   218  	f.allocate(1, 1)
   219  	if x := f.free_count(); x != 3 {
   220  		t.Fatalf("exp=3; got=%v", x)
   221  	}
   222  
   223  	f.allocate(1, 0)
   224  	if x := f.free_count(); x != 3 {
   225  		t.Fatalf("exp=3; got=%v", x)
   226  	}
   227  }
   228  
   229  // Ensure that a freelist can find contiguous blocks of pages.
   230  func TestFreelistArray_allocate(t *testing.T) {
   231  	f := newTestFreelist()
   232  	if f.freelistType != FreelistArrayType {
   233  		t.Skip()
   234  	}
   235  	ids := []pgid{3, 4, 5, 6, 7, 9, 12, 13, 18}
   236  	f.readIDs(ids)
   237  	if id := int(f.allocate(1, 3)); id != 3 {
   238  		t.Fatalf("exp=3; got=%v", id)
   239  	}
   240  	if id := int(f.allocate(1, 1)); id != 6 {
   241  		t.Fatalf("exp=6; got=%v", id)
   242  	}
   243  	if id := int(f.allocate(1, 3)); id != 0 {
   244  		t.Fatalf("exp=0; got=%v", id)
   245  	}
   246  	if id := int(f.allocate(1, 2)); id != 12 {
   247  		t.Fatalf("exp=12; got=%v", id)
   248  	}
   249  	if id := int(f.allocate(1, 1)); id != 7 {
   250  		t.Fatalf("exp=7; got=%v", id)
   251  	}
   252  	if id := int(f.allocate(1, 0)); id != 0 {
   253  		t.Fatalf("exp=0; got=%v", id)
   254  	}
   255  	if id := int(f.allocate(1, 0)); id != 0 {
   256  		t.Fatalf("exp=0; got=%v", id)
   257  	}
   258  	if exp := []pgid{9, 18}; !reflect.DeepEqual(exp, f.getFreePageIDs()) {
   259  		t.Fatalf("exp=%v; got=%v", exp, f.getFreePageIDs())
   260  	}
   261  
   262  	if id := int(f.allocate(1, 1)); id != 9 {
   263  		t.Fatalf("exp=9; got=%v", id)
   264  	}
   265  	if id := int(f.allocate(1, 1)); id != 18 {
   266  		t.Fatalf("exp=18; got=%v", id)
   267  	}
   268  	if id := int(f.allocate(1, 1)); id != 0 {
   269  		t.Fatalf("exp=0; got=%v", id)
   270  	}
   271  	if exp := []pgid{}; !reflect.DeepEqual(exp, f.getFreePageIDs()) {
   272  		t.Fatalf("exp=%v; got=%v", exp, f.getFreePageIDs())
   273  	}
   274  }
   275  
   276  // Ensure that a freelist can deserialize from a freelist page.
   277  func TestFreelist_read(t *testing.T) {
   278  	// Create a page.
   279  	var buf [4096]byte
   280  	page := (*page)(unsafe.Pointer(&buf[0]))
   281  	page.flags = freelistPageFlag
   282  	page.count = 2
   283  
   284  	// Insert 2 page ids.
   285  	ids := (*[3]pgid)(unsafe.Pointer(uintptr(unsafe.Pointer(page)) + unsafe.Sizeof(*page)))
   286  	ids[0] = 23
   287  	ids[1] = 50
   288  
   289  	// Deserialize page into a freelist.
   290  	f := newTestFreelist()
   291  	f.read(page)
   292  
   293  	// Ensure that there are two page ids in the freelist.
   294  	if exp := []pgid{23, 50}; !reflect.DeepEqual(exp, f.getFreePageIDs()) {
   295  		t.Fatalf("exp=%v; got=%v", exp, f.getFreePageIDs())
   296  	}
   297  }
   298  
   299  // Ensure that a freelist can serialize into a freelist page.
   300  func TestFreelist_write(t *testing.T) {
   301  	// Create a freelist and write it to a page.
   302  	var buf [4096]byte
   303  	f := newTestFreelist()
   304  
   305  	f.readIDs([]pgid{12, 39})
   306  	f.pending[100] = &txPending{ids: []pgid{28, 11}}
   307  	f.pending[101] = &txPending{ids: []pgid{3}}
   308  	p := (*page)(unsafe.Pointer(&buf[0]))
   309  	if err := f.write(p); err != nil {
   310  		t.Fatal(err)
   311  	}
   312  
   313  	// Read the page back out.
   314  	f2 := newTestFreelist()
   315  	f2.read(p)
   316  
   317  	// Ensure that the freelist is correct.
   318  	// All pages should be present and in reverse order.
   319  	if exp := []pgid{3, 11, 12, 28, 39}; !reflect.DeepEqual(exp, f2.getFreePageIDs()) {
   320  		t.Fatalf("exp=%v; got=%v", exp, f2.getFreePageIDs())
   321  	}
   322  }
   323  
   324  func Benchmark_FreelistRelease10K(b *testing.B)    { benchmark_FreelistRelease(b, 10000) }
   325  func Benchmark_FreelistRelease100K(b *testing.B)   { benchmark_FreelistRelease(b, 100000) }
   326  func Benchmark_FreelistRelease1000K(b *testing.B)  { benchmark_FreelistRelease(b, 1000000) }
   327  func Benchmark_FreelistRelease10000K(b *testing.B) { benchmark_FreelistRelease(b, 10000000) }
   328  
   329  func benchmark_FreelistRelease(b *testing.B, size int) {
   330  	ids := randomPgids(size)
   331  	pending := randomPgids(len(ids) / 400)
   332  	b.ResetTimer()
   333  	for i := 0; i < b.N; i++ {
   334  		txp := &txPending{ids: pending}
   335  		f := newTestFreelist()
   336  		f.pending = map[txid]*txPending{1: txp}
   337  		f.readIDs(ids)
   338  		f.release(1)
   339  	}
   340  }
   341  
   342  func randomPgids(n int) []pgid {
   343  	rand.Seed(42)
   344  	pgids := make(pgids, n)
   345  	for i := range pgids {
   346  		pgids[i] = pgid(rand.Int63())
   347  	}
   348  	sort.Sort(pgids)
   349  	return pgids
   350  }
   351  
   352  func Test_freelist_ReadIDs_and_getFreePageIDs(t *testing.T) {
   353  	f := newTestFreelist()
   354  	exp := []pgid{3, 4, 5, 6, 7, 9, 12, 13, 18}
   355  
   356  	f.readIDs(exp)
   357  
   358  	if got := f.getFreePageIDs(); !reflect.DeepEqual(exp, got) {
   359  		t.Fatalf("exp=%v; got=%v", exp, got)
   360  	}
   361  
   362  	f2 := newTestFreelist()
   363  	var exp2 []pgid
   364  	f2.readIDs(exp2)
   365  
   366  	if got2 := f2.getFreePageIDs(); !reflect.DeepEqual(got2, exp2) {
   367  		t.Fatalf("exp2=%#v; got2=%#v", exp2, got2)
   368  	}
   369  
   370  }
   371  
   372  func Test_freelist_mergeWithExist(t *testing.T) {
   373  	bm1 := pidSet{1: struct{}{}}
   374  
   375  	bm2 := pidSet{5: struct{}{}}
   376  	tests := []struct {
   377  		name            string
   378  		ids             []pgid
   379  		pgid            pgid
   380  		want            []pgid
   381  		wantForwardmap  map[pgid]uint64
   382  		wantBackwardmap map[pgid]uint64
   383  		wantfreemap     map[uint64]pidSet
   384  	}{
   385  		{
   386  			name:            "test1",
   387  			ids:             []pgid{1, 2, 4, 5, 6},
   388  			pgid:            3,
   389  			want:            []pgid{1, 2, 3, 4, 5, 6},
   390  			wantForwardmap:  map[pgid]uint64{1: 6},
   391  			wantBackwardmap: map[pgid]uint64{6: 6},
   392  			wantfreemap:     map[uint64]pidSet{6: bm1},
   393  		},
   394  		{
   395  			name:            "test2",
   396  			ids:             []pgid{1, 2, 5, 6},
   397  			pgid:            3,
   398  			want:            []pgid{1, 2, 3, 5, 6},
   399  			wantForwardmap:  map[pgid]uint64{1: 3, 5: 2},
   400  			wantBackwardmap: map[pgid]uint64{6: 2, 3: 3},
   401  			wantfreemap:     map[uint64]pidSet{3: bm1, 2: bm2},
   402  		},
   403  		{
   404  			name:            "test3",
   405  			ids:             []pgid{1, 2},
   406  			pgid:            3,
   407  			want:            []pgid{1, 2, 3},
   408  			wantForwardmap:  map[pgid]uint64{1: 3},
   409  			wantBackwardmap: map[pgid]uint64{3: 3},
   410  			wantfreemap:     map[uint64]pidSet{3: bm1},
   411  		},
   412  		{
   413  			name:            "test4",
   414  			ids:             []pgid{2, 3},
   415  			pgid:            1,
   416  			want:            []pgid{1, 2, 3},
   417  			wantForwardmap:  map[pgid]uint64{1: 3},
   418  			wantBackwardmap: map[pgid]uint64{3: 3},
   419  			wantfreemap:     map[uint64]pidSet{3: bm1},
   420  		},
   421  	}
   422  	for _, tt := range tests {
   423  		f := newTestFreelist()
   424  		if f.freelistType == FreelistArrayType {
   425  			t.Skip()
   426  		}
   427  		f.readIDs(tt.ids)
   428  
   429  		f.mergeWithExistingSpan(tt.pgid)
   430  
   431  		if got := f.getFreePageIDs(); !reflect.DeepEqual(tt.want, got) {
   432  			t.Fatalf("name %s; exp=%v; got=%v", tt.name, tt.want, got)
   433  		}
   434  		if got := f.forwardMap; !reflect.DeepEqual(tt.wantForwardmap, got) {
   435  			t.Fatalf("name %s; exp=%v; got=%v", tt.name, tt.wantForwardmap, got)
   436  		}
   437  		if got := f.backwardMap; !reflect.DeepEqual(tt.wantBackwardmap, got) {
   438  			t.Fatalf("name %s; exp=%v; got=%v", tt.name, tt.wantBackwardmap, got)
   439  		}
   440  		if got := f.freemaps; !reflect.DeepEqual(tt.wantfreemap, got) {
   441  			t.Fatalf("name %s; exp=%v; got=%v", tt.name, tt.wantfreemap, got)
   442  		}
   443  	}
   444  }
   445  
   446  // newTestFreelist get the freelist type from env and initial the freelist
   447  func newTestFreelist() *freelist {
   448  	freelistType := FreelistArrayType
   449  	if env := os.Getenv(TestFreelistType); env == string(FreelistMapType) {
   450  		freelistType = FreelistMapType
   451  	}
   452  
   453  	return newFreelist(freelistType)
   454  }