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 }