Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 8de3e41

Browse files
committed
In RelationClearRelation, postpone cache reload if !IsTransactionState().
We may process relcache flush requests during transaction startup or shutdown. In general it's not terribly safe to do catalog access at those times, so the code's habit of trying to immediately revalidate unflushable relcache entries is risky. Although there are no field trouble reports that are positively traceable to this, we have been able to demonstrate failure of the assertions recently added in RelationIdGetRelation() and SearchCatCache(). On the other hand, it seems safe to just postpone revalidation of the cache entry until we're inside a valid transaction. The one case where this is questionable is where we're exiting a subtransaction and the outer transaction is holding the relcache entry open --- but if we made any significant changes to the rel inside such a subtransaction, we've got problems anyway. There are mechanisms in place to prevent that (to wit, locks for cross-session cases and CheckTableNotInUse() for intra-session cases), so let's trust to those mechanisms to keep us out of trouble.
1 parent 45e1b6c commit 8de3e41

File tree

3 files changed

+69
-10
lines changed

3 files changed

+69
-10
lines changed

src/backend/utils/cache/relcache.c

+36-10
Original file line numberDiff line numberDiff line change
@@ -1601,6 +1601,7 @@ RelationIdGetRelation(Oid relationId)
16011601
RelationReloadIndexInfo(rd);
16021602
else
16031603
RelationClearRelation(rd, true);
1604+
Assert(rd->rd_isvalid);
16041605
}
16051606
return rd;
16061607
}
@@ -1712,8 +1713,9 @@ RelationReloadIndexInfo(Relation relation)
17121713
/* Should be called only for invalidated indexes */
17131714
Assert(relation->rd_rel->relkind == RELKIND_INDEX &&
17141715
!relation->rd_isvalid);
1715-
/* Should be closed at smgr level */
1716-
Assert(relation->rd_smgr == NULL);
1716+
1717+
/* Ensure it's closed at smgr level */
1718+
RelationCloseSmgr(relation);
17171719

17181720
/* Must free any AM cached data upon relcache flush */
17191721
if (relation->rd_amcache)
@@ -1892,12 +1894,11 @@ RelationClearRelation(Relation relation, bool rebuild)
18921894
* be unable to recover. However, we must redo RelationInitPhysicalAddr
18931895
* in case it is a mapped relation whose mapping changed.
18941896
*
1895-
* If it's a nailed index, then we need to re-read the pg_class row to see
1896-
* if its relfilenode changed. We can't necessarily do that here, because
1897-
* we might be in a failed transaction. We assume it's okay to do it if
1898-
* there are open references to the relcache entry (cf notes for
1899-
* AtEOXact_RelationCache). Otherwise just mark the entry as possibly
1900-
* invalid, and it'll be fixed when next opened.
1897+
* If it's a nailed-but-not-mapped index, then we need to re-read the
1898+
* pg_class row to see if its relfilenode changed. We do that immediately
1899+
* if we're inside a valid transaction and the relation is open (not
1900+
* counting the nailed refcount). Otherwise just mark the entry as
1901+
* possibly invalid, and it'll be fixed when next opened.
19011902
*/
19021903
if (relation->rd_isnailed)
19031904
{
@@ -1906,7 +1907,7 @@ RelationClearRelation(Relation relation, bool rebuild)
19061907
if (relation->rd_rel->relkind == RELKIND_INDEX)
19071908
{
19081909
relation->rd_isvalid = false; /* needs to be revalidated */
1909-
if (relation->rd_refcnt > 1)
1910+
if (relation->rd_refcnt > 1 && IsTransactionState())
19101911
RelationReloadIndexInfo(relation);
19111912
}
19121913
return;
@@ -1924,7 +1925,8 @@ RelationClearRelation(Relation relation, bool rebuild)
19241925
relation->rd_indexcxt != NULL)
19251926
{
19261927
relation->rd_isvalid = false; /* needs to be revalidated */
1927-
RelationReloadIndexInfo(relation);
1928+
if (IsTransactionState())
1929+
RelationReloadIndexInfo(relation);
19281930
return;
19291931
}
19301932

@@ -1945,6 +1947,29 @@ RelationClearRelation(Relation relation, bool rebuild)
19451947
/* And release storage */
19461948
RelationDestroyRelation(relation);
19471949
}
1950+
else if (!IsTransactionState())
1951+
{
1952+
/*
1953+
* If we're not inside a valid transaction, we can't do any catalog
1954+
* access so it's not possible to rebuild yet. Just exit, leaving
1955+
* rd_isvalid = false so that the rebuild will occur when the entry is
1956+
* next opened.
1957+
*
1958+
* Note: it's possible that we come here during subtransaction abort,
1959+
* and the reason for wanting to rebuild is that the rel is open in
1960+
* the outer transaction. In that case it might seem unsafe to not
1961+
* rebuild immediately, since whatever code has the rel already open
1962+
* will keep on using the relcache entry as-is. However, in such a
1963+
* case the outer transaction should be holding a lock that's
1964+
* sufficient to prevent any significant change in the rel's schema,
1965+
* so the existing entry contents should be good enough for its
1966+
* purposes; at worst we might be behind on statistics updates or the
1967+
* like. (See also CheckTableNotInUse() and its callers.) These same
1968+
* remarks also apply to the cases above where we exit without having
1969+
* done RelationReloadIndexInfo() yet.
1970+
*/
1971+
return;
1972+
}
19481973
else
19491974
{
19501975
/*
@@ -2057,6 +2082,7 @@ RelationClearRelation(Relation relation, bool rebuild)
20572082
* RelationFlushRelation
20582083
*
20592084
* Rebuild the relation if it is open (refcount > 0), else blow it away.
2085+
* This is used when we receive a cache invalidation event for the rel.
20602086
*/
20612087
static void
20622088
RelationFlushRelation(Relation relation)

src/test/regress/expected/transactions.out

+14
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,20 @@ ROLLBACK;
552552
DROP TABLE foo;
553553
DROP TABLE baz;
554554
DROP TABLE barbaz;
555+
-- test case for problems with revalidating an open relation during abort
556+
create function inverse(int) returns float8 as
557+
$$
558+
begin
559+
analyze revalidate_bug;
560+
return 1::float8/$1;
561+
exception
562+
when division_by_zero then return 0;
563+
end$$ language plpgsql volatile;
564+
create table revalidate_bug (c float8 unique);
565+
insert into revalidate_bug values (1);
566+
insert into revalidate_bug values (inverse(0));
567+
drop table revalidate_bug;
568+
drop function inverse(int);
555569
-- verify that cursors created during an aborted subtransaction are
556570
-- closed, but that we do not rollback the effect of any FETCHs
557571
-- performed in the aborted subtransaction

src/test/regress/sql/transactions.sql

+19
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,25 @@ DROP TABLE foo;
333333
DROP TABLE baz;
334334
DROP TABLE barbaz;
335335

336+
337+
-- test case for problems with revalidating an open relation during abort
338+
create function inverse(int) returns float8 as
339+
$$
340+
begin
341+
analyze revalidate_bug;
342+
return 1::float8/$1;
343+
exception
344+
when division_by_zero then return 0;
345+
end$$ language plpgsql volatile;
346+
347+
create table revalidate_bug (c float8 unique);
348+
insert into revalidate_bug values (1);
349+
insert into revalidate_bug values (inverse(0));
350+
351+
drop table revalidate_bug;
352+
drop function inverse(int);
353+
354+
336355
-- verify that cursors created during an aborted subtransaction are
337356
-- closed, but that we do not rollback the effect of any FETCHs
338357
-- performed in the aborted subtransaction

0 commit comments

Comments
 (0)