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