Avoid using a fake relcache entry to own an SmgrRelation.

If an error occurs before we close the fake relcache entry, the the
fake relcache entry will be destroyed by the SmgrRelation will
survive until end of transaction. Its smgr_owner pointer ends up
pointing to already-freed memory.

The original reason for using a fake relcache entry here was to try
to avoid reusing an SMgrRelation across a relevant invalidation. To
avoid that problem, just call smgropen() again each time we need a
reference to it. Hopefully someday we will come up with a more
elegant approach, but accessing uninitialized memory is bad so let's
do this for now.

Dilip Kumar, reviewed by Andres Freund and Tom Lane. Report by
Justin Pryzby.

Discussion: http://postgr.es/m/20220802175043.GA13682@telsasoft.com
Discussion: http://postgr.es/m/CAFiTN-vSFeE6_W9z698XNtFROOA_nSqUXWqLcG0emob_kJ+dEQ@mail.gmail.com
This commit is contained in:
Robert Haas 2022-08-12 08:55:07 -04:00
parent d6d1fbf353
commit 1b94f8f232
2 changed files with 28 additions and 38 deletions

View File

@ -257,8 +257,8 @@ ScanSourceDatabasePgClass(Oid tbid, Oid dbid, char *srcpath)
Page page;
List *rnodelist = NIL;
LockRelId relid;
Relation rel;
Snapshot snapshot;
SMgrRelation smgr;
BufferAccessStrategy bstrategy;
/* Get pg_class relfilenode. */
@ -275,16 +275,9 @@ ScanSourceDatabasePgClass(Oid tbid, Oid dbid, char *srcpath)
rnode.dbNode = dbid;
rnode.relNode = relfilenode;
/*
* We can't use a real relcache entry for a relation in some other
* database, but since we're only going to access the fields related to
* physical storage, a fake one is good enough. If we didn't do this and
* used the smgr layer directly, we would have to worry about
* invalidations.
*/
rel = CreateFakeRelcacheEntry(rnode);
nblocks = smgrnblocks(RelationGetSmgr(rel), MAIN_FORKNUM);
FreeFakeRelcacheEntry(rel);
smgr = smgropen(rnode, InvalidBackendId);
nblocks = smgrnblocks(smgr, MAIN_FORKNUM);
smgrclose(smgr);
/* Use a buffer access strategy since this is a bulk read operation. */
bstrategy = GetAccessStrategy(BAS_BULKREAD);

View File

