mirror of
https://github.com/postgres/postgres.git
synced 2025-06-16 06:01:02 +03:00
Assert that a snapshot is active or registered before it's used
The comment in GetTransactionSnapshot() said that you "should call RegisterSnapshot or PushActiveSnapshot on the returned snap if it is to be used very long". That felt too unclear to me. Make the comment more strongly worded. To enforce that rule and to catch potential bugs where a snapshot might get invalidated while it's still in use, add an assertion to HeapTupleSatisfiesMVCC() to check that the snapshot is registered or pushed to active stack. No new bugs were found by this, but it seems like good future-proofing. It's not a great place for the check; HeapTupleSatisfiesMVCC() is in fact safe to call with an unregistered snapshot, and the assertion won't catch other unsafe uses. But it goes a long way in practice. Fix a few cases that were playing fast and loose with that and just assumed that the snapshot cannot be invalidated during a scan. Those assumptions were not wrong, but they're not performance critical, so let's drop the excuses and just register the snapshot. These were false positives found by the new assertion. Discussion: https://www.postgresql.org/message-id/7c56f180-b9e1-481e-8c1d-efa63de3ecbb@iki.fi
This commit is contained in:
@ -962,6 +962,15 @@ HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
|
|||||||
{
|
{
|
||||||
HeapTupleHeader tuple = htup->t_data;
|
HeapTupleHeader tuple = htup->t_data;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Assert that the caller has registered the snapshot. This function
|
||||||
|
* doesn't care about the registration as such, but in general you
|
||||||
|
* shouldn't try to use a snapshot without registration because it might
|
||||||
|
* get invalidated while it's still in use, and this is a convenient place
|
||||||
|
* to check for that.
|
||||||
|
*/
|
||||||
|
Assert(snapshot->regd_count > 0 || snapshot->active_count > 0);
|
||||||
|
|
||||||
Assert(ItemPointerIsValid(&htup->t_self));
|
Assert(ItemPointerIsValid(&htup->t_self));
|
||||||
Assert(htup->t_tableOid != InvalidOid);
|
Assert(htup->t_tableOid != InvalidOid);
|
||||||
|
|
||||||
|
@ -577,17 +577,13 @@ systable_recheck_tuple(SysScanDesc sysscan, HeapTuple tup)
|
|||||||
|
|
||||||
Assert(tup == ExecFetchSlotHeapTuple(sysscan->slot, false, NULL));
|
Assert(tup == ExecFetchSlotHeapTuple(sysscan->slot, false, NULL));
|
||||||
|
|
||||||
/*
|
|
||||||
* Trust that table_tuple_satisfies_snapshot() and its subsidiaries
|
|
||||||
* (commonly LockBuffer() and HeapTupleSatisfiesMVCC()) do not themselves
|
|
||||||
* acquire snapshots, so we need not register the snapshot. Those
|
|
||||||
* facilities are too low-level to have any business scanning tables.
|
|
||||||
*/
|
|
||||||
freshsnap = GetCatalogSnapshot(RelationGetRelid(sysscan->heap_rel));
|
freshsnap = GetCatalogSnapshot(RelationGetRelid(sysscan->heap_rel));
|
||||||
|
freshsnap = RegisterSnapshot(freshsnap);
|
||||||
|
|
||||||
result = table_tuple_satisfies_snapshot(sysscan->heap_rel,
|
result = table_tuple_satisfies_snapshot(sysscan->heap_rel,
|
||||||
sysscan->slot,
|
sysscan->slot,
|
||||||
freshsnap);
|
freshsnap);
|
||||||
|
UnregisterSnapshot(freshsnap);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Handle the concurrent abort while fetching the catalog tuple during
|
* Handle the concurrent abort while fetching the catalog tuple during
|
||||||
|
@ -288,7 +288,7 @@ ScanSourceDatabasePgClass(Oid tbid, Oid dbid, char *srcpath)
|
|||||||
* snapshot - or the active snapshot - might not be new enough for that,
|
* snapshot - or the active snapshot - might not be new enough for that,
|
||||||
* but the return value of GetLatestSnapshot() should work fine.
|
* but the return value of GetLatestSnapshot() should work fine.
|
||||||
*/
|
*/
|
||||||
snapshot = GetLatestSnapshot();
|
snapshot = RegisterSnapshot(GetLatestSnapshot());
|
||||||
|
|
||||||
/* Process the relation block by block. */
|
/* Process the relation block by block. */
|
||||||
for (blkno = 0; blkno < nblocks; blkno++)
|
for (blkno = 0; blkno < nblocks; blkno++)
|
||||||
@ -313,6 +313,7 @@ ScanSourceDatabasePgClass(Oid tbid, Oid dbid, char *srcpath)
|
|||||||
|
|
||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
}
|
}
|
||||||
|
UnregisterSnapshot(snapshot);
|
||||||
|
|
||||||
/* Release relation lock. */
|
/* Release relation lock. */
|
||||||
UnlockRelationId(&relid, AccessShareLock);
|
UnlockRelationId(&relid, AccessShareLock);
|
||||||
|
15
src/backend/utils/cache/relcache.c
vendored
15
src/backend/utils/cache/relcache.c
vendored
@ -371,14 +371,13 @@ ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_historic)
|
|||||||
pg_class_desc = table_open(RelationRelationId, AccessShareLock);
|
pg_class_desc = table_open(RelationRelationId, AccessShareLock);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The caller might need a tuple that's newer than the one the historic
|
* The caller might need a tuple that's newer than what's visible to the
|
||||||
* snapshot; currently the only case requiring to do so is looking up the
|
* historic snapshot; currently the only case requiring to do so is
|
||||||
* relfilenumber of non mapped system relations during decoding. That
|
* looking up the relfilenumber of non mapped system relations during
|
||||||
* snapshot can't change in the midst of a relcache build, so there's no
|
* decoding.
|
||||||
* need to register the snapshot.
|
|
||||||
*/
|
*/
|
||||||
if (force_non_historic)
|
if (force_non_historic)
|
||||||
snapshot = GetNonHistoricCatalogSnapshot(RelationRelationId);
|
snapshot = RegisterSnapshot(GetNonHistoricCatalogSnapshot(RelationRelationId));
|
||||||
|
|
||||||
pg_class_scan = systable_beginscan(pg_class_desc, ClassOidIndexId,
|
pg_class_scan = systable_beginscan(pg_class_desc, ClassOidIndexId,
|
||||||
indexOK && criticalRelcachesBuilt,
|
indexOK && criticalRelcachesBuilt,
|
||||||
@ -395,6 +394,10 @@ ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_historic)
|
|||||||
|
|
||||||
/* all done */
|
/* all done */
|
||||||
systable_endscan(pg_class_scan);
|
systable_endscan(pg_class_scan);
|
||||||
|
|
||||||
|
if (snapshot)
|
||||||
|
UnregisterSnapshot(snapshot);
|
||||||
|
|
||||||
table_close(pg_class_desc, AccessShareLock);
|
table_close(pg_class_desc, AccessShareLock);
|
||||||
|
|
||||||
return pg_class_tuple;
|
return pg_class_tuple;
|
||||||
|
@ -203,10 +203,10 @@ typedef struct SerializedSnapshotData
|
|||||||
* GetTransactionSnapshot
|
* GetTransactionSnapshot
|
||||||
* Get the appropriate snapshot for a new query in a transaction.
|
* Get the appropriate snapshot for a new query in a transaction.
|
||||||
*
|
*
|
||||||
* Note that the return value may point at static storage that will be modified
|
* Note that the return value points at static storage that will be modified
|
||||||
* by future calls and by CommandCounterIncrement(). Callers should call
|
* by future calls and by CommandCounterIncrement(). Callers must call
|
||||||
* RegisterSnapshot or PushActiveSnapshot on the returned snap if it is to be
|
* RegisterSnapshot or PushActiveSnapshot on the returned snap before doing
|
||||||
* used very long.
|
* any other non-trivial work that could invalidate it.
|
||||||
*/
|
*/
|
||||||
Snapshot
|
Snapshot
|
||||||
GetTransactionSnapshot(void)
|
GetTransactionSnapshot(void)
|
||||||
|
Reference in New Issue
Block a user