Skip to content

Commit e0dbe9d

Browse files
committed
save
1 parent eacc027 commit e0dbe9d

File tree

1 file changed

+128
-0
lines changed

1 file changed

+128
-0
lines changed

mdbx/txn_test.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)