1
1
package freelist
2
2
3
3
import (
4
+ "fmt"
5
+ "math"
4
6
"math/rand"
5
7
"os"
6
8
"reflect"
9
+ "slices"
7
10
"sort"
8
11
"testing"
12
+ "testing/quick"
9
13
"unsafe"
10
14
11
15
"github.com/stretchr/testify/require"
@@ -34,6 +38,55 @@ func TestFreelist_free_overflow(t *testing.T) {
34
38
}
35
39
}
36
40
41
+ // Ensure that double freeing a page is causing a panic
42
+ func TestFreelist_free_double_free_panics (t * testing.T ) {
43
+ f := newTestFreelist ()
44
+ f .Free (100 , common .NewPage (12 , 0 , 0 , 3 ))
45
+ require .Panics (t , func () {
46
+ f .Free (100 , common .NewPage (12 , 0 , 0 , 3 ))
47
+ })
48
+ }
49
+
50
+ // Ensure that attempting to free the meta page panics
51
+ func TestFreelist_free_meta_panics (t * testing.T ) {
52
+ f := newTestFreelist ()
53
+ require .Panics (t , func () {
54
+ f .Free (100 , common .NewPage (0 , 0 , 0 , 0 ))
55
+ })
56
+ require .Panics (t , func () {
57
+ f .Free (100 , common .NewPage (1 , 0 , 0 , 0 ))
58
+ })
59
+ }
60
+
61
+ func TestFreelist_free_freelist (t * testing.T ) {
62
+ f := newTestFreelist ()
63
+ f .Free (100 , common .NewPage (12 , common .FreelistPageFlag , 0 , 0 ))
64
+ pp := f .pendingPageIds ()[100 ]
65
+ require .Equal (t , []common.Pgid {12 }, pp .ids )
66
+ require .Equal (t , []common.Txid {0 }, pp .alloctx )
67
+ }
68
+
69
+ func TestFreelist_free_freelist_alloctx (t * testing.T ) {
70
+ f := newTestFreelist ()
71
+ f .Free (100 , common .NewPage (12 , common .FreelistPageFlag , 0 , 0 ))
72
+ f .Rollback (100 )
73
+ require .Empty (t , f .freePageIds ())
74
+ require .Empty (t , f .pendingPageIds ())
75
+ require .False (t , f .Freed (12 ))
76
+
77
+ f .Free (101 , common .NewPage (12 , common .FreelistPageFlag , 0 , 0 ))
78
+ require .True (t , f .Freed (12 ))
79
+ if exp := []common.Pgid {12 }; ! reflect .DeepEqual (exp , f .pendingPageIds ()[101 ].ids ) {
80
+ t .Fatalf ("exp=%v; got=%v" , exp , f .pendingPageIds ()[101 ].ids )
81
+ }
82
+ f .ReleasePendingPages ()
83
+ require .True (t , f .Freed (12 ))
84
+ require .Empty (t , f .pendingPageIds ())
85
+ if exp := common .Pgids ([]common.Pgid {12 }); ! reflect .DeepEqual (exp , f .freePageIds ()) {
86
+ t .Fatalf ("exp=%v; got=%v" , exp , f .freePageIds ())
87
+ }
88
+ }
89
+
37
90
// Ensure that a transaction's free pages can be released.
38
91
func TestFreelist_release (t * testing.T ) {
39
92
f := newTestFreelist ()
@@ -220,6 +273,30 @@ func TestFreeList_reload(t *testing.T) {
220
273
require .Equal (t , []common.Pgid {10 , 11 , 12 }, f2 .pendingPageIds ()[5 ].ids )
221
274
}
222
275
276
+ // Ensure that the txIDx swap, less and len are properly implemented
277
+ func TestTxidSorting (t * testing.T ) {
278
+ require .NoError (t , quick .Check (func (a []uint64 ) bool {
279
+ var txids []common.Txid
280
+ for _ , txid := range a {
281
+ txids = append (txids , common .Txid (txid ))
282
+ }
283
+
284
+ sort .Sort (txIDx (txids ))
285
+
286
+ var r []uint64
287
+ for _ , txid := range txids {
288
+ r = append (r , uint64 (txid ))
289
+ }
290
+
291
+ if ! slices .IsSorted (r ) {
292
+ t .Errorf ("txids were not sorted correctly=%v" , txids )
293
+ return false
294
+ }
295
+
296
+ return true
297
+ }, nil ))
298
+ }
299
+
223
300
// Ensure that a freelist can deserialize from a freelist page.
224
301
func TestFreelist_read (t * testing.T ) {
225
302
// Create a page.
@@ -243,6 +320,18 @@ func TestFreelist_read(t *testing.T) {
243
320
}
244
321
}
245
322
323
+ // Ensure that we never read a non-freelist page
324
+ func TestFreelist_read_panics (t * testing.T ) {
325
+ buf := make ([]byte , 4096 )
326
+ page := common .LoadPage (buf )
327
+ page .SetFlags (common .BranchPageFlag )
328
+ page .SetCount (2 )
329
+ f := newTestFreelist ()
330
+ require .Panics (t , func () {
331
+ f .Read (page )
332
+ })
333
+ }
334
+
246
335
// Ensure that a freelist can serialize into a freelist page.
247
336
func TestFreelist_write (t * testing.T ) {
248
337
// Create a freelist and write it to a page.
@@ -266,6 +355,216 @@ func TestFreelist_write(t *testing.T) {
266
355
}
267
356
}
268
357
358
+ func TestFreelist_E2E_HappyPath (t * testing.T ) {
359
+ f := newTestFreelist ()
360
+ f .Init ([]common.Pgid {})
361
+ requirePages (t , f , common.Pgids {}, common.Pgids {})
362
+
363
+ allocated := f .Allocate (common .Txid (1 ), 5 )
364
+ require .Equal (t , common .Pgid (0 ), allocated )
365
+ // tx.go may now allocate more space, and eventually we need to delete a page again
366
+ f .Free (common .Txid (2 ), common .NewPage (5 , common .LeafPageFlag , 0 , 0 ))
367
+ f .Free (common .Txid (2 ), common .NewPage (3 , common .LeafPageFlag , 0 , 0 ))
368
+ f .Free (common .Txid (2 ), common .NewPage (8 , common .LeafPageFlag , 0 , 0 ))
369
+ // the above will only mark the pages as pending, so free pages should not return anything
370
+ requirePages (t , f , common.Pgids {}, common.Pgids {3 , 5 , 8 })
371
+
372
+ // someone wants to do a read on top of the next tx id
373
+ f .AddReadonlyTXID (common .Txid (3 ))
374
+ // this should free the above pages for tx 2 entirely
375
+ f .ReleasePendingPages ()
376
+ requirePages (t , f , common.Pgids {3 , 5 , 8 }, common.Pgids {})
377
+
378
+ // no span of two pages available should yield a zero-page result
379
+ require .Equal (t , common .Pgid (0 ), f .Allocate (common .Txid (4 ), 2 ))
380
+ // we should be able to allocate those pages independently however,
381
+ // map and array differ in the order they return the pages
382
+ expectedPgids := map [common.Pgid ]struct {}{3 : {}, 5 : {}, 8 : {}}
383
+ for i := 0 ; i < 3 ; i ++ {
384
+ allocated = f .Allocate (common .Txid (4 ), 1 )
385
+ require .Contains (t , expectedPgids , allocated , "expected to find pgid %d" , allocated )
386
+ require .False (t , f .Freed (allocated ))
387
+ delete (expectedPgids , allocated )
388
+ }
389
+ require .Emptyf (t , expectedPgids , "unexpectedly more than one page was still found" )
390
+ // no more free pages to allocate
391
+ require .Equal (t , common .Pgid (0 ), f .Allocate (common .Txid (4 ), 1 ))
392
+ }
393
+
394
+ func TestFreelist_E2E_MultiSpanOverflows (t * testing.T ) {
395
+ f := newTestFreelist ()
396
+ f .Init ([]common.Pgid {})
397
+ f .Free (common .Txid (10 ), common .NewPage (20 , common .LeafPageFlag , 0 , 1 ))
398
+ f .Free (common .Txid (10 ), common .NewPage (25 , common .LeafPageFlag , 0 , 2 ))
399
+ f .Free (common .Txid (10 ), common .NewPage (35 , common .LeafPageFlag , 0 , 3 ))
400
+ f .Free (common .Txid (10 ), common .NewPage (39 , common .LeafPageFlag , 0 , 2 ))
401
+ f .Free (common .Txid (10 ), common .NewPage (45 , common .LeafPageFlag , 0 , 4 ))
402
+ requirePages (t , f , common.Pgids {}, common.Pgids {20 , 21 , 25 , 26 , 27 , 35 , 36 , 37 , 38 , 39 , 40 , 41 , 45 , 46 , 47 , 48 , 49 })
403
+ f .ReleasePendingPages ()
404
+ requirePages (t , f , common.Pgids {20 , 21 , 25 , 26 , 27 , 35 , 36 , 37 , 38 , 39 , 40 , 41 , 45 , 46 , 47 , 48 , 49 }, common.Pgids {})
405
+
406
+ // that sequence, regardless of implementation, should always yield the same blocks of pages
407
+ allocSequence := []int {7 , 5 , 3 , 2 }
408
+ expectedSpanStarts := []common.Pgid {35 , 45 , 25 , 20 }
409
+ for i , pageNums := range allocSequence {
410
+ allocated := f .Allocate (common .Txid (11 ), pageNums )
411
+ require .Equal (t , expectedSpanStarts [i ], allocated )
412
+ // ensure all pages in that span are not considered free anymore
413
+ for i := 0 ; i < pageNums ; i ++ {
414
+ require .False (t , f .Freed (allocated + common .Pgid (i )))
415
+ }
416
+ }
417
+ }
418
+
419
+ func TestFreelist_E2E_Rollbacks (t * testing.T ) {
420
+ freelist := newTestFreelist ()
421
+ freelist .Init ([]common.Pgid {})
422
+ freelist .Free (common .Txid (2 ), common .NewPage (5 , common .LeafPageFlag , 0 , 1 ))
423
+ freelist .Free (common .Txid (2 ), common .NewPage (8 , common .LeafPageFlag , 0 , 0 ))
424
+ requirePages (t , freelist , common.Pgids {}, common.Pgids {5 , 6 , 8 })
425
+ freelist .Rollback (common .Txid (2 ))
426
+ requirePages (t , freelist , common.Pgids {}, common.Pgids {})
427
+
428
+ // unknown transaction should not trigger anything
429
+ freelist .Free (common .Txid (4 ), common .NewPage (13 , common .LeafPageFlag , 0 , 3 ))
430
+ requirePages (t , freelist , common.Pgids {}, common.Pgids {13 , 14 , 15 , 16 })
431
+ freelist .ReleasePendingPages ()
432
+ requirePages (t , freelist , common.Pgids {13 , 14 , 15 , 16 }, common.Pgids {})
433
+ freelist .Rollback (common .Txid (1337 ))
434
+ requirePages (t , freelist , common.Pgids {13 , 14 , 15 , 16 }, common.Pgids {})
435
+ }
436
+
437
+ func TestFreelist_E2E_RollbackPanics (t * testing.T ) {
438
+ freelist := newTestFreelist ()
439
+ freelist .Init ([]common.Pgid {5 })
440
+ requirePages (t , freelist , common.Pgids {5 }, common.Pgids {})
441
+
442
+ _ = freelist .Allocate (common .Txid (5 ), 1 )
443
+ require .Panics (t , func () {
444
+ // depending on the verification level, either should panic
445
+ freelist .Free (common .Txid (5 ), common .NewPage (5 , common .LeafPageFlag , 0 , 0 ))
446
+ freelist .Rollback (5 )
447
+ })
448
+ }
449
+
450
+ // tests the reloading from another physical page
451
+ func TestFreelist_E2E_Reload (t * testing.T ) {
452
+ freelist := newTestFreelist ()
453
+ freelist .Init ([]common.Pgid {})
454
+ freelist .Free (common .Txid (2 ), common .NewPage (5 , common .LeafPageFlag , 0 , 1 ))
455
+ freelist .Free (common .Txid (2 ), common .NewPage (8 , common .LeafPageFlag , 0 , 0 ))
456
+ freelist .ReleasePendingPages ()
457
+ requirePages (t , freelist , common.Pgids {5 , 6 , 8 }, common.Pgids {})
458
+ buf := make ([]byte , 4096 )
459
+ p := common .LoadPage (buf )
460
+ freelist .Write (p )
461
+
462
+ freelist .Free (common .Txid (3 ), common .NewPage (3 , common .LeafPageFlag , 0 , 1 ))
463
+ freelist .Free (common .Txid (3 ), common .NewPage (10 , common .LeafPageFlag , 0 , 2 ))
464
+ requirePages (t , freelist , common.Pgids {5 , 6 , 8 }, common.Pgids {3 , 4 , 10 , 11 , 12 })
465
+
466
+ otherBuf := make ([]byte , 4096 )
467
+ px := common .LoadPage (otherBuf )
468
+ freelist .Write (px )
469
+
470
+ loadFreeList := newTestFreelist ()
471
+ loadFreeList .Init ([]common.Pgid {})
472
+ loadFreeList .Read (px )
473
+ requirePages (t , loadFreeList , common.Pgids {3 , 4 , 5 , 6 , 8 , 10 , 11 , 12 }, common.Pgids {})
474
+ // restore the original freelist again
475
+ loadFreeList .Reload (p )
476
+ requirePages (t , loadFreeList , common.Pgids {5 , 6 , 8 }, common.Pgids {})
477
+
478
+ // reload another page with different free pages to test we are deduplicating the free pages with the pending ones correctly
479
+ freelist = newTestFreelist ()
480
+ freelist .Init ([]common.Pgid {})
481
+ freelist .Free (common .Txid (5 ), common .NewPage (5 , common .LeafPageFlag , 0 , 4 ))
482
+ freelist .Reload (p )
483
+ requirePages (t , freelist , common.Pgids {}, common.Pgids {5 , 6 , 7 , 8 , 9 })
484
+ }
485
+
486
+ // tests the loading and reloading from physical pages
487
+ func TestFreelist_E2E_SerDe_HappyPath (t * testing.T ) {
488
+ freelist := newTestFreelist ()
489
+ freelist .Init ([]common.Pgid {})
490
+ freelist .Free (common .Txid (2 ), common .NewPage (5 , common .LeafPageFlag , 0 , 1 ))
491
+ freelist .Free (common .Txid (2 ), common .NewPage (8 , common .LeafPageFlag , 0 , 0 ))
492
+ freelist .ReleasePendingPages ()
493
+ requirePages (t , freelist , common.Pgids {5 , 6 , 8 }, common.Pgids {})
494
+
495
+ freelist .Free (common .Txid (3 ), common .NewPage (3 , common .LeafPageFlag , 0 , 1 ))
496
+ freelist .Free (common .Txid (3 ), common .NewPage (10 , common .LeafPageFlag , 0 , 2 ))
497
+ requirePages (t , freelist , common.Pgids {5 , 6 , 8 }, common.Pgids {3 , 4 , 10 , 11 , 12 })
498
+
499
+ buf := make ([]byte , 4096 )
500
+ p := common .LoadPage (buf )
501
+ require .Equal (t , 80 , freelist .EstimatedWritePageSize ())
502
+ freelist .Write (p )
503
+
504
+ loadFreeList := newTestFreelist ()
505
+ loadFreeList .Init ([]common.Pgid {})
506
+ loadFreeList .Read (p )
507
+ requirePages (t , loadFreeList , common.Pgids {3 , 4 , 5 , 6 , 8 , 10 , 11 , 12 }, common.Pgids {})
508
+ }
509
+
510
+ // tests the loading of a freelist against other implementations with various sizes
511
+ func TestFreelist_E2E_SerDe_AcrossImplementations (t * testing.T ) {
512
+ testSizes := []int {0 , 1 , 10 , 100 , 1000 , math .MaxUint16 , math .MaxUint16 + 1 , math .MaxUint16 * 2 }
513
+ for _ , size := range testSizes {
514
+ t .Run (fmt .Sprintf ("n=%d" , size ), func (t * testing.T ) {
515
+ freelist := newTestFreelist ()
516
+ expectedFreePgids := common.Pgids {}
517
+ for i := 0 ; i < size ; i ++ {
518
+ pgid := common .Pgid (i + 2 )
519
+ freelist .Free (common .Txid (1 ), common .NewPage (pgid , common .LeafPageFlag , 0 , 0 ))
520
+ expectedFreePgids = append (expectedFreePgids , pgid )
521
+ }
522
+ freelist .ReleasePendingPages ()
523
+ requirePages (t , freelist , expectedFreePgids , common.Pgids {})
524
+ buf := make ([]byte , freelist .EstimatedWritePageSize ())
525
+ p := common .LoadPage (buf )
526
+ freelist .Write (p )
527
+
528
+ for n , loadFreeList := range map [string ]Interface {
529
+ "hashmap" : NewHashMapFreelist (),
530
+ "array" : NewArrayFreelist (),
531
+ } {
532
+ t .Run (n , func (t * testing.T ) {
533
+ loadFreeList .Read (p )
534
+ requirePages (t , loadFreeList , expectedFreePgids , common.Pgids {})
535
+ })
536
+ }
537
+ })
538
+ }
539
+ }
540
+
541
+ func requirePages (t * testing.T , f Interface , freePageIds common.Pgids , pendingPageIds common.Pgids ) {
542
+ require .Equal (t , f .FreeCount ()+ f .PendingCount (), f .Count ())
543
+ require .Equalf (t , freePageIds , f .freePageIds (), "unexpected free pages" )
544
+ require .Equal (t , len (freePageIds ), f .FreeCount ())
545
+
546
+ pp := allPendingPages (f .pendingPageIds ())
547
+ require .Equalf (t , pendingPageIds , pp , "unexpected pending pages" )
548
+ require .Equal (t , len (pp ), f .PendingCount ())
549
+
550
+ for _ , pgid := range f .freePageIds () {
551
+ require .Truef (t , f .Freed (pgid ), "expected free page to return true on Freed" )
552
+ }
553
+
554
+ for _ , pgid := range pp {
555
+ require .Truef (t , f .Freed (pgid ), "expected pending page to return true on Freed" )
556
+ }
557
+ }
558
+
559
+ func allPendingPages (p map [common.Txid ]* txPending ) common.Pgids {
560
+ pgids := common.Pgids {}
561
+ for _ , pending := range p {
562
+ pgids = append (pgids , pending .ids ... )
563
+ }
564
+ sort .Sort (pgids )
565
+ return pgids
566
+ }
567
+
269
568
func Benchmark_FreelistRelease10K (b * testing.B ) { benchmark_FreelistRelease (b , 10000 ) }
270
569
func Benchmark_FreelistRelease100K (b * testing.B ) { benchmark_FreelistRelease (b , 100000 ) }
271
570
func Benchmark_FreelistRelease1000K (b * testing.B ) { benchmark_FreelistRelease (b , 1000000 ) }
0 commit comments