@@ -1230,11 +1230,6 @@ SimpleLruTruncate(SlruCtl ctl, int cutoffPage)
1230
1230
/* update the stats counter of truncates */
1231
1231
pgstat_count_slru_truncate (shared -> slru_stats_idx );
1232
1232
1233
- /*
1234
- * The cutoff point is the start of the segment containing cutoffPage.
1235
- */
1236
- cutoffPage -= cutoffPage % SLRU_PAGES_PER_SEGMENT ;
1237
-
1238
1233
/*
1239
1234
* Scan shared memory and remove any pages preceding the cutoff page, to
1240
1235
* ensure we won't rewrite them later. (Since this is normally called in
@@ -1247,9 +1242,7 @@ restart:;
1247
1242
1248
1243
/*
1249
1244
* While we are holding the lock, make an important safety check: the
1250
- * planned cutoff point must be <= the current endpoint page. Otherwise we
1251
- * have already wrapped around, and proceeding with the truncation would
1252
- * risk removing the current segment.
1245
+ * current endpoint page must not be eligible for removal.
1253
1246
*/
1254
1247
if (ctl -> PagePrecedes (shared -> latest_page_number , cutoffPage ))
1255
1248
{
@@ -1281,8 +1274,11 @@ restart:;
1281
1274
* Hmm, we have (or may have) I/O operations acting on the page, so
1282
1275
* we've got to wait for them to finish and then start again. This is
1283
1276
* the same logic as in SlruSelectLRUPage. (XXX if page is dirty,
1284
- * wouldn't it be OK to just discard it without writing it? For now,
1285
- * keep the logic the same as it was.)
1277
+ * wouldn't it be OK to just discard it without writing it?
1278
+ * SlruMayDeleteSegment() uses a stricter qualification, so we might
1279
+ * not delete this page in the end; even if we don't delete it, we
1280
+ * won't have cause to read its data again. For now, keep the logic
1281
+ * the same as it was.)
1286
1282
*/
1287
1283
if (shared -> page_status [slotno ] == SLRU_PAGE_VALID )
1288
1284
SlruInternalWritePage (ctl , slotno , NULL );
@@ -1377,19 +1373,134 @@ SlruDeleteSegment(SlruCtl ctl, int segno)
1377
1373
LWLockRelease (shared -> ControlLock );
1378
1374
}
1379
1375
1376
+ /*
1377
+ * Determine whether a segment is okay to delete.
1378
+ *
1379
+ * segpage is the first page of the segment, and cutoffPage is the oldest (in
1380
+ * PagePrecedes order) page in the SLRU containing still-useful data. Since
1381
+ * every core PagePrecedes callback implements "wrap around", check the
1382
+ * segment's first and last pages:
1383
+ *
1384
+ * first<cutoff && last<cutoff: yes
1385
+ * first<cutoff && last>=cutoff: no; cutoff falls inside this segment
1386
+ * first>=cutoff && last<cutoff: no; wrap point falls inside this segment
1387
+ * first>=cutoff && last>=cutoff: no; every page of this segment is too young
1388
+ */
1389
+ static bool
1390
+ SlruMayDeleteSegment (SlruCtl ctl , int segpage , int cutoffPage )
1391
+ {
1392
+ int seg_last_page = segpage + SLRU_PAGES_PER_SEGMENT - 1 ;
1393
+
1394
+ Assert (segpage % SLRU_PAGES_PER_SEGMENT == 0 );
1395
+
1396
+ return (ctl -> PagePrecedes (segpage , cutoffPage ) &&
1397
+ ctl -> PagePrecedes (seg_last_page , cutoffPage ));
1398
+ }
1399
+
1400
+ #ifdef USE_ASSERT_CHECKING
1401
+ static void
1402
+ SlruPagePrecedesTestOffset (SlruCtl ctl , int per_page , uint32 offset )
1403
+ {
1404
+ TransactionId lhs ,
1405
+ rhs ;
1406
+ int newestPage ,
1407
+ oldestPage ;
1408
+ TransactionId newestXact ,
1409
+ oldestXact ;
1410
+
1411
+ /*
1412
+ * Compare an XID pair having undefined order (see RFC 1982), a pair at
1413
+ * "opposite ends" of the XID space. TransactionIdPrecedes() treats each
1414
+ * as preceding the other. If RHS is oldestXact, LHS is the first XID we
1415
+ * must not assign.
1416
+ */
1417
+ lhs = per_page + offset ; /* skip first page to avoid non-normal XIDs */
1418
+ rhs = lhs + (1U << 31 );
1419
+ Assert (TransactionIdPrecedes (lhs , rhs ));
1420
+ Assert (TransactionIdPrecedes (rhs , lhs ));
1421
+ Assert (!TransactionIdPrecedes (lhs - 1 , rhs ));
1422
+ Assert (TransactionIdPrecedes (rhs , lhs - 1 ));
1423
+ Assert (TransactionIdPrecedes (lhs + 1 , rhs ));
1424
+ Assert (!TransactionIdPrecedes (rhs , lhs + 1 ));
1425
+ Assert (!TransactionIdFollowsOrEquals (lhs , rhs ));
1426
+ Assert (!TransactionIdFollowsOrEquals (rhs , lhs ));
1427
+ Assert (!ctl -> PagePrecedes (lhs / per_page , lhs / per_page ));
1428
+ Assert (!ctl -> PagePrecedes (lhs / per_page , rhs / per_page ));
1429
+ Assert (!ctl -> PagePrecedes (rhs / per_page , lhs / per_page ));
1430
+ Assert (!ctl -> PagePrecedes ((lhs - per_page ) / per_page , rhs / per_page ));
1431
+ Assert (ctl -> PagePrecedes (rhs / per_page , (lhs - 3 * per_page ) / per_page ));
1432
+ Assert (ctl -> PagePrecedes (rhs / per_page , (lhs - 2 * per_page ) / per_page ));
1433
+ Assert (ctl -> PagePrecedes (rhs / per_page , (lhs - 1 * per_page ) / per_page )
1434
+ || (1U << 31 ) % per_page != 0 ); /* See CommitTsPagePrecedes() */
1435
+ Assert (ctl -> PagePrecedes ((lhs + 1 * per_page ) / per_page , rhs / per_page )
1436
+ || (1U << 31 ) % per_page != 0 );
1437
+ Assert (ctl -> PagePrecedes ((lhs + 2 * per_page ) / per_page , rhs / per_page ));
1438
+ Assert (ctl -> PagePrecedes ((lhs + 3 * per_page ) / per_page , rhs / per_page ));
1439
+ Assert (!ctl -> PagePrecedes (rhs / per_page , (lhs + per_page ) / per_page ));
1440
+
1441
+ /*
1442
+ * GetNewTransactionId() has assigned the last XID it can safely use, and
1443
+ * that XID is in the *LAST* page of the second segment. We must not
1444
+ * delete that segment.
1445
+ */
1446
+ newestPage = 2 * SLRU_PAGES_PER_SEGMENT - 1 ;
1447
+ newestXact = newestPage * per_page + offset ;
1448
+ Assert (newestXact / per_page == newestPage );
1449
+ oldestXact = newestXact + 1 ;
1450
+ oldestXact -= 1U << 31 ;
1451
+ oldestPage = oldestXact / per_page ;
1452
+ Assert (!SlruMayDeleteSegment (ctl ,
1453
+ (newestPage -
1454
+ newestPage % SLRU_PAGES_PER_SEGMENT ),
1455
+ oldestPage ));
1456
+
1457
+ /*
1458
+ * GetNewTransactionId() has assigned the last XID it can safely use, and
1459
+ * that XID is in the *FIRST* page of the second segment. We must not
1460
+ * delete that segment.
1461
+ */
1462
+ newestPage = SLRU_PAGES_PER_SEGMENT ;
1463
+ newestXact = newestPage * per_page + offset ;
1464
+ Assert (newestXact / per_page == newestPage );
1465
+ oldestXact = newestXact + 1 ;
1466
+ oldestXact -= 1U << 31 ;
1467
+ oldestPage = oldestXact / per_page ;
1468
+ Assert (!SlruMayDeleteSegment (ctl ,
1469
+ (newestPage -
1470
+ newestPage % SLRU_PAGES_PER_SEGMENT ),
1471
+ oldestPage ));
1472
+ }
1473
+
1474
+ /*
1475
+ * Unit-test a PagePrecedes function.
1476
+ *
1477
+ * This assumes every uint32 >= FirstNormalTransactionId is a valid key. It
1478
+ * assumes each value occupies a contiguous, fixed-size region of SLRU bytes.
1479
+ * (MultiXactMemberCtl separates flags from XIDs. AsyncCtl has
1480
+ * variable-length entries, no keys, and no random access. These unit tests
1481
+ * do not apply to them.)
1482
+ */
1483
+ void
1484
+ SlruPagePrecedesUnitTests (SlruCtl ctl , int per_page )
1485
+ {
1486
+ /* Test first, middle and last entries of a page. */
1487
+ SlruPagePrecedesTestOffset (ctl , per_page , 0 );
1488
+ SlruPagePrecedesTestOffset (ctl , per_page , per_page / 2 );
1489
+ SlruPagePrecedesTestOffset (ctl , per_page , per_page - 1 );
1490
+ }
1491
+ #endif
1492
+
1380
1493
/*
1381
1494
* SlruScanDirectory callback
1382
- * This callback reports true if there's any segment prior to the one
1383
- * containing the page passed as "data".
1495
+ * This callback reports true if there's any segment wholly prior to the
1496
+ * one containing the page passed as "data".
1384
1497
*/
1385
1498
bool
1386
1499
SlruScanDirCbReportPresence (SlruCtl ctl , char * filename , int segpage , void * data )
1387
1500
{
1388
1501
int cutoffPage = * (int * ) data ;
1389
1502
1390
- cutoffPage -= cutoffPage % SLRU_PAGES_PER_SEGMENT ;
1391
-
1392
- if (ctl -> PagePrecedes (segpage , cutoffPage ))
1503
+ if (SlruMayDeleteSegment (ctl , segpage , cutoffPage ))
1393
1504
return true; /* found one; don't iterate any more */
1394
1505
1395
1506
return false; /* keep going */
@@ -1404,7 +1515,7 @@ SlruScanDirCbDeleteCutoff(SlruCtl ctl, char *filename, int segpage, void *data)
1404
1515
{
1405
1516
int cutoffPage = * (int * ) data ;
1406
1517
1407
- if (ctl -> PagePrecedes ( segpage , cutoffPage ))
1518
+ if (SlruMayDeleteSegment ( ctl , segpage , cutoffPage ))
1408
1519
SlruInternalDeleteSegment (ctl , segpage / SLRU_PAGES_PER_SEGMENT );
1409
1520
1410
1521
return false; /* keep going */
0 commit comments