diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-16 19:46:48 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-16 19:46:48 +0000 |
commit | 311bcfc6b3acdd6fd152798c7f287ddf74fa2a98 (patch) | |
tree | 0ec307299b1dada3701e42f4ca6eda57d708261e /src/backend/catalog/storage.c | |
parent | Initial commit. (diff) | |
download | postgresql-15-upstream.tar.xz postgresql-15-upstream.zip |
Adding upstream version 15.4.upstream/15.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/backend/catalog/storage.c')
-rw-r--r-- | src/backend/catalog/storage.c | 1063 |
1 files changed, 1063 insertions, 0 deletions
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c new file mode 100644 index 0000000..c06e414 --- /dev/null +++ b/src/backend/catalog/storage.c @@ -0,0 +1,1063 @@ +/*------------------------------------------------------------------------- + * + * storage.c + * code to create and destroy physical storage for relations + * + * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/catalog/storage.c + * + * NOTES + * Some of this code used to be in storage/smgr/smgr.c, and the + * function names still reflect that. + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/parallel.h" +#include "access/visibilitymap.h" +#include "access/xact.h" +#include "access/xlog.h" +#include "access/xloginsert.h" +#include "access/xlogutils.h" +#include "catalog/storage.h" +#include "catalog/storage_xlog.h" +#include "miscadmin.h" +#include "storage/freespace.h" +#include "storage/smgr.h" +#include "utils/hsearch.h" +#include "utils/memutils.h" +#include "utils/rel.h" + +/* GUC variables */ +int wal_skip_threshold = 2048; /* in kilobytes */ + +/* + * We keep a list of all relations (represented as RelFileNode values) + * that have been created or deleted in the current transaction. When + * a relation is created, we create the physical file immediately, but + * remember it so that we can delete the file again if the current + * transaction is aborted. Conversely, a deletion request is NOT + * executed immediately, but is just entered in the list. When and if + * the transaction commits, we can delete the physical file. + * + * To handle subtransactions, every entry is marked with its transaction + * nesting level. At subtransaction commit, we reassign the subtransaction's + * entries to the parent nesting level. At subtransaction abort, we can + * immediately execute the abort-time actions for all entries of the current + * nesting level. + * + * NOTE: the list is kept in TopMemoryContext to be sure it won't disappear + * unbetimes. It'd probably be OK to keep it in TopTransactionContext, + * but I'm being paranoid. + */ + +typedef struct PendingRelDelete +{ + RelFileNode relnode; /* relation that may need to be deleted */ + BackendId backend; /* InvalidBackendId if not a temp rel */ + bool atCommit; /* T=delete at commit; F=delete at abort */ + int nestLevel; /* xact nesting level of request */ + struct PendingRelDelete *next; /* linked-list link */ +} PendingRelDelete; + +typedef struct PendingRelSync +{ + RelFileNode rnode; + bool is_truncated; /* Has the file experienced truncation? */ +} PendingRelSync; + +static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */ +static HTAB *pendingSyncHash = NULL; + + +/* + * AddPendingSync + * Queue an at-commit fsync. + */ +static void +AddPendingSync(const RelFileNode *rnode) +{ + PendingRelSync *pending; + bool found; + + /* create the hash if not yet */ + if (!pendingSyncHash) + { + HASHCTL ctl; + + ctl.keysize = sizeof(RelFileNode); + ctl.entrysize = sizeof(PendingRelSync); + ctl.hcxt = TopTransactionContext; + pendingSyncHash = hash_create("pending sync hash", 16, &ctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + } + + pending = hash_search(pendingSyncHash, rnode, HASH_ENTER, &found); + Assert(!found); + pending->is_truncated = false; +} + +/* + * RelationCreateStorage + * Create physical storage for a relation. + * + * Create the underlying disk file storage for the relation. This only + * creates the main fork; additional forks are created lazily by the + * modules that need them. + * + * This function is transactional. The creation is WAL-logged, and if the + * transaction aborts later on, the storage will be destroyed. A caller + * that does not want the storage to be destroyed in case of an abort may + * pass register_delete = false. + */ +SMgrRelation +RelationCreateStorage(RelFileNode rnode, char relpersistence, + bool register_delete) +{ + SMgrRelation srel; + BackendId backend; + bool needs_wal; + + Assert(!IsInParallelMode()); /* couldn't update pendingSyncHash */ + + switch (relpersistence) + { + case RELPERSISTENCE_TEMP: + backend = BackendIdForTempRelations(); + needs_wal = false; + break; + case RELPERSISTENCE_UNLOGGED: + backend = InvalidBackendId; + needs_wal = false; + break; + case RELPERSISTENCE_PERMANENT: + backend = InvalidBackendId; + needs_wal = true; + break; + default: + elog(ERROR, "invalid relpersistence: %c", relpersistence); + return NULL; /* placate compiler */ + } + + srel = smgropen(rnode, backend); + smgrcreate(srel, MAIN_FORKNUM, false); + + if (needs_wal) + log_smgrcreate(&srel->smgr_rnode.node, MAIN_FORKNUM); + + /* + * Add the relation to the list of stuff to delete at abort, if we are + * asked to do so. + */ + if (register_delete) + { + PendingRelDelete *pending; + + pending = (PendingRelDelete *) + MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete)); + pending->relnode = rnode; + pending->backend = backend; + pending->atCommit = false; /* delete if abort */ + pending->nestLevel = GetCurrentTransactionNestLevel(); + pending->next = pendingDeletes; + pendingDeletes = pending; + } + + if (relpersistence == RELPERSISTENCE_PERMANENT && !XLogIsNeeded()) + { + Assert(backend == InvalidBackendId); + AddPendingSync(&rnode); + } + + return srel; +} + +/* + * Perform XLogInsert of an XLOG_SMGR_CREATE record to WAL. + */ +void +log_smgrcreate(const RelFileNode *rnode, ForkNumber forkNum) +{ + xl_smgr_create xlrec; + + /* + * Make an XLOG entry reporting the file creation. + */ + xlrec.rnode = *rnode; + xlrec.forkNum = forkNum; + + XLogBeginInsert(); + XLogRegisterData((char *) &xlrec, sizeof(xlrec)); + XLogInsert(RM_SMGR_ID, XLOG_SMGR_CREATE | XLR_SPECIAL_REL_UPDATE); +} + +/* + * RelationDropStorage + * Schedule unlinking of physical storage at transaction commit. + */ +void +RelationDropStorage(Relation rel) +{ + PendingRelDelete *pending; + + /* Add the relation to the list of stuff to delete at commit */ + pending = (PendingRelDelete *) + MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete)); + pending->relnode = rel->rd_node; + pending->backend = rel->rd_backend; + pending->atCommit = true; /* delete if commit */ + pending->nestLevel = GetCurrentTransactionNestLevel(); + pending->next = pendingDeletes; + pendingDeletes = pending; + + /* + * NOTE: if the relation was created in this transaction, it will now be + * present in the pending-delete list twice, once with atCommit true and + * once with atCommit false. Hence, it will be physically deleted at end + * of xact in either case (and the other entry will be ignored by + * smgrDoPendingDeletes, so no error will occur). We could instead remove + * the existing list entry and delete the physical file immediately, but + * for now I'll keep the logic simple. + */ + + RelationCloseSmgr(rel); +} + +/* + * RelationPreserveStorage + * Mark a relation as not to be deleted after all. + * + * We need this function because relation mapping changes are committed + * separately from commit of the whole transaction, so it's still possible + * for the transaction to abort after the mapping update is done. + * When a new physical relation is installed in the map, it would be + * scheduled for delete-on-abort, so we'd delete it, and be in trouble. + * The relation mapper fixes this by telling us to not delete such relations + * after all as part of its commit. + * + * We also use this to reuse an old build of an index during ALTER TABLE, this + * time removing the delete-at-commit entry. + * + * No-op if the relation is not among those scheduled for deletion. + */ +void +RelationPreserveStorage(RelFileNode rnode, bool atCommit) +{ + PendingRelDelete *pending; + PendingRelDelete *prev; + PendingRelDelete *next; + + prev = NULL; + for (pending = pendingDeletes; pending != NULL; pending = next) + { + next = pending->next; + if (RelFileNodeEquals(rnode, pending->relnode) + && pending->atCommit == atCommit) + { + /* unlink and delete list entry */ + if (prev) + prev->next = next; + else + pendingDeletes = next; + pfree(pending); + /* prev does not change */ + } + else + { + /* unrelated entry, don't touch it */ + prev = pending; + } + } +} + +/* + * RelationTruncate + * Physically truncate a relation to the specified number of blocks. + * + * This includes getting rid of any buffers for the blocks that are to be + * dropped. + */ +void +RelationTruncate(Relation rel, BlockNumber nblocks) +{ + bool fsm; + bool vm; + bool need_fsm_vacuum = false; + ForkNumber forks[MAX_FORKNUM]; + BlockNumber blocks[MAX_FORKNUM]; + int nforks = 0; + SMgrRelation reln; + + /* + * Make sure smgr_targblock etc aren't pointing somewhere past new end. + * (Note: don't rely on this reln pointer below this loop.) + */ + reln = RelationGetSmgr(rel); + reln->smgr_targblock = InvalidBlockNumber; + for (int i = 0; i <= MAX_FORKNUM; ++i) + reln->smgr_cached_nblocks[i] = InvalidBlockNumber; + + /* Prepare for truncation of MAIN fork of the relation */ + forks[nforks] = MAIN_FORKNUM; + blocks[nforks] = nblocks; + nforks++; + + /* Prepare for truncation of the FSM if it exists */ + fsm = smgrexists(RelationGetSmgr(rel), FSM_FORKNUM); + if (fsm) + { + blocks[nforks] = FreeSpaceMapPrepareTruncateRel(rel, nblocks); + if (BlockNumberIsValid(blocks[nforks])) + { + forks[nforks] = FSM_FORKNUM; + nforks++; + need_fsm_vacuum = true; + } + } + + /* Prepare for truncation of the visibility map too if it exists */ + vm = smgrexists(RelationGetSmgr(rel), VISIBILITYMAP_FORKNUM); + if (vm) + { + blocks[nforks] = visibilitymap_prepare_truncate(rel, nblocks); + if (BlockNumberIsValid(blocks[nforks])) + { + forks[nforks] = VISIBILITYMAP_FORKNUM; + nforks++; + } + } + + RelationPreTruncate(rel); + + /* + * Make sure that a concurrent checkpoint can't complete while truncation + * is in progress. + * + * The truncation operation might drop buffers that the checkpoint + * otherwise would have flushed. If it does, then it's essential that the + * files actually get truncated on disk before the checkpoint record is + * written. Otherwise, if reply begins from that checkpoint, the + * to-be-truncated blocks might still exist on disk but have older + * contents than expected, which can cause replay to fail. It's OK for the + * blocks to not exist on disk at all, but not for them to have the wrong + * contents. + */ + Assert((MyProc->delayChkptFlags & DELAY_CHKPT_COMPLETE) == 0); + MyProc->delayChkptFlags |= DELAY_CHKPT_COMPLETE; + + /* + * We WAL-log the truncation before actually truncating, which means + * trouble if the truncation fails. If we then crash, the WAL replay + * likely isn't going to succeed in the truncation either, and cause a + * PANIC. It's tempting to put a critical section here, but that cure + * would be worse than the disease. It would turn a usually harmless + * failure to truncate, that might spell trouble at WAL replay, into a + * certain PANIC. + */ + if (RelationNeedsWAL(rel)) + { + /* + * Make an XLOG entry reporting the file truncation. + */ + XLogRecPtr lsn; + xl_smgr_truncate xlrec; + + xlrec.blkno = nblocks; + xlrec.rnode = rel->rd_node; + xlrec.flags = SMGR_TRUNCATE_ALL; + + XLogBeginInsert(); + XLogRegisterData((char *) &xlrec, sizeof(xlrec)); + + lsn = XLogInsert(RM_SMGR_ID, + XLOG_SMGR_TRUNCATE | XLR_SPECIAL_REL_UPDATE); + + /* + * Flush, because otherwise the truncation of the main relation might + * hit the disk before the WAL record, and the truncation of the FSM + * or visibility map. If we crashed during that window, we'd be left + * with a truncated heap, but the FSM or visibility map would still + * contain entries for the non-existent heap pages. + */ + if (fsm || vm) + XLogFlush(lsn); + } + + /* + * This will first remove any buffers from the buffer pool that should no + * longer exist after truncation is complete, and then truncate the + * corresponding files on disk. + */ + smgrtruncate(RelationGetSmgr(rel), forks, nforks, blocks); + + /* We've done all the critical work, so checkpoints are OK now. */ + MyProc->delayChkptFlags &= ~DELAY_CHKPT_COMPLETE; + + /* + * Update upper-level FSM pages to account for the truncation. This is + * important because the just-truncated pages were likely marked as + * all-free, and would be preferentially selected. + * + * NB: There's no point in delaying checkpoints until this is done. + * Because the FSM is not WAL-logged, we have to be prepared for the + * possibility of corruption after a crash anyway. + */ + if (need_fsm_vacuum) + FreeSpaceMapVacuumRange(rel, nblocks, InvalidBlockNumber); +} + +/* + * RelationPreTruncate + * Perform AM-independent work before a physical truncation. + * + * If an access method's relation_nontransactional_truncate does not call + * RelationTruncate(), it must call this before decreasing the table size. + */ +void +RelationPreTruncate(Relation rel) +{ + PendingRelSync *pending; + + if (!pendingSyncHash) + return; + + pending = hash_search(pendingSyncHash, + &(RelationGetSmgr(rel)->smgr_rnode.node), + HASH_FIND, NULL); + if (pending) + pending->is_truncated = true; +} + +/* + * Copy a fork's data, block by block. + * + * Note that this requires that there is no dirty data in shared buffers. If + * it's possible that there are, callers need to flush those using + * e.g. FlushRelationBuffers(rel). + * + * Also note that this is frequently called via locutions such as + * RelationCopyStorage(RelationGetSmgr(rel), ...); + * That's safe only because we perform only smgr and WAL operations here. + * If we invoked anything else, a relcache flush could cause our SMgrRelation + * argument to become a dangling pointer. + */ +void +RelationCopyStorage(SMgrRelation src, SMgrRelation dst, + ForkNumber forkNum, char relpersistence) +{ + PGAlignedBlock buf; + Page page; + bool use_wal; + bool copying_initfork; + BlockNumber nblocks; + BlockNumber blkno; + + page = (Page) buf.data; + + /* + * The init fork for an unlogged relation in many respects has to be + * treated the same as normal relation, changes need to be WAL logged and + * it needs to be synced to disk. + */ + copying_initfork = relpersistence == RELPERSISTENCE_UNLOGGED && + forkNum == INIT_FORKNUM; + + /* + * We need to log the copied data in WAL iff WAL archiving/streaming is + * enabled AND it's a permanent relation. This gives the same answer as + * "RelationNeedsWAL(rel) || copying_initfork", because we know the + * current operation created a new relfilenode. + */ + use_wal = XLogIsNeeded() && + (relpersistence == RELPERSISTENCE_PERMANENT || copying_initfork); + + nblocks = smgrnblocks(src, forkNum); + + for (blkno = 0; blkno < nblocks; blkno++) + { + /* If we got a cancel signal during the copy of the data, quit */ + CHECK_FOR_INTERRUPTS(); + + smgrread(src, forkNum, blkno, buf.data); + + if (!PageIsVerifiedExtended(page, blkno, + PIV_LOG_WARNING | PIV_REPORT_STAT)) + { + /* + * For paranoia's sake, capture the file path before invoking the + * ereport machinery. This guards against the possibility of a + * relcache flush caused by, e.g., an errcontext callback. + * (errcontext callbacks shouldn't be risking any such thing, but + * people have been known to forget that rule.) + */ + char *relpath = relpathbackend(src->smgr_rnode.node, + src->smgr_rnode.backend, + forkNum); + + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("invalid page in block %u of relation %s", + blkno, relpath))); + } + + /* + * WAL-log the copied page. Unfortunately we don't know what kind of a + * page this is, so we have to log the full page including any unused + * space. + */ + if (use_wal) + log_newpage(&dst->smgr_rnode.node, forkNum, blkno, page, false); + + PageSetChecksumInplace(page, blkno); + + /* + * Now write the page. We say skipFsync = true because there's no + * need for smgr to schedule an fsync for this write; we'll do it + * ourselves below. + */ + smgrextend(dst, forkNum, blkno, buf.data, true); + } + + /* + * When we WAL-logged rel pages, we must nonetheless fsync them. The + * reason is that since we're copying outside shared buffers, a CHECKPOINT + * occurring during the copy has no way to flush the previously written + * data to disk (indeed it won't know the new rel even exists). A crash + * later on would replay WAL from the checkpoint, therefore it wouldn't + * replay our earlier WAL entries. If we do not fsync those pages here, + * they might still not be on disk when the crash occurs. + */ + if (use_wal || copying_initfork) + smgrimmedsync(dst, forkNum); +} + +/* + * RelFileNodeSkippingWAL + * Check if a BM_PERMANENT relfilenode is using WAL. + * + * Changes of certain relfilenodes must not write WAL; see "Skipping WAL for + * New RelFileNode" in src/backend/access/transam/README. Though it is known + * from Relation efficiently, this function is intended for the code paths not + * having access to Relation. + */ +bool +RelFileNodeSkippingWAL(RelFileNode rnode) +{ + if (!pendingSyncHash || + hash_search(pendingSyncHash, &rnode, HASH_FIND, NULL) == NULL) + return false; + + return true; +} + +/* + * EstimatePendingSyncsSpace + * Estimate space needed to pass syncs to parallel workers. + */ +Size +EstimatePendingSyncsSpace(void) +{ + long entries; + + entries = pendingSyncHash ? hash_get_num_entries(pendingSyncHash) : 0; + return mul_size(1 + entries, sizeof(RelFileNode)); +} + +/* + * SerializePendingSyncs + * Serialize syncs for parallel workers. + */ +void +SerializePendingSyncs(Size maxSize, char *startAddress) +{ + HTAB *tmphash; + HASHCTL ctl; + HASH_SEQ_STATUS scan; + PendingRelSync *sync; + PendingRelDelete *delete; + RelFileNode *src; + RelFileNode *dest = (RelFileNode *) startAddress; + + if (!pendingSyncHash) + goto terminate; + + /* Create temporary hash to collect active relfilenodes */ + ctl.keysize = sizeof(RelFileNode); + ctl.entrysize = sizeof(RelFileNode); + ctl.hcxt = CurrentMemoryContext; + tmphash = hash_create("tmp relfilenodes", + hash_get_num_entries(pendingSyncHash), &ctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + + /* collect all rnodes from pending syncs */ + hash_seq_init(&scan, pendingSyncHash); + while ((sync = (PendingRelSync *) hash_seq_search(&scan))) + (void) hash_search(tmphash, &sync->rnode, HASH_ENTER, NULL); + + /* remove deleted rnodes */ + for (delete = pendingDeletes; delete != NULL; delete = delete->next) + if (delete->atCommit) + (void) hash_search(tmphash, (void *) &delete->relnode, + HASH_REMOVE, NULL); + + hash_seq_init(&scan, tmphash); + while ((src = (RelFileNode *) hash_seq_search(&scan))) + *dest++ = *src; + + hash_destroy(tmphash); + +terminate: + MemSet(dest, 0, sizeof(RelFileNode)); +} + +/* + * RestorePendingSyncs + * Restore syncs within a parallel worker. + * + * RelationNeedsWAL() and RelFileNodeSkippingWAL() must offer the correct + * answer to parallel workers. Only smgrDoPendingSyncs() reads the + * is_truncated field, at end of transaction. Hence, don't restore it. + */ +void +RestorePendingSyncs(char *startAddress) +{ + RelFileNode *rnode; + + Assert(pendingSyncHash == NULL); + for (rnode = (RelFileNode *) startAddress; rnode->relNode != 0; rnode++) + AddPendingSync(rnode); +} + +/* + * smgrDoPendingDeletes() -- Take care of relation deletes at end of xact. + * + * This also runs when aborting a subxact; we want to clean up a failed + * subxact immediately. + * + * Note: It's possible that we're being asked to remove a relation that has + * no physical storage in any fork. In particular, it's possible that we're + * cleaning up an old temporary relation for which RemovePgTempFiles has + * already recovered the physical storage. + */ +void +smgrDoPendingDeletes(bool isCommit) +{ + int nestLevel = GetCurrentTransactionNestLevel(); + PendingRelDelete *pending; + PendingRelDelete *prev; + PendingRelDelete *next; + int nrels = 0, + maxrels = 0; + SMgrRelation *srels = NULL; + + prev = NULL; + for (pending = pendingDeletes; pending != NULL; pending = next) + { + next = pending->next; + if (pending->nestLevel < nestLevel) + { + /* outer-level entries should not be processed yet */ + prev = pending; + } + else + { + /* unlink list entry first, so we don't retry on failure */ + if (prev) + prev->next = next; + else + pendingDeletes = next; + /* do deletion if called for */ + if (pending->atCommit == isCommit) + { + SMgrRelation srel; + + srel = smgropen(pending->relnode, pending->backend); + + /* allocate the initial array, or extend it, if needed */ + if (maxrels == 0) + { + maxrels = 8; + srels = palloc(sizeof(SMgrRelation) * maxrels); + } + else if (maxrels <= nrels) + { + maxrels *= 2; + srels = repalloc(srels, sizeof(SMgrRelation) * maxrels); + } + + srels[nrels++] = srel; + } + /* must explicitly free the list entry */ + pfree(pending); + /* prev does not change */ + } + } + + if (nrels > 0) + { + smgrdounlinkall(srels, nrels, false); + + for (int i = 0; i < nrels; i++) + smgrclose(srels[i]); + + pfree(srels); + } +} + +/* + * smgrDoPendingSyncs() -- Take care of relation syncs at end of xact. + */ +void +smgrDoPendingSyncs(bool isCommit, bool isParallelWorker) +{ + PendingRelDelete *pending; + int nrels = 0, + maxrels = 0; + SMgrRelation *srels = NULL; + HASH_SEQ_STATUS scan; + PendingRelSync *pendingsync; + + Assert(GetCurrentTransactionNestLevel() == 1); + + if (!pendingSyncHash) + return; /* no relation needs sync */ + + /* Abort -- just throw away all pending syncs */ + if (!isCommit) + { + pendingSyncHash = NULL; + return; + } + + AssertPendingSyncs_RelationCache(); + + /* Parallel worker -- just throw away all pending syncs */ + if (isParallelWorker) + { + pendingSyncHash = NULL; + return; + } + + /* Skip syncing nodes that smgrDoPendingDeletes() will delete. */ + for (pending = pendingDeletes; pending != NULL; pending = pending->next) + if (pending->atCommit) + (void) hash_search(pendingSyncHash, (void *) &pending->relnode, + HASH_REMOVE, NULL); + + hash_seq_init(&scan, pendingSyncHash); + while ((pendingsync = (PendingRelSync *) hash_seq_search(&scan))) + { + ForkNumber fork; + BlockNumber nblocks[MAX_FORKNUM + 1]; + BlockNumber total_blocks = 0; + SMgrRelation srel; + + srel = smgropen(pendingsync->rnode, InvalidBackendId); + + /* + * We emit newpage WAL records for smaller relations. + * + * Small WAL records have a chance to be emitted along with other + * backends' WAL records. We emit WAL records instead of syncing for + * files that are smaller than a certain threshold, expecting faster + * commit. The threshold is defined by the GUC wal_skip_threshold. + */ + if (!pendingsync->is_truncated) + { + for (fork = 0; fork <= MAX_FORKNUM; fork++) + { + if (smgrexists(srel, fork)) + { + BlockNumber n = smgrnblocks(srel, fork); + + /* we shouldn't come here for unlogged relations */ + Assert(fork != INIT_FORKNUM); + nblocks[fork] = n; + total_blocks += n; + } + else + nblocks[fork] = InvalidBlockNumber; + } + } + + /* + * Sync file or emit WAL records for its contents. + * + * Although we emit WAL record if the file is small enough, do file + * sync regardless of the size if the file has experienced a + * truncation. It is because the file would be followed by trailing + * garbage blocks after a crash recovery if, while a past longer file + * had been flushed out, we omitted syncing-out of the file and + * emitted WAL instead. You might think that we could choose WAL if + * the current main fork is longer than ever, but there's a case where + * main fork is longer than ever but FSM fork gets shorter. + */ + if (pendingsync->is_truncated || + total_blocks * BLCKSZ / 1024 >= wal_skip_threshold) + { + /* allocate the initial array, or extend it, if needed */ + if (maxrels == 0) + { + maxrels = 8; + srels = palloc(sizeof(SMgrRelation) * maxrels); + } + else if (maxrels <= nrels) + { + maxrels *= 2; + srels = repalloc(srels, sizeof(SMgrRelation) * maxrels); + } + + srels[nrels++] = srel; + } + else + { + /* Emit WAL records for all blocks. The file is small enough. */ + for (fork = 0; fork <= MAX_FORKNUM; fork++) + { + int n = nblocks[fork]; + Relation rel; + + if (!BlockNumberIsValid(n)) + continue; + + /* + * Emit WAL for the whole file. Unfortunately we don't know + * what kind of a page this is, so we have to log the full + * page including any unused space. ReadBufferExtended() + * counts some pgstat events; unfortunately, we discard them. + */ + rel = CreateFakeRelcacheEntry(srel->smgr_rnode.node); + log_newpage_range(rel, fork, 0, n, false); + FreeFakeRelcacheEntry(rel); + } + } + } + + pendingSyncHash = NULL; + + if (nrels > 0) + { + smgrdosyncall(srels, nrels); + pfree(srels); + } +} + +/* + * smgrGetPendingDeletes() -- Get a list of non-temp relations to be deleted. + * + * The return value is the number of relations scheduled for termination. + * *ptr is set to point to a freshly-palloc'd array of RelFileNodes. + * If there are no relations to be deleted, *ptr is set to NULL. + * + * Only non-temporary relations are included in the returned list. This is OK + * because the list is used only in contexts where temporary relations don't + * matter: we're either writing to the two-phase state file (and transactions + * that have touched temp tables can't be prepared) or we're writing to xlog + * (and all temporary files will be zapped if we restart anyway, so no need + * for redo to do it also). + * + * Note that the list does not include anything scheduled for termination + * by upper-level transactions. + */ +int +smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr) +{ + int nestLevel = GetCurrentTransactionNestLevel(); + int nrels; + RelFileNode *rptr; + PendingRelDelete *pending; + + nrels = 0; + for (pending = pendingDeletes; pending != NULL; pending = pending->next) + { + if (pending->nestLevel >= nestLevel && pending->atCommit == forCommit + && pending->backend == InvalidBackendId) + nrels++; + } + if (nrels == 0) + { + *ptr = NULL; + return 0; + } + rptr = (RelFileNode *) palloc(nrels * sizeof(RelFileNode)); + *ptr = rptr; + for (pending = pendingDeletes; pending != NULL; pending = pending->next) + { + if (pending->nestLevel >= nestLevel && pending->atCommit == forCommit + && pending->backend == InvalidBackendId) + { + *rptr = pending->relnode; + rptr++; + } + } + return nrels; +} + +/* + * PostPrepare_smgr -- Clean up after a successful PREPARE + * + * What we have to do here is throw away the in-memory state about pending + * relation deletes. It's all been recorded in the 2PC state file and + * it's no longer smgr's job to worry about it. + */ +void +PostPrepare_smgr(void) +{ + PendingRelDelete *pending; + PendingRelDelete *next; + + for (pending = pendingDeletes; pending != NULL; pending = next) + { + next = pending->next; + pendingDeletes = next; + /* must explicitly free the list entry */ + pfree(pending); + } +} + + +/* + * AtSubCommit_smgr() --- Take care of subtransaction commit. + * + * Reassign all items in the pending-deletes list to the parent transaction. + */ +void +AtSubCommit_smgr(void) +{ + int nestLevel = GetCurrentTransactionNestLevel(); + PendingRelDelete *pending; + + for (pending = pendingDeletes; pending != NULL; pending = pending->next) + { + if (pending->nestLevel >= nestLevel) + pending->nestLevel = nestLevel - 1; + } +} + +/* + * AtSubAbort_smgr() --- Take care of subtransaction abort. + * + * Delete created relations and forget about deleted relations. + * We can execute these operations immediately because we know this + * subtransaction will not commit. + */ +void +AtSubAbort_smgr(void) +{ + smgrDoPendingDeletes(false); +} + +void +smgr_redo(XLogReaderState *record) +{ + XLogRecPtr lsn = record->EndRecPtr; + uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; + + /* Backup blocks are not used in smgr records */ + Assert(!XLogRecHasAnyBlockRefs(record)); + + if (info == XLOG_SMGR_CREATE) + { + xl_smgr_create *xlrec = (xl_smgr_create *) XLogRecGetData(record); + SMgrRelation reln; + + reln = smgropen(xlrec->rnode, InvalidBackendId); + smgrcreate(reln, xlrec->forkNum, true); + } + else if (info == XLOG_SMGR_TRUNCATE) + { + xl_smgr_truncate *xlrec = (xl_smgr_truncate *) XLogRecGetData(record); + SMgrRelation reln; + Relation rel; + ForkNumber forks[MAX_FORKNUM]; + BlockNumber blocks[MAX_FORKNUM]; + int nforks = 0; + bool need_fsm_vacuum = false; + + reln = smgropen(xlrec->rnode, InvalidBackendId); + + /* + * Forcibly create relation if it doesn't exist (which suggests that + * it was dropped somewhere later in the WAL sequence). As in + * XLogReadBufferForRedo, we prefer to recreate the rel and replay the + * log as best we can until the drop is seen. + */ + smgrcreate(reln, MAIN_FORKNUM, true); + + /* + * Before we perform the truncation, update minimum recovery point to + * cover this WAL record. Once the relation is truncated, there's no + * going back. The buffer manager enforces the WAL-first rule for + * normal updates to relation files, so that the minimum recovery + * point is always updated before the corresponding change in the data + * file is flushed to disk. We have to do the same manually here. + * + * Doing this before the truncation means that if the truncation fails + * for some reason, you cannot start up the system even after restart, + * until you fix the underlying situation so that the truncation will + * succeed. Alternatively, we could update the minimum recovery point + * after truncation, but that would leave a small window where the + * WAL-first rule could be violated. + */ + XLogFlush(lsn); + + /* Prepare for truncation of MAIN fork */ + if ((xlrec->flags & SMGR_TRUNCATE_HEAP) != 0) + { + forks[nforks] = MAIN_FORKNUM; + blocks[nforks] = xlrec->blkno; + nforks++; + + /* Also tell xlogutils.c about it */ + XLogTruncateRelation(xlrec->rnode, MAIN_FORKNUM, xlrec->blkno); + } + + /* Prepare for truncation of FSM and VM too */ + rel = CreateFakeRelcacheEntry(xlrec->rnode); + + if ((xlrec->flags & SMGR_TRUNCATE_FSM) != 0 && + smgrexists(reln, FSM_FORKNUM)) + { + blocks[nforks] = FreeSpaceMapPrepareTruncateRel(rel, xlrec->blkno); + if (BlockNumberIsValid(blocks[nforks])) + { + forks[nforks] = FSM_FORKNUM; + nforks++; + need_fsm_vacuum = true; + } + } + if ((xlrec->flags & SMGR_TRUNCATE_VM) != 0 && + smgrexists(reln, VISIBILITYMAP_FORKNUM)) + { + blocks[nforks] = visibilitymap_prepare_truncate(rel, xlrec->blkno); + if (BlockNumberIsValid(blocks[nforks])) + { + forks[nforks] = VISIBILITYMAP_FORKNUM; + nforks++; + } + } + + /* Do the real work to truncate relation forks */ + if (nforks > 0) + smgrtruncate(reln, forks, nforks, blocks); + + /* + * Update upper-level FSM pages to account for the truncation. This is + * important because the just-truncated pages were likely marked as + * all-free, and would be preferentially selected. + */ + if (need_fsm_vacuum) + FreeSpaceMapVacuumRange(rel, xlrec->blkno, + InvalidBlockNumber); + + FreeFakeRelcacheEntry(rel); + } + else + elog(PANIC, "smgr_redo: unknown op code %u", info); +} |