@ -487,9 +487,9 @@ static void FindAndDropRelFileNodeBuffers(RelFileNode rnode,
ForkNumber forkNum,
BlockNumber nForkBlock,
BlockNumber firstDelBlock);
static void RelationCopyStorageUsingBuffer(Relation src, Relation dst,
ForkNumber forkNum,
bool isunlogged);
static void RelationCopyStorageUsingBuffer(RelFileNode srcnode,
RelFileNode dstnode,
ForkNumber forkNum, bool permanent);
static void AtProcExit_Buffers(int code, Datum arg);
static void CheckForBufferLeaks(void);
static int rnode_comparator(const void *p1, const void *p2);
@ -3702,8 +3702,9 @@ FlushRelationsAllBuffers(SMgrRelation *smgrs, int nrels)
* --------------------------------------------------------------------
*/
static void
RelationCopyStorageUsingBuffer(Relation src, Relation dst, ForkNumber forkNum,
bool permanent)
RelationCopyStorageUsingBuffer(RelFileNode srcnode,
RelFileNode dstnode,
ForkNumber forkNum, bool permanent)
{
Buffer srcBuf;
Buffer dstBuf;
@ -3723,7 +3724,8 @@ RelationCopyStorageUsingBuffer(Relation src, Relation dst, ForkNumber forkNum,
use_wal = XLogIsNeeded() && (permanent || forkNum == INIT_FORKNUM);
/* Get number of blocks in the source relation. */
nblocks = smgrnblocks(RelationGetSmgr(src), forkNum);
nblocks = smgrnblocks(smgropen(srcnode, InvalidBackendId),
forkNum);
/* Nothing to copy; just return. */
if (nblocks == 0)
@ -3739,14 +3741,14 @@ RelationCopyStorageUsingBuffer(Relation src, Relation dst, ForkNumber forkNum,
CHECK_FOR_INTERRUPTS();
/* Read block from source relation. */
srcBuf = ReadBufferWithoutRelcache(src->rd_node, forkNum, blkno,
srcBuf = ReadBufferWithoutRelcache(srcnode, forkNum, blkno,
RBM_NORMAL, bstrategy_src,
permanent);
LockBuffer(srcBuf, BUFFER_LOCK_SHARE);
srcPage = BufferGetPage(srcBuf);
/* Use P_NEW to extend the destination relation. */
dstBuf = ReadBufferWithoutRelcache(dst->rd_node, forkNum, P_NEW,
dstBuf = ReadBufferWithoutRelcache(dstnode, forkNum, P_NEW,
RBM_NORMAL, bstrategy_dst,
permanent);
LockBuffer(dstBuf, BUFFER_LOCK_EXCLUSIVE);
@ -3784,24 +3786,13 @@ void
CreateAndCopyRelationData(RelFileNode src_rnode, RelFileNode dst_rnode,
bool permanent)
{
Relation src_rel;
Relation dst_rel;
RelFileNodeBackend rnode;
char relpersistence;
/* Set the relpersistence. */
relpersistence = permanent ?
RELPERSISTENCE_PERMANENT : RELPERSISTENCE_UNLOGGED;
/*
* We can't use a real relcache entry for a relation in some other
* database, but since we're only going to access the fields related to
* physical storage, a fake one is good enough. If we didn't do this and
* used the smgr layer directly, we would have to worry about
* invalidations.
*/
src_rel = CreateFakeRelcacheEntry(src_rnode);
dst_rel = CreateFakeRelcacheEntry(dst_rnode);
/*
* Create and copy all forks of the relation. During create database we
* have a separate cleanup mechanism which deletes complete database
@ -3811,15 +3802,16 @@ CreateAndCopyRelationData(RelFileNode src_rnode, RelFileNode dst_rnode,
RelationCreateStorage(dst_rnode, relpersistence, false);
/* copy main fork. */
RelationCopyStorageUsingBuffer(src_rel, dst_rel, MAIN_FORKNUM, permanent);
RelationCopyStorageUsingBuffer(src_rnode, dst_rnode, MAIN_FORKNUM,
permanent);
/* copy those extra forks that exist */
for (ForkNumber forkNum = MAIN_FORKNUM + 1;
forkNum <= MAX_FORKNUM; forkNum++)
{
if (smgrexists(RelationGetSmgr(src_rel), forkNum))
if (smgrexists(smgropen(src_rnode, InvalidBackendId), forkNum))
{
smgrcreate(RelationGetSmgr(dst_rel), forkNum, false);
smgrcreate(smgropen(dst_rnode, InvalidBackendId), forkNum, false);
/*
* WAL log creation if the relation is persistent, or this is the
@ -3829,14 +3821,19 @@ CreateAndCopyRelationData(RelFileNode src_rnode, RelFileNode dst_rnode,
log_smgrcreate(&dst_rnode, forkNum);
/* Copy a fork's data, block by block. */
RelationCopyStorageUsingBuffer(src_rel, dst_rel, forkNum,
RelationCopyStorageUsingBuffer(src_rnode, dst_rnode, forkNum,
permanent);
}
}
/* Release fake relcache entries. */
FreeFakeRelcacheEntry(src_rel);
FreeFakeRelcacheEntry(dst_rel);
/* close source and destination smgr if exists. */
rnode.backend = InvalidBackendId;
rnode.node = src_rnode;
smgrclosenode(rnode);
rnode.node = dst_rnode;
smgrclosenode(rnode);
}
/* ---------------------------------------------------------------------