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