@@ -89,10 +89,10 @@ static void CatCachePrintStats(int code, Datum arg);
89
89
static void CatCacheRemoveCTup (CatCache * cache , CatCTup * ct );
90
90
static void CatCacheRemoveCList (CatCache * cache , CatCList * cl );
91
91
static void CatalogCacheInitializeCache (CatCache * cache );
92
- static CatCTup * CatalogCacheCreateEntry (CatCache * cache , HeapTuple ntp ,
92
+ static CatCTup * CatalogCacheCreateEntry (CatCache * cache ,
93
+ HeapTuple ntp , SysScanDesc scandesc ,
93
94
Datum * arguments ,
94
- uint32 hashValue , Index hashIndex ,
95
- bool negative );
95
+ uint32 hashValue , Index hashIndex );
96
96
97
97
static void CatCacheFreeKeys (TupleDesc tupdesc , int nkeys , int * attnos ,
98
98
Datum * keys );
@@ -1321,6 +1321,7 @@ SearchCatCacheMiss(CatCache *cache,
1321
1321
SysScanDesc scandesc ;
1322
1322
HeapTuple ntp ;
1323
1323
CatCTup * ct ;
1324
+ bool stale ;
1324
1325
Datum arguments [CATCACHE_MAXKEYS ];
1325
1326
1326
1327
/* Initialize local parameter array */
@@ -1329,16 +1330,6 @@ SearchCatCacheMiss(CatCache *cache,
1329
1330
arguments [2 ] = v3 ;
1330
1331
arguments [3 ] = v4 ;
1331
1332
1332
- /*
1333
- * Ok, need to make a lookup in the relation, copy the scankey and fill
1334
- * out any per-call fields.
1335
- */
1336
- memcpy (cur_skey , cache -> cc_skey , sizeof (ScanKeyData ) * nkeys );
1337
- cur_skey [0 ].sk_argument = v1 ;
1338
- cur_skey [1 ].sk_argument = v2 ;
1339
- cur_skey [2 ].sk_argument = v3 ;
1340
- cur_skey [3 ].sk_argument = v4 ;
1341
-
1342
1333
/*
1343
1334
* Tuple was not found in cache, so we have to try to retrieve it directly
1344
1335
* from the relation. If found, we will add it to the cache; if not
@@ -1353,9 +1344,28 @@ SearchCatCacheMiss(CatCache *cache,
1353
1344
* will eventually age out of the cache, so there's no functional problem.
1354
1345
* This case is rare enough that it's not worth expending extra cycles to
1355
1346
* detect.
1347
+ *
1348
+ * Another case, which we *must* handle, is that the tuple could become
1349
+ * outdated during CatalogCacheCreateEntry's attempt to detoast it (since
1350
+ * AcceptInvalidationMessages can run during TOAST table access). We do
1351
+ * not want to return already-stale catcache entries, so we loop around
1352
+ * and do the table scan again if that happens.
1356
1353
*/
1357
1354
relation = table_open (cache -> cc_reloid , AccessShareLock );
1358
1355
1356
+ do
1357
+ {
1358
+ /*
1359
+ * Ok, need to make a lookup in the relation, copy the scankey and
1360
+ * fill out any per-call fields. (We must re-do this when retrying,
1361
+ * because systable_beginscan scribbles on the scankey.)
1362
+ */
1363
+ memcpy (cur_skey , cache -> cc_skey , sizeof (ScanKeyData ) * nkeys );
1364
+ cur_skey [0 ].sk_argument = v1 ;
1365
+ cur_skey [1 ].sk_argument = v2 ;
1366
+ cur_skey [2 ].sk_argument = v3 ;
1367
+ cur_skey [3 ].sk_argument = v4 ;
1368
+
1359
1369
scandesc = systable_beginscan (relation ,
1360
1370
cache -> cc_indexoid ,
1361
1371
IndexScanOK (cache , cur_skey ),
@@ -1364,12 +1374,18 @@ SearchCatCacheMiss(CatCache *cache,
1364
1374
cur_skey );
1365
1375
1366
1376
ct = NULL ;
1377
+ stale = false;
1367
1378
1368
1379
while (HeapTupleIsValid (ntp = systable_getnext (scandesc )))
1369
1380
{
1370
- ct = CatalogCacheCreateEntry (cache , ntp , arguments ,
1371
- hashValue , hashIndex ,
1372
- false);
1381
+ ct = CatalogCacheCreateEntry (cache , ntp , scandesc , NULL ,
1382
+ hashValue , hashIndex );
1383
+ /* upon failure, we must start the scan over */
1384
+ if (ct == NULL )
1385
+ {
1386
+ stale = true;
1387
+ break ;
1388
+ }
1373
1389
/* immediately set the refcount to 1 */
1374
1390
ResourceOwnerEnlargeCatCacheRefs (CurrentResourceOwner );
1375
1391
ct -> refcount ++ ;
@@ -1378,6 +1394,7 @@ SearchCatCacheMiss(CatCache *cache,
1378
1394
}
1379
1395
1380
1396
systable_endscan (scandesc );
1397
+ } while (stale );
1381
1398
1382
1399
table_close (relation , AccessShareLock );
1383
1400
@@ -1396,9 +1413,11 @@ SearchCatCacheMiss(CatCache *cache,
1396
1413
if (IsBootstrapProcessingMode ())
1397
1414
return NULL ;
1398
1415
1399
- ct = CatalogCacheCreateEntry (cache , NULL , arguments ,
1400
- hashValue , hashIndex ,
1401
- true);
1416
+ ct = CatalogCacheCreateEntry (cache , NULL , NULL , arguments ,
1417
+ hashValue , hashIndex );
1418
+
1419
+ /* Creating a negative cache entry shouldn't fail */
1420
+ Assert (ct != NULL );
1402
1421
1403
1422
CACHE_elog (DEBUG2 , "SearchCatCache(%s): Contains %d/%d tuples" ,
1404
1423
cache -> cc_relname , cache -> cc_ntup , CacheHdr -> ch_ntup );
@@ -1605,7 +1624,8 @@ SearchCatCacheList(CatCache *cache,
1605
1624
* We have to bump the member refcounts temporarily to ensure they won't
1606
1625
* get dropped from the cache while loading other members. We use a PG_TRY
1607
1626
* block to ensure we can undo those refcounts if we get an error before
1608
- * we finish constructing the CatCList.
1627
+ * we finish constructing the CatCList. ctlist must be valid throughout
1628
+ * the PG_TRY block.
1609
1629
*/
1610
1630
ResourceOwnerEnlargeCatCacheListRefs (CurrentResourceOwner );
1611
1631
@@ -1616,19 +1636,23 @@ SearchCatCacheList(CatCache *cache,
1616
1636
ScanKeyData cur_skey [CATCACHE_MAXKEYS ];
1617
1637
Relation relation ;
1618
1638
SysScanDesc scandesc ;
1619
-
1620
- /*
1621
- * Ok, need to make a lookup in the relation, copy the scankey and
1622
- * fill out any per-call fields.
1623
- */
1624
- memcpy (cur_skey , cache -> cc_skey , sizeof (ScanKeyData ) * cache -> cc_nkeys );
1625
- cur_skey [0 ].sk_argument = v1 ;
1626
- cur_skey [1 ].sk_argument = v2 ;
1627
- cur_skey [2 ].sk_argument = v3 ;
1628
- cur_skey [3 ].sk_argument = v4 ;
1639
+ bool stale ;
1629
1640
1630
1641
relation = table_open (cache -> cc_reloid , AccessShareLock );
1631
1642
1643
+ do
1644
+ {
1645
+ /*
1646
+ * Ok, need to make a lookup in the relation, copy the scankey and
1647
+ * fill out any per-call fields. (We must re-do this when
1648
+ * retrying, because systable_beginscan scribbles on the scankey.)
1649
+ */
1650
+ memcpy (cur_skey , cache -> cc_skey , sizeof (ScanKeyData ) * cache -> cc_nkeys );
1651
+ cur_skey [0 ].sk_argument = v1 ;
1652
+ cur_skey [1 ].sk_argument = v2 ;
1653
+ cur_skey [2 ].sk_argument = v3 ;
1654
+ cur_skey [3 ].sk_argument = v4 ;
1655
+
1632
1656
scandesc = systable_beginscan (relation ,
1633
1657
cache -> cc_indexoid ,
1634
1658
IndexScanOK (cache , cur_skey ),
@@ -1639,6 +1663,8 @@ SearchCatCacheList(CatCache *cache,
1639
1663
/* The list will be ordered iff we are doing an index scan */
1640
1664
ordered = (scandesc -> irel != NULL );
1641
1665
1666
+ stale = false;
1667
+
1642
1668
while (HeapTupleIsValid (ntp = systable_getnext (scandesc )))
1643
1669
{
1644
1670
uint32 hashValue ;
@@ -1681,9 +1707,32 @@ SearchCatCacheList(CatCache *cache,
1681
1707
if (!found )
1682
1708
{
1683
1709
/* We didn't find a usable entry, so make a new one */
1684
- ct = CatalogCacheCreateEntry (cache , ntp , arguments ,
1685
- hashValue , hashIndex ,
1686
- false);
1710
+ ct = CatalogCacheCreateEntry (cache , ntp , scandesc , NULL ,
1711
+ hashValue , hashIndex );
1712
+ /* upon failure, we must start the scan over */
1713
+ if (ct == NULL )
1714
+ {
1715
+ /*
1716
+ * Release refcounts on any items we already had. We dare
1717
+ * not try to free them if they're now unreferenced, since
1718
+ * an error while doing that would result in the PG_CATCH
1719
+ * below doing extra refcount decrements. Besides, we'll
1720
+ * likely re-adopt those items in the next iteration, so
1721
+ * it's not worth complicating matters to try to get rid
1722
+ * of them.
1723
+ */
1724
+ foreach (ctlist_item , ctlist )
1725
+ {
1726
+ ct = (CatCTup * ) lfirst (ctlist_item );
1727
+ Assert (ct -> c_list == NULL );
1728
+ Assert (ct -> refcount > 0 );
1729
+ ct -> refcount -- ;
1730
+ }
1731
+ /* Reset ctlist in preparation for new try */
1732
+ ctlist = NIL ;
1733
+ stale = true;
1734
+ break ;
1735
+ }
1687
1736
}
1688
1737
1689
1738
/* Careful here: add entry to ctlist, then bump its refcount */
@@ -1693,6 +1742,7 @@ SearchCatCacheList(CatCache *cache,
1693
1742
}
1694
1743
1695
1744
systable_endscan (scandesc );
1745
+ } while (stale );
1696
1746
1697
1747
table_close (relation , AccessShareLock );
1698
1748
@@ -1800,22 +1850,42 @@ ReleaseCatCacheList(CatCList *list)
1800
1850
* CatalogCacheCreateEntry
1801
1851
* Create a new CatCTup entry, copying the given HeapTuple and other
1802
1852
* supplied data into it. The new entry initially has refcount 0.
1853
+ *
1854
+ * To create a normal cache entry, ntp must be the HeapTuple just fetched
1855
+ * from scandesc, and "arguments" is not used. To create a negative cache
1856
+ * entry, pass NULL for ntp and scandesc; then "arguments" is the cache
1857
+ * keys to use. In either case, hashValue/hashIndex are the hash values
1858
+ * computed from the cache keys.
1859
+ *
1860
+ * Returns NULL if we attempt to detoast the tuple and observe that it
1861
+ * became stale. (This cannot happen for a negative entry.) Caller must
1862
+ * retry the tuple lookup in that case.
1803
1863
*/
1804
1864
static CatCTup *
1805
- CatalogCacheCreateEntry (CatCache * cache , HeapTuple ntp , Datum * arguments ,
1806
- uint32 hashValue , Index hashIndex ,
1807
- bool negative )
1865
+ CatalogCacheCreateEntry (CatCache * cache , HeapTuple ntp , SysScanDesc scandesc ,
1866
+ Datum * arguments ,
1867
+ uint32 hashValue , Index hashIndex )
1808
1868
{
1809
1869
CatCTup * ct ;
1810
1870
HeapTuple dtp ;
1811
1871
MemoryContext oldcxt ;
1812
1872
1813
- /* negative entries have no tuple associated */
1814
1873
if (ntp )
1815
1874
{
1816
1875
int i ;
1817
1876
1818
- Assert (!negative );
1877
+ /*
1878
+ * The visibility recheck below essentially never fails during our
1879
+ * regression tests, and there's no easy way to force it to fail for
1880
+ * testing purposes. To ensure we have test coverage for the retry
1881
+ * paths in our callers, make debug builds randomly fail about 0.1% of
1882
+ * the times through this code path, even when there's no toasted
1883
+ * fields.
1884
+ */
1885
+ #ifdef USE_ASSERT_CHECKING
1886
+ if (random () <= (MAX_RANDOM_VALUE / 1000 ))
1887
+ return NULL ;
1888
+ #endif
1819
1889
1820
1890
/*
1821
1891
* If there are any out-of-line toasted fields in the tuple, expand
@@ -1825,7 +1895,20 @@ CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp, Datum *arguments,
1825
1895
* something using a slightly stale catcache entry.
1826
1896
*/
1827
1897
if (HeapTupleHasExternal (ntp ))
1898
+ {
1828
1899
dtp = toast_flatten_tuple (ntp , cache -> cc_tupdesc );
1900
+
1901
+ /*
1902
+ * The tuple could become stale while we are doing toast table
1903
+ * access (since AcceptInvalidationMessages can run then), so we
1904
+ * must recheck its visibility afterwards.
1905
+ */
1906
+ if (!systable_recheck_tuple (scandesc , ntp ))
1907
+ {
1908
+ heap_freetuple (dtp );
1909
+ return NULL ;
1910
+ }
1911
+ }
1829
1912
else
1830
1913
dtp = ntp ;
1831
1914
@@ -1864,7 +1947,7 @@ CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp, Datum *arguments,
1864
1947
}
1865
1948
else
1866
1949
{
1867
- Assert ( negative );
1950
+ /* Set up keys for a negative cache entry */
1868
1951
oldcxt = MemoryContextSwitchTo (CacheMemoryContext );
1869
1952
ct = (CatCTup * ) palloc (sizeof (CatCTup ));
1870
1953
@@ -1886,7 +1969,7 @@ CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp, Datum *arguments,
1886
1969
ct -> c_list = NULL ;
1887
1970
ct -> refcount = 0 ; /* for the moment */
1888
1971
ct -> dead = false;
1889
- ct -> negative = negative ;
1972
+ ct -> negative = ( ntp == NULL ) ;
1890
1973
ct -> hash_value = hashValue ;
1891
1974
1892
1975
dlist_push_head (& cache -> cc_bucket [hashIndex ], & ct -> cache_elem );
0 commit comments