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
@@ -90,10 +91,10 @@ static void CatCachePrintStats(int code, Datum arg);
90
91
static void CatCacheRemoveCTup (CatCache * cache ,CatCTup * ct );
91
92
static void CatCacheRemoveCList (CatCache * cache ,CatCList * cl );
92
93
static void CatalogCacheInitializeCache (CatCache * cache );
93
- static CatCTup * CatalogCacheCreateEntry (CatCache * cache ,HeapTuple ntp ,
94
+ static CatCTup * CatalogCacheCreateEntry (CatCache * cache ,
95
+ HeapTuple ntp ,SysScanDesc scandesc ,
94
96
Datum * arguments ,
95
- uint32 hashValue ,Index hashIndex ,
96
- bool negative );
97
+ uint32 hashValue ,Index hashIndex );
97
98
98
99
static void ReleaseCatCacheWithOwner (HeapTuple tuple ,ResourceOwner resowner );
99
100
static void ReleaseCatCacheListWithOwner (CatCList * list ,ResourceOwner resowner );
@@ -1372,6 +1373,7 @@ SearchCatCacheMiss(CatCache *cache,
1372
1373
SysScanDesc scandesc ;
1373
1374
HeapTuple ntp ;
1374
1375
CatCTup * ct ;
1376
+ bool stale ;
1375
1377
Datum arguments [CATCACHE_MAXKEYS ];
1376
1378
1377
1379
/* Initialize local parameter array */
@@ -1380,16 +1382,6 @@ SearchCatCacheMiss(CatCache *cache,
1380
1382
arguments [2 ]= v3 ;
1381
1383
arguments [3 ]= v4 ;
1382
1384
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
-
1393
1385
/*
1394
1386
* Tuple was not found in cache, so we have to try to retrieve it directly
1395
1387
* from the relation. If found, we will add it to the cache; if not
@@ -1404,9 +1396,28 @@ SearchCatCacheMiss(CatCache *cache,
1404
1396
* will eventually age out of the cache, so there's no functional problem.
1405
1397
* This case is rare enough that it's not worth expending extra cycles to
1406
1398
* 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.
1407
1405
*/
1408
1406
relation = table_open (cache -> cc_reloid ,AccessShareLock );
1409
1407
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
+
1410
1421
scandesc = systable_beginscan (relation ,
1411
1422
cache -> cc_indexoid ,
1412
1423
IndexScanOK (cache ,cur_skey ),
@@ -1415,12 +1426,18 @@ SearchCatCacheMiss(CatCache *cache,
1415
1426
cur_skey );
1416
1427
1417
1428
ct = NULL ;
1429
+ stale = false;
1418
1430
1419
1431
while (HeapTupleIsValid (ntp = systable_getnext (scandesc )))
1420
1432
{
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
+ }
1424
1441
/* immediately set the refcount to 1 */
1425
1442
ResourceOwnerEnlarge (CurrentResourceOwner );
1426
1443
ct -> refcount ++ ;
@@ -1429,6 +1446,7 @@ SearchCatCacheMiss(CatCache *cache,
1429
1446
}
1430
1447
1431
1448
systable_endscan (scandesc );
1449
+ }while (stale );
1432
1450
1433
1451
table_close (relation ,AccessShareLock );
1434
1452
@@ -1447,9 +1465,11 @@ SearchCatCacheMiss(CatCache *cache,
1447
1465
if (IsBootstrapProcessingMode ())
1448
1466
return NULL ;
1449
1467
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 );
1453
1473
1454
1474
CACHE_elog (DEBUG2 ,"SearchCatCache(%s): Contains %d/%d tuples" ,
1455
1475
cache -> cc_relname ,cache -> cc_ntup ,CacheHdr -> ch_ntup );
@@ -1663,7 +1683,8 @@ SearchCatCacheList(CatCache *cache,
1663
1683
* We have to bump the member refcounts temporarily to ensure they won't
1664
1684
* get dropped from the cache while loading other members. We use a PG_TRY
1665
1685
* 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.
1667
1688
*/
1668
1689
ctlist = NIL ;
1669
1690
@@ -1672,19 +1693,23 @@ SearchCatCacheList(CatCache *cache,
1672
1693
ScanKeyData cur_skey [CATCACHE_MAXKEYS ];
1673
1694
Relation relation ;
1674
1695
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 ;
1685
1697
1686
1698
relation = table_open (cache -> cc_reloid ,AccessShareLock );
1687
1699
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
+
1688
1713
scandesc = systable_beginscan (relation ,
1689
1714
cache -> cc_indexoid ,
1690
1715
IndexScanOK (cache ,cur_skey ),
@@ -1695,6 +1720,8 @@ SearchCatCacheList(CatCache *cache,
1695
1720
/* The list will be ordered iff we are doing an index scan */
1696
1721
ordered = (scandesc -> irel != NULL );
1697
1722
1723
+ stale = false;
1724
+
1698
1725
while (HeapTupleIsValid (ntp = systable_getnext (scandesc )))
1699
1726
{
1700
1727
uint32 hashValue ;
@@ -1737,9 +1764,32 @@ SearchCatCacheList(CatCache *cache,
1737
1764
if (!found )
1738
1765
{
1739
1766
/* 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
+ }
1743
1793
}
1744
1794
1745
1795
/* Careful here: add entry to ctlist, then bump its refcount */
@@ -1749,6 +1799,7 @@ SearchCatCacheList(CatCache *cache,
1749
1799
}
1750
1800
1751
1801
systable_endscan (scandesc );
1802
+ }while (stale );
1752
1803
1753
1804
table_close (relation ,AccessShareLock );
1754
1805
@@ -1865,22 +1916,42 @@ ReleaseCatCacheListWithOwner(CatCList *list, ResourceOwner resowner)
1865
1916
* CatalogCacheCreateEntry
1866
1917
*Create a new CatCTup entry, copying the given HeapTuple and other
1867
1918
*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.
1868
1929
*/
1869
1930
static 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 )
1873
1934
{
1874
1935
CatCTup * ct ;
1875
1936
HeapTuple dtp ;
1876
1937
MemoryContext oldcxt ;
1877
1938
1878
- /* negative entries have no tuple associated */
1879
1939
if (ntp )
1880
1940
{
1881
1941
int i ;
1882
1942
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
1884
1955
1885
1956
/*
1886
1957
* If there are any out-of-line toasted fields in the tuple, expand
@@ -1890,7 +1961,20 @@ CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp, Datum *arguments,
1890
1961
* something using a slightly stale catcache entry.
1891
1962
*/
1892
1963
if (HeapTupleHasExternal (ntp ))
1964
+ {
1893
1965
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
+ }
1894
1978
else
1895
1979
dtp = ntp ;
1896
1980
@@ -1929,7 +2013,7 @@ CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp, Datum *arguments,
1929
2013
}
1930
2014
else
1931
2015
{
1932
- Assert ( negative );
2016
+ /* Set up keys for a negative cache entry */
1933
2017
oldcxt = MemoryContextSwitchTo (CacheMemoryContext );
1934
2018
ct = (CatCTup * )palloc (sizeof (CatCTup ));
1935
2019
@@ -1951,7 +2035,7 @@ CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp, Datum *arguments,
1951
2035
ct -> c_list = NULL ;
1952
2036
ct -> refcount = 0 ;/* for the moment */
1953
2037
ct -> dead = false;
1954
- ct -> negative = negative ;
2038
+ ct -> negative = ( ntp == NULL ) ;
1955
2039
ct -> hash_value = hashValue ;
1956
2040
1957
2041
dlist_push_head (& cache -> cc_bucket [hashIndex ],& ct -> cache_elem );