2424#include "catalog/pg_operator.h"
2525#include "catalog/pg_type.h"
2626#include "common/hashfn.h"
27+ #include "common/pg_prng.h"
2728#include "miscadmin.h"
2829#include "port/pg_bitutils.h"
2930#ifdef CATCACHE_STATS
@@ -90,10 +91,10 @@ static void CatCachePrintStats(int code, Datum arg);
9091static void CatCacheRemoveCTup (CatCache * cache , CatCTup * ct );
9192static void CatCacheRemoveCList (CatCache * cache , CatCList * cl );
9293static void CatalogCacheInitializeCache (CatCache * cache );
93- static CatCTup * CatalogCacheCreateEntry (CatCache * cache , HeapTuple ntp ,
94+ static CatCTup * CatalogCacheCreateEntry (CatCache * cache ,
95+ HeapTuple ntp , SysScanDesc scandesc ,
9496 Datum * arguments ,
95- uint32 hashValue , Index hashIndex ,
96- bool negative );
97+ uint32 hashValue , Index hashIndex );
9798
9899static void ReleaseCatCacheWithOwner (HeapTuple tuple , ResourceOwner resowner );
99100static void ReleaseCatCacheListWithOwner (CatCList * list , ResourceOwner resowner );
@@ -1372,6 +1373,7 @@ SearchCatCacheMiss(CatCache *cache,
13721373 SysScanDesc scandesc ;
13731374 HeapTuple ntp ;
13741375 CatCTup * ct ;
1376+ bool stale ;
13751377 Datum arguments [CATCACHE_MAXKEYS ];
13761378
13771379 /* Initialize local parameter array */
@@ -1380,16 +1382,6 @@ SearchCatCacheMiss(CatCache *cache,
13801382 arguments [2 ] = v3 ;
13811383 arguments [3 ] = v4 ;
13821384
1383- /*
1384- * Ok, need to make a lookup in the relation, copy the scankey and fill
1385- * out any per-call fields.
1386- */
1387- memcpy (cur_skey , cache -> cc_skey , sizeof (ScanKeyData ) * nkeys );
1388- cur_skey [0 ].sk_argument = v1 ;
1389- cur_skey [1 ].sk_argument = v2 ;
1390- cur_skey [2 ].sk_argument = v3 ;
1391- cur_skey [3 ].sk_argument = v4 ;
1392-
13931385 /*
13941386 * Tuple was not found in cache, so we have to try to retrieve it directly
13951387 * from the relation. If found, we will add it to the cache; if not
@@ -1404,9 +1396,28 @@ SearchCatCacheMiss(CatCache *cache,
14041396 * will eventually age out of the cache, so there's no functional problem.
14051397 * This case is rare enough that it's not worth expending extra cycles to
14061398 * detect.
1399+ *
1400+ * Another case, which we *must* handle, is that the tuple could become
1401+ * outdated during CatalogCacheCreateEntry's attempt to detoast it (since
1402+ * AcceptInvalidationMessages can run during TOAST table access). We do
1403+ * not want to return already-stale catcache entries, so we loop around
1404+ * and do the table scan again if that happens.
14071405 */
14081406 relation = table_open (cache -> cc_reloid , AccessShareLock );
14091407
1408+ do
1409+ {
1410+ /*
1411+ * Ok, need to make a lookup in the relation, copy the scankey and
1412+ * fill out any per-call fields. (We must re-do this when retrying,
1413+ * because systable_beginscan scribbles on the scankey.)
1414+ */
1415+ memcpy (cur_skey , cache -> cc_skey , sizeof (ScanKeyData ) * nkeys );
1416+ cur_skey [0 ].sk_argument = v1 ;
1417+ cur_skey [1 ].sk_argument = v2 ;
1418+ cur_skey [2 ].sk_argument = v3 ;
1419+ cur_skey [3 ].sk_argument = v4 ;
1420+
14101421 scandesc = systable_beginscan (relation ,
14111422 cache -> cc_indexoid ,
14121423 IndexScanOK (cache , cur_skey ),
@@ -1415,12 +1426,18 @@ SearchCatCacheMiss(CatCache *cache,
14151426 cur_skey );
14161427
14171428 ct = NULL ;
1429+ stale = false;
14181430
14191431 while (HeapTupleIsValid (ntp = systable_getnext (scandesc )))
14201432 {
1421- ct = CatalogCacheCreateEntry (cache , ntp , arguments ,
1422- hashValue , hashIndex ,
1423- false);
1433+ ct = CatalogCacheCreateEntry (cache , ntp , scandesc , NULL ,
1434+ hashValue , hashIndex );
1435+ /* upon failure, we must start the scan over */
1436+ if (ct == NULL )
1437+ {
1438+ stale = true;
1439+ break ;
1440+ }
14241441 /* immediately set the refcount to 1 */
14251442 ResourceOwnerEnlarge (CurrentResourceOwner );
14261443 ct -> refcount ++ ;
@@ -1429,6 +1446,7 @@ SearchCatCacheMiss(CatCache *cache,
14291446 }
14301447
14311448 systable_endscan (scandesc );
1449+ } while (stale );
14321450
14331451 table_close (relation , AccessShareLock );
14341452
@@ -1447,9 +1465,11 @@ SearchCatCacheMiss(CatCache *cache,
14471465 if (IsBootstrapProcessingMode ())
14481466 return NULL ;
14491467
1450- ct = CatalogCacheCreateEntry (cache , NULL , arguments ,
1451- hashValue , hashIndex ,
1452- true);
1468+ ct = CatalogCacheCreateEntry (cache , NULL , NULL , arguments ,
1469+ hashValue , hashIndex );
1470+
1471+ /* Creating a negative cache entry shouldn't fail */
1472+ Assert (ct != NULL );
14531473
14541474 CACHE_elog (DEBUG2 , "SearchCatCache(%s): Contains %d/%d tuples" ,
14551475 cache -> cc_relname , cache -> cc_ntup , CacheHdr -> ch_ntup );
@@ -1663,7 +1683,8 @@ SearchCatCacheList(CatCache *cache,
16631683 * We have to bump the member refcounts temporarily to ensure they won't
16641684 * get dropped from the cache while loading other members. We use a PG_TRY
16651685 * block to ensure we can undo those refcounts if we get an error before
1666- * we finish constructing the CatCList.
1686+ * we finish constructing the CatCList. ctlist must be valid throughout
1687+ * the PG_TRY block.
16671688 */
16681689 ctlist = NIL ;
16691690
@@ -1672,19 +1693,23 @@ SearchCatCacheList(CatCache *cache,
16721693 ScanKeyData cur_skey [CATCACHE_MAXKEYS ];
16731694 Relation relation ;
16741695 SysScanDesc scandesc ;
1675-
1676- /*
1677- * Ok, need to make a lookup in the relation, copy the scankey and
1678- * fill out any per-call fields.
1679- */
1680- memcpy (cur_skey , cache -> cc_skey , sizeof (ScanKeyData ) * cache -> cc_nkeys );
1681- cur_skey [0 ].sk_argument = v1 ;
1682- cur_skey [1 ].sk_argument = v2 ;
1683- cur_skey [2 ].sk_argument = v3 ;
1684- cur_skey [3 ].sk_argument = v4 ;
1696+ bool stale ;
16851697
16861698 relation = table_open (cache -> cc_reloid , AccessShareLock );
16871699
1700+ do
1701+ {
1702+ /*
1703+ * Ok, need to make a lookup in the relation, copy the scankey and
1704+ * fill out any per-call fields. (We must re-do this when
1705+ * retrying, because systable_beginscan scribbles on the scankey.)
1706+ */
1707+ memcpy (cur_skey , cache -> cc_skey , sizeof (ScanKeyData ) * cache -> cc_nkeys );
1708+ cur_skey [0 ].sk_argument = v1 ;
1709+ cur_skey [1 ].sk_argument = v2 ;
1710+ cur_skey [2 ].sk_argument = v3 ;
1711+ cur_skey [3 ].sk_argument = v4 ;
1712+
16881713 scandesc = systable_beginscan (relation ,
16891714 cache -> cc_indexoid ,
16901715 IndexScanOK (cache , cur_skey ),
@@ -1695,6 +1720,8 @@ SearchCatCacheList(CatCache *cache,
16951720 /* The list will be ordered iff we are doing an index scan */
16961721 ordered = (scandesc -> irel != NULL );
16971722
1723+ stale = false;
1724+
16981725 while (HeapTupleIsValid (ntp = systable_getnext (scandesc )))
16991726 {
17001727 uint32 hashValue ;
@@ -1737,9 +1764,32 @@ SearchCatCacheList(CatCache *cache,
17371764 if (!found )
17381765 {
17391766 /* We didn't find a usable entry, so make a new one */
1740- ct = CatalogCacheCreateEntry (cache , ntp , arguments ,
1741- hashValue , hashIndex ,
1742- false);
1767+ ct = CatalogCacheCreateEntry (cache , ntp , scandesc , NULL ,
1768+ hashValue , hashIndex );
1769+ /* upon failure, we must start the scan over */
1770+ if (ct == NULL )
1771+ {
1772+ /*
1773+ * Release refcounts on any items we already had. We dare
1774+ * not try to free them if they're now unreferenced, since
1775+ * an error while doing that would result in the PG_CATCH
1776+ * below doing extra refcount decrements. Besides, we'll
1777+ * likely re-adopt those items in the next iteration, so
1778+ * it's not worth complicating matters to try to get rid
1779+ * of them.
1780+ */
1781+ foreach (ctlist_item , ctlist )
1782+ {
1783+ ct = (CatCTup * ) lfirst (ctlist_item );
1784+ Assert (ct -> c_list == NULL );
1785+ Assert (ct -> refcount > 0 );
1786+ ct -> refcount -- ;
1787+ }
1788+ /* Reset ctlist in preparation for new try */
1789+ ctlist = NIL ;
1790+ stale = true;
1791+ break ;
1792+ }
17431793 }
17441794
17451795 /* Careful here: add entry to ctlist, then bump its refcount */
@@ -1749,6 +1799,7 @@ SearchCatCacheList(CatCache *cache,
17491799 }
17501800
17511801 systable_endscan (scandesc );
1802+ } while (stale );
17521803
17531804 table_close (relation , AccessShareLock );
17541805
@@ -1865,22 +1916,42 @@ ReleaseCatCacheListWithOwner(CatCList *list, ResourceOwner resowner)
18651916 * CatalogCacheCreateEntry
18661917 * Create a new CatCTup entry, copying the given HeapTuple and other
18671918 * supplied data into it. The new entry initially has refcount 0.
1919+ *
1920+ * To create a normal cache entry, ntp must be the HeapTuple just fetched
1921+ * from scandesc, and "arguments" is not used. To create a negative cache
1922+ * entry, pass NULL for ntp and scandesc; then "arguments" is the cache
1923+ * keys to use. In either case, hashValue/hashIndex are the hash values
1924+ * computed from the cache keys.
1925+ *
1926+ * Returns NULL if we attempt to detoast the tuple and observe that it
1927+ * became stale. (This cannot happen for a negative entry.) Caller must
1928+ * retry the tuple lookup in that case.
18681929 */
18691930static CatCTup *
1870- CatalogCacheCreateEntry (CatCache * cache , HeapTuple ntp , Datum * arguments ,
1871- uint32 hashValue , Index hashIndex ,
1872- bool negative )
1931+ CatalogCacheCreateEntry (CatCache * cache , HeapTuple ntp , SysScanDesc scandesc ,
1932+ Datum * arguments ,
1933+ uint32 hashValue , Index hashIndex )
18731934{
18741935 CatCTup * ct ;
18751936 HeapTuple dtp ;
18761937 MemoryContext oldcxt ;
18771938
1878- /* negative entries have no tuple associated */
18791939 if (ntp )
18801940 {
18811941 int i ;
18821942
1883- Assert (!negative );
1943+ /*
1944+ * The visibility recheck below essentially never fails during our
1945+ * regression tests, and there's no easy way to force it to fail for
1946+ * testing purposes. To ensure we have test coverage for the retry
1947+ * paths in our callers, make debug builds randomly fail about 0.1% of
1948+ * the times through this code path, even when there's no toasted
1949+ * fields.
1950+ */
1951+ #ifdef USE_ASSERT_CHECKING
1952+ if (pg_prng_uint32 (& pg_global_prng_state ) <= (PG_UINT32_MAX / 1000 ))
1953+ return NULL ;
1954+ #endif
18841955
18851956 /*
18861957 * If there are any out-of-line toasted fields in the tuple, expand
@@ -1890,7 +1961,20 @@ CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp, Datum *arguments,
18901961 * something using a slightly stale catcache entry.
18911962 */
18921963 if (HeapTupleHasExternal (ntp ))
1964+ {
18931965 dtp = toast_flatten_tuple (ntp , cache -> cc_tupdesc );
1966+
1967+ /*
1968+ * The tuple could become stale while we are doing toast table
1969+ * access (since AcceptInvalidationMessages can run then), so we
1970+ * must recheck its visibility afterwards.
1971+ */
1972+ if (!systable_recheck_tuple (scandesc , ntp ))
1973+ {
1974+ heap_freetuple (dtp );
1975+ return NULL ;
1976+ }
1977+ }
18941978 else
18951979 dtp = ntp ;
18961980
@@ -1929,7 +2013,7 @@ CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp, Datum *arguments,
19292013 }
19302014 else
19312015 {
1932- Assert ( negative );
2016+ /* Set up keys for a negative cache entry */
19332017 oldcxt = MemoryContextSwitchTo (CacheMemoryContext );
19342018 ct = (CatCTup * ) palloc (sizeof (CatCTup ));
19352019
@@ -1951,7 +2035,7 @@ CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp, Datum *arguments,
19512035 ct -> c_list = NULL ;
19522036 ct -> refcount = 0 ; /* for the moment */
19532037 ct -> dead = false;
1954- ct -> negative = negative ;
2038+ ct -> negative = ( ntp == NULL ) ;
19552039 ct -> hash_value = hashValue ;
19562040
19572041 dlist_push_head (& cache -> cc_bucket [hashIndex ], & ct -> cache_elem );
0 commit comments