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