@@ -1156,11 +1156,6 @@ SimpleLruTruncate(SlruCtl ctl, int cutoffPage)
1156
1156
SlruShared shared = ctl -> shared ;
1157
1157
int slotno ;
1158
1158
1159
- /*
1160
- * The cutoff point is the start of the segment containing cutoffPage.
1161
- */
1162
- cutoffPage -= cutoffPage % SLRU_PAGES_PER_SEGMENT ;
1163
-
1164
1159
/*
1165
1160
* Scan shared memory and remove any pages preceding the cutoff page, to
1166
1161
* ensure we won't rewrite them later. (Since this is normally called in
@@ -1173,9 +1168,7 @@ restart:;
1173
1168
1174
1169
/*
1175
1170
* While we are holding the lock, make an important safety check: the
1176
- * planned cutoff point must be <= the current endpoint page. Otherwise we
1177
- * have already wrapped around, and proceeding with the truncation would
1178
- * risk removing the current segment.
1171
+ * current endpoint page must not be eligible for removal.
1179
1172
*/
1180
1173
if (ctl -> PagePrecedes (shared -> latest_page_number , cutoffPage ))
1181
1174
{
@@ -1207,8 +1200,11 @@ restart:;
1207
1200
* Hmm, we have (or may have) I/O operations acting on the page, so
1208
1201
* we've got to wait for them to finish and then start again. This is
1209
1202
* the same logic as in SlruSelectLRUPage. (XXX if page is dirty,
1210
- * wouldn't it be OK to just discard it without writing it? For now,
1211
- * keep the logic the same as it was.)
1203
+ * wouldn't it be OK to just discard it without writing it?
1204
+ * SlruMayDeleteSegment() uses a stricter qualification, so we might
1205
+ * not delete this page in the end; even if we don't delete it, we
1206
+ * won't have cause to read its data again. For now, keep the logic
1207
+ * the same as it was.)
1212
1208
*/
1213
1209
if (shared -> page_status [slotno ] == SLRU_PAGE_VALID )
1214
1210
SlruInternalWritePage (ctl , slotno , NULL );
@@ -1298,19 +1294,134 @@ SlruDeleteSegment(SlruCtl ctl, int segno)
1298
1294
LWLockRelease (shared -> ControlLock );
1299
1295
}
1300
1296
1297
+ /*
1298
+ * Determine whether a segment is okay to delete.
1299
+ *
1300
+ * segpage is the first page of the segment, and cutoffPage is the oldest (in
1301
+ * PagePrecedes order) page in the SLRU containing still-useful data. Since
1302
+ * every core PagePrecedes callback implements "wrap around", check the
1303
+ * segment's first and last pages:
1304
+ *
1305
+ * first<cutoff && last<cutoff: yes
1306
+ * first<cutoff && last>=cutoff: no; cutoff falls inside this segment
1307
+ * first>=cutoff && last<cutoff: no; wrap point falls inside this segment
1308
+ * first>=cutoff && last>=cutoff: no; every page of this segment is too young
1309
+ */
1310
+ static bool
1311
+ SlruMayDeleteSegment (SlruCtl ctl , int segpage , int cutoffPage )
1312
+ {
1313
+ int seg_last_page = segpage + SLRU_PAGES_PER_SEGMENT - 1 ;
1314
+
1315
+ Assert (segpage % SLRU_PAGES_PER_SEGMENT == 0 );
1316
+
1317
+ return (ctl -> PagePrecedes (segpage , cutoffPage ) &&
1318
+ ctl -> PagePrecedes (seg_last_page , cutoffPage ));
1319
+ }
1320
+
1321
+ #ifdef USE_ASSERT_CHECKING
1322
+ static void
1323
+ SlruPagePrecedesTestOffset (SlruCtl ctl , int per_page , uint32 offset )
1324
+ {
1325
+ TransactionId lhs ,
1326
+ rhs ;
1327
+ int newestPage ,
1328
+ oldestPage ;
1329
+ TransactionId newestXact ,
1330
+ oldestXact ;
1331
+
1332
+ /*
1333
+ * Compare an XID pair having undefined order (see RFC 1982), a pair at
1334
+ * "opposite ends" of the XID space. TransactionIdPrecedes() treats each
1335
+ * as preceding the other. If RHS is oldestXact, LHS is the first XID we
1336
+ * must not assign.
1337
+ */
1338
+ lhs = per_page + offset ; /* skip first page to avoid non-normal XIDs */
1339
+ rhs = lhs + (1U << 31 );
1340
+ Assert (TransactionIdPrecedes (lhs , rhs ));
1341
+ Assert (TransactionIdPrecedes (rhs , lhs ));
1342
+ Assert (!TransactionIdPrecedes (lhs - 1 , rhs ));
1343
+ Assert (TransactionIdPrecedes (rhs , lhs - 1 ));
1344
+ Assert (TransactionIdPrecedes (lhs + 1 , rhs ));
1345
+ Assert (!TransactionIdPrecedes (rhs , lhs + 1 ));
1346
+ Assert (!TransactionIdFollowsOrEquals (lhs , rhs ));
1347
+ Assert (!TransactionIdFollowsOrEquals (rhs , lhs ));
1348
+ Assert (!ctl -> PagePrecedes (lhs / per_page , lhs / per_page ));
1349
+ Assert (!ctl -> PagePrecedes (lhs / per_page , rhs / per_page ));
1350
+ Assert (!ctl -> PagePrecedes (rhs / per_page , lhs / per_page ));
1351
+ Assert (!ctl -> PagePrecedes ((lhs - per_page ) / per_page , rhs / per_page ));
1352
+ Assert (ctl -> PagePrecedes (rhs / per_page , (lhs - 3 * per_page ) / per_page ));
1353
+ Assert (ctl -> PagePrecedes (rhs / per_page , (lhs - 2 * per_page ) / per_page ));
1354
+ Assert (ctl -> PagePrecedes (rhs / per_page , (lhs - 1 * per_page ) / per_page )
1355
+ || (1U << 31 ) % per_page != 0 ); /* See CommitTsPagePrecedes() */
1356
+ Assert (ctl -> PagePrecedes ((lhs + 1 * per_page ) / per_page , rhs / per_page )
1357
+ || (1U << 31 ) % per_page != 0 );
1358
+ Assert (ctl -> PagePrecedes ((lhs + 2 * per_page ) / per_page , rhs / per_page ));
1359
+ Assert (ctl -> PagePrecedes ((lhs + 3 * per_page ) / per_page , rhs / per_page ));
1360
+ Assert (!ctl -> PagePrecedes (rhs / per_page , (lhs + per_page ) / per_page ));
1361
+
1362
+ /*
1363
+ * GetNewTransactionId() has assigned the last XID it can safely use, and
1364
+ * that XID is in the *LAST* page of the second segment. We must not
1365
+ * delete that segment.
1366
+ */
1367
+ newestPage = 2 * SLRU_PAGES_PER_SEGMENT - 1 ;
1368
+ newestXact = newestPage * per_page + offset ;
1369
+ Assert (newestXact / per_page == newestPage );
1370
+ oldestXact = newestXact + 1 ;
1371
+ oldestXact -= 1U << 31 ;
1372
+ oldestPage = oldestXact / per_page ;
1373
+ Assert (!SlruMayDeleteSegment (ctl ,
1374
+ (newestPage -
1375
+ newestPage % SLRU_PAGES_PER_SEGMENT ),
1376
+ oldestPage ));
1377
+
1378
+ /*
1379
+ * GetNewTransactionId() has assigned the last XID it can safely use, and
1380
+ * that XID is in the *FIRST* page of the second segment. We must not
1381
+ * delete that segment.
1382
+ */
1383
+ newestPage = SLRU_PAGES_PER_SEGMENT ;
1384
+ newestXact = newestPage * per_page + offset ;
1385
+ Assert (newestXact / per_page == newestPage );
1386
+ oldestXact = newestXact + 1 ;
1387
+ oldestXact -= 1U << 31 ;
1388
+ oldestPage = oldestXact / per_page ;
1389
+ Assert (!SlruMayDeleteSegment (ctl ,
1390
+ (newestPage -
1391
+ newestPage % SLRU_PAGES_PER_SEGMENT ),
1392
+ oldestPage ));
1393
+ }
1394
+
1395
+ /*
1396
+ * Unit-test a PagePrecedes function.
1397
+ *
1398
+ * This assumes every uint32 >= FirstNormalTransactionId is a valid key. It
1399
+ * assumes each value occupies a contiguous, fixed-size region of SLRU bytes.
1400
+ * (MultiXactMemberCtl separates flags from XIDs. AsyncCtl has
1401
+ * variable-length entries, no keys, and no random access. These unit tests
1402
+ * do not apply to them.)
1403
+ */
1404
+ void
1405
+ SlruPagePrecedesUnitTests (SlruCtl ctl , int per_page )
1406
+ {
1407
+ /* Test first, middle and last entries of a page. */
1408
+ SlruPagePrecedesTestOffset (ctl , per_page , 0 );
1409
+ SlruPagePrecedesTestOffset (ctl , per_page , per_page / 2 );
1410
+ SlruPagePrecedesTestOffset (ctl , per_page , per_page - 1 );
1411
+ }
1412
+ #endif
1413
+
1301
1414
/*
1302
1415
* SlruScanDirectory callback
1303
- * This callback reports true if there's any segment prior to the one
1304
- * containing the page passed as "data".
1416
+ * This callback reports true if there's any segment wholly prior to the
1417
+ * one containing the page passed as "data".
1305
1418
*/
1306
1419
bool
1307
1420
SlruScanDirCbReportPresence (SlruCtl ctl , char * filename , int segpage , void * data )
1308
1421
{
1309
1422
int cutoffPage = * (int * ) data ;
1310
1423
1311
- cutoffPage -= cutoffPage % SLRU_PAGES_PER_SEGMENT ;
1312
-
1313
- if (ctl -> PagePrecedes (segpage , cutoffPage ))
1424
+ if (SlruMayDeleteSegment (ctl , segpage , cutoffPage ))
1314
1425
return true; /* found one; don't iterate any more */
1315
1426
1316
1427
return false; /* keep going */
@@ -1325,7 +1436,7 @@ SlruScanDirCbDeleteCutoff(SlruCtl ctl, char *filename, int segpage, void *data)
1325
1436
{
1326
1437
int cutoffPage = * (int * ) data ;
1327
1438
1328
- if (ctl -> PagePrecedes ( segpage , cutoffPage ))
1439
+ if (SlruMayDeleteSegment ( ctl , segpage , cutoffPage ))
1329
1440
SlruInternalDeleteSegment (ctl , filename );
1330
1441
1331
1442
return false; /* keep going */
0 commit comments