@@ -1482,3 +1482,131 @@ func TestTxn_CommitLatency(t *testing.T) {
14821482 t .Errorf ("MaxRetainedPages is negative: %d" , latency .GCDetails .MaxRetainedPages )
14831483 }
14841484}
1485+
1486+ func TestTxn_CommitLatency_MaxRetainedPages (t * testing.T ) {
1487+ env , _ := setup (t )
1488+
1489+ var db DBI
1490+ if err := env .Update (func (txn * Txn ) (err error ) {
1491+ db , err = txn .OpenDBISimple ("testdb" , Create )
1492+ return err
1493+ }); err != nil {
1494+ t .Fatalf ("create db: %v" , err )
1495+ }
1496+
1497+ // Phase 1: open an early reader (R1) that pins a near-zero pages_retired
1498+ // snapshot. R1 becomes the "oldest reader" (lowest txnid). MDBX caches
1499+ // its txnid as prev_oldest on the first GC call that detects it.
1500+ r1Ready := make (chan struct {})
1501+ r1Close := make (chan struct {})
1502+ r1Done := make (chan error , 1 )
1503+ go func () {
1504+ runtime .LockOSThread ()
1505+ defer runtime .UnlockOSThread ()
1506+ txn , err := env .BeginTxn (nil , Readonly )
1507+ if err != nil {
1508+ r1Done <- err
1509+ close (r1Ready )
1510+ return
1511+ }
1512+ defer txn .Abort ()
1513+ close (r1Ready )
1514+ <- r1Close
1515+ r1Done <- nil
1516+ }()
1517+ <- r1Ready
1518+
1519+ // Phase 2: bulk writes + deletes while R1 is open. These commit pages to
1520+ // the GC freelist, advancing pages_retired to R_high. GC detects R1 on
1521+ // the first commit here (condition fires once, retained=0 since no
1522+ // retirements yet) and caches R1's txnid as prev_oldest. All later
1523+ // commits find result==prev_oldest → condition FALSE.
1524+ for i := 0 ; i < 100 ; i ++ {
1525+ k := make ([]byte , 8 )
1526+ binary .BigEndian .PutUint64 (k , uint64 (i ))
1527+ if err := env .Update (func (txn * Txn ) error {
1528+ return txn .Put (db , k , make ([]byte , 4096 ), 0 )
1529+ }); err != nil {
1530+ t .Fatalf ("put %d: %v" , i , err )
1531+ }
1532+ }
1533+ for i := 0 ; i < 100 ; i ++ {
1534+ k := make ([]byte , 8 )
1535+ binary .BigEndian .PutUint64 (k , uint64 (i ))
1536+ if err := env .Update (func (txn * Txn ) error {
1537+ if err := txn .Del (db , k , nil ); err != nil && err != ErrNotFound {
1538+ return err
1539+ }
1540+ return nil
1541+ }); err != nil {
1542+ t .Fatalf ("del %d: %v" , i , err )
1543+ }
1544+ }
1545+
1546+ // Phase 3: open R2 at the current state so its snapshot = R_high.
1547+ // R1 is still the oldest reader; R2 is newer.
1548+ r2Txn , err := env .BeginTxn (nil , Readonly )
1549+ if err != nil {
1550+ t .Fatalf ("begin R2: %v" , err )
1551+ }
1552+ defer r2Txn .Abort ()
1553+
1554+ // Phase 4: more writes+deletes with both readers open.
1555+ // R1 is still the oldest reader, so GC condition stays FALSE.
1556+ // pages_retired advances to R_high + delta.
1557+ for i := 100 ; i < 150 ; i ++ {
1558+ k := make ([]byte , 8 )
1559+ binary .BigEndian .PutUint64 (k , uint64 (i ))
1560+ if err := env .Update (func (txn * Txn ) error {
1561+ return txn .Put (db , k , make ([]byte , 4096 ), 0 )
1562+ }); err != nil {
1563+ t .Fatalf ("put2 %d: %v" , i , err )
1564+ }
1565+ }
1566+ for i := 100 ; i < 150 ; i ++ {
1567+ k := make ([]byte , 8 )
1568+ binary .BigEndian .PutUint64 (k , uint64 (i ))
1569+ if err := env .Update (func (txn * Txn ) error {
1570+ if err := txn .Del (db , k , nil ); err != nil && err != ErrNotFound {
1571+ return err
1572+ }
1573+ return nil
1574+ }); err != nil {
1575+ t .Fatalf ("del2 %d: %v" , i , err )
1576+ }
1577+ }
1578+
1579+ // Phase 5: close R1. rdt_refresh_flag is set to true.
1580+ // R2 (at T_high, snapshot=R_high) is now the oldest reader.
1581+ close (r1Close )
1582+ if err := <- r1Done ; err != nil {
1583+ t .Fatalf ("R1 goroutine: %v" , err )
1584+ }
1585+
1586+ // Phase 6: final commit. GC now sees the oldest reader changed from
1587+ // R1's txnid (prev_oldest) to R2's txnid (T_high). The condition fires:
1588+ // result = T_high != prev_oldest = T_R1
1589+ // oldest_retired_pages = R2.snapshot = R_high
1590+ // recent_retired = R_high + delta (phase-4 retirements)
1591+ // max_retained_pages = delta > 0
1592+ runtime .LockOSThread ()
1593+ defer runtime .UnlockOSThread ()
1594+
1595+ finalTxn , err := env .BeginTxn (nil , 0 )
1596+ if err != nil {
1597+ t .Fatalf ("begin final txn: %v" , err )
1598+ }
1599+ if err := finalTxn .Put (db , []byte ("z" ), []byte ("z" ), 0 ); err != nil {
1600+ finalTxn .Abort ()
1601+ t .Fatalf ("put final: %v" , err )
1602+ }
1603+ latency , err := finalTxn .Commit ()
1604+ if err != nil {
1605+ t .Fatalf ("commit final: %v" , err )
1606+ }
1607+
1608+ t .Logf ("MaxRetainedPages: %d" , latency .GCDetails .MaxRetainedPages )
1609+ if latency .GCDetails .MaxRetainedPages == 0 {
1610+ t .Errorf ("expected MaxRetainedPages > 0, got 0" )
1611+ }
1612+ }
0 commit comments