@@ -1199,11 +1199,6 @@ SimpleLruTruncate(SlruCtl ctl, int cutoffPage)
1199
1199
SlruShared shared = ctl -> shared ;
1200
1200
int slotno ;
1201
1201
1202
- /*
1203
- * The cutoff point is the start of the segment containing cutoffPage.
1204
- */
1205
- cutoffPage -= cutoffPage % SLRU_PAGES_PER_SEGMENT ;
1206
-
1207
1202
/*
1208
1203
* Scan shared memory and remove any pages preceding the cutoff page, to
1209
1204
* ensure we won't rewrite them later. (Since this is normally called in
@@ -1216,9 +1211,7 @@ restart:;
1216
1211
1217
1212
/*
1218
1213
* While we are holding the lock, make an important safety check: the
1219
- * planned cutoff point must be <= the current endpoint page. Otherwise we
1220
- * have already wrapped around, and proceeding with the truncation would
1221
- * risk removing the current segment.
1214
+ * current endpoint page must not be eligible for removal.
1222
1215
*/
1223
1216
if (ctl -> PagePrecedes (shared -> latest_page_number , cutoffPage ))
1224
1217
{
@@ -1250,8 +1243,11 @@ restart:;
1250
1243
* Hmm, we have (or may have) I/O operations acting on the page, so
1251
1244
* we've got to wait for them to finish and then start again. This is
1252
1245
* the same logic as in SlruSelectLRUPage. (XXX if page is dirty,
1253
- * wouldn't it be OK to just discard it without writing it? For now,
1254
- * keep the logic the same as it was.)
1246
+ * wouldn't it be OK to just discard it without writing it?
1247
+ * SlruMayDeleteSegment() uses a stricter qualification, so we might
1248
+ * not delete this page in the end; even if we don't delete it, we
1249
+ * won't have cause to read its data again. For now, keep the logic
1250
+ * the same as it was.)
1255
1251
*/
1256
1252
if (shared -> page_status [slotno ] == SLRU_PAGE_VALID )
1257
1253
SlruInternalWritePage (ctl , slotno , NULL );
@@ -1341,19 +1337,134 @@ SlruDeleteSegment(SlruCtl ctl, int segno)
1341
1337
LWLockRelease (shared -> ControlLock );
1342
1338
}
1343
1339
1340
+ /*
1341
+ * Determine whether a segment is okay to delete.
1342
+ *
1343
+ * segpage is the first page of the segment, and cutoffPage is the oldest (in
1344
+ * PagePrecedes order) page in the SLRU containing still-useful data. Since
1345
+ * every core PagePrecedes callback implements "wrap around", check the
1346
+ * segment's first and last pages:
1347
+ *
1348
+ * first<cutoff && last<cutoff: yes
1349
+ * first<cutoff && last>=cutoff: no; cutoff falls inside this segment
1350
+ * first>=cutoff && last<cutoff: no; wrap point falls inside this segment
1351
+ * first>=cutoff && last>=cutoff: no; every page of this segment is too young
1352
+ */
1353
+ static bool
1354
+ SlruMayDeleteSegment (SlruCtl ctl , int segpage , int cutoffPage )
1355
+ {
1356
+ int seg_last_page = segpage + SLRU_PAGES_PER_SEGMENT - 1 ;
1357
+
1358
+ Assert (segpage % SLRU_PAGES_PER_SEGMENT == 0 );
1359
+
1360
+ return (ctl -> PagePrecedes (segpage , cutoffPage ) &&
1361
+ ctl -> PagePrecedes (seg_last_page , cutoffPage ));
1362
+ }
1363
+
1364
+ #ifdef USE_ASSERT_CHECKING
1365
+ static void
1366
+ SlruPagePrecedesTestOffset (SlruCtl ctl , int per_page , uint32 offset )
1367
+ {
1368
+ TransactionId lhs ,
1369
+ rhs ;
1370
+ int newestPage ,
1371
+ oldestPage ;
1372
+ TransactionId newestXact ,
1373
+ oldestXact ;
1374
+
1375
+ /*
1376
+ * Compare an XID pair having undefined order (see RFC 1982), a pair at
1377
+ * "opposite ends" of the XID space. TransactionIdPrecedes() treats each
1378
+ * as preceding the other. If RHS is oldestXact, LHS is the first XID we
1379
+ * must not assign.
1380
+ */
1381
+ lhs = per_page + offset ; /* skip first page to avoid non-normal XIDs */
1382
+ rhs = lhs + (1U << 31 );
1383
+ Assert (TransactionIdPrecedes (lhs , rhs ));
1384
+ Assert (TransactionIdPrecedes (rhs , lhs ));
1385
+ Assert (!TransactionIdPrecedes (lhs - 1 , rhs ));
1386
+ Assert (TransactionIdPrecedes (rhs , lhs - 1 ));
1387
+ Assert (TransactionIdPrecedes (lhs + 1 , rhs ));
1388
+ Assert (!TransactionIdPrecedes (rhs , lhs + 1 ));
1389
+ Assert (!TransactionIdFollowsOrEquals (lhs , rhs ));
1390
+ Assert (!TransactionIdFollowsOrEquals (rhs , lhs ));
1391
+ Assert (!ctl -> PagePrecedes (lhs / per_page , lhs / per_page ));
1392
+ Assert (!ctl -> PagePrecedes (lhs / per_page , rhs / per_page ));
1393
+ Assert (!ctl -> PagePrecedes (rhs / per_page , lhs / per_page ));
1394
+ Assert (!ctl -> PagePrecedes ((lhs - per_page ) / per_page , rhs / per_page ));
1395
+ Assert (ctl -> PagePrecedes (rhs / per_page , (lhs - 3 * per_page ) / per_page ));
1396
+ Assert (ctl -> PagePrecedes (rhs / per_page , (lhs - 2 * per_page ) / per_page ));
1397
+ Assert (ctl -> PagePrecedes (rhs / per_page , (lhs - 1 * per_page ) / per_page )
1398
+ || (1U << 31 ) % per_page != 0 ); /* See CommitTsPagePrecedes() */
1399
+ Assert (ctl -> PagePrecedes ((lhs + 1 * per_page ) / per_page , rhs / per_page )
1400
+ || (1U << 31 ) % per_page != 0 );
1401
+ Assert (ctl -> PagePrecedes ((lhs + 2 * per_page ) / per_page , rhs / per_page ));
1402
+ Assert (ctl -> PagePrecedes ((lhs + 3 * per_page ) / per_page , rhs / per_page ));
1403
+ Assert (!ctl -> PagePrecedes (rhs / per_page , (lhs + per_page ) / per_page ));
1404
+
1405
+ /*
1406
+ * GetNewTransactionId() has assigned the last XID it can safely use, and
1407
+ * that XID is in the *LAST* page of the second segment. We must not
1408
+ * delete that segment.
1409
+ */
1410
+ newestPage = 2 * SLRU_PAGES_PER_SEGMENT - 1 ;
1411
+ newestXact = newestPage * per_page + offset ;
1412
+ Assert (newestXact / per_page == newestPage );
1413
+ oldestXact = newestXact + 1 ;
1414
+ oldestXact -= 1U << 31 ;
1415
+ oldestPage = oldestXact / per_page ;
1416
+ Assert (!SlruMayDeleteSegment (ctl ,
1417
+ (newestPage -
1418
+ newestPage % SLRU_PAGES_PER_SEGMENT ),
1419
+ oldestPage ));
1420
+
1421
+ /*
1422
+ * GetNewTransactionId() has assigned the last XID it can safely use, and
1423
+ * that XID is in the *FIRST* page of the second segment. We must not
1424
+ * delete that segment.
1425
+ */
1426
+ newestPage = SLRU_PAGES_PER_SEGMENT ;
1427
+ newestXact = newestPage * per_page + offset ;
1428
+ Assert (newestXact / per_page == newestPage );
1429
+ oldestXact = newestXact + 1 ;
1430
+ oldestXact -= 1U << 31 ;
1431
+ oldestPage = oldestXact / per_page ;
1432
+ Assert (!SlruMayDeleteSegment (ctl ,
1433
+ (newestPage -
1434
+ newestPage % SLRU_PAGES_PER_SEGMENT ),
1435
+ oldestPage ));
1436
+ }
1437
+
1438
+ /*
1439
+ * Unit-test a PagePrecedes function.
1440
+ *
1441
+ * This assumes every uint32 >= FirstNormalTransactionId is a valid key. It
1442
+ * assumes each value occupies a contiguous, fixed-size region of SLRU bytes.
1443
+ * (MultiXactMemberCtl separates flags from XIDs. AsyncCtl has
1444
+ * variable-length entries, no keys, and no random access. These unit tests
1445
+ * do not apply to them.)
1446
+ */
1447
+ void
1448
+ SlruPagePrecedesUnitTests (SlruCtl ctl , int per_page )
1449
+ {
1450
+ /* Test first, middle and last entries of a page. */
1451
+ SlruPagePrecedesTestOffset (ctl , per_page , 0 );
1452
+ SlruPagePrecedesTestOffset (ctl , per_page , per_page / 2 );
1453
+ SlruPagePrecedesTestOffset (ctl , per_page , per_page - 1 );
1454
+ }
1455
+ #endif
1456
+
1344
1457
/*
1345
1458
* SlruScanDirectory callback
1346
- * This callback reports true if there's any segment prior to the one
1347
- * containing the page passed as "data".
1459
+ * This callback reports true if there's any segment wholly prior to the
1460
+ * one containing the page passed as "data".
1348
1461
*/
1349
1462
bool
1350
1463
SlruScanDirCbReportPresence (SlruCtl ctl , char * filename , int segpage , void * data )
1351
1464
{
1352
1465
int cutoffPage = * (int * ) data ;
1353
1466
1354
- cutoffPage -= cutoffPage % SLRU_PAGES_PER_SEGMENT ;
1355
-
1356
- if (ctl -> PagePrecedes (segpage , cutoffPage ))
1467
+ if (SlruMayDeleteSegment (ctl , segpage , cutoffPage ))
1357
1468
return true; /* found one; don't iterate any more */
1358
1469
1359
1470
return false; /* keep going */
@@ -1368,7 +1479,7 @@ SlruScanDirCbDeleteCutoff(SlruCtl ctl, char *filename, int segpage, void *data)
1368
1479
{
1369
1480
int cutoffPage = * (int * ) data ;
1370
1481
1371
- if (ctl -> PagePrecedes ( segpage , cutoffPage ))
1482
+ if (SlruMayDeleteSegment ( ctl , segpage , cutoffPage ))
1372
1483
SlruInternalDeleteSegment (ctl , filename );
1373
1484
1374
1485
return false; /* keep going */
0 commit comments