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