On Thu, Oct 13, 2022 at 4:43 PM Alvaro Herrera <alvhe...@alvh.no-ip.org> wrote: >
Thanks for reviewing. > > Hm. Agree. But, that requires us to include xlogbackup.h in > > xlog_internal.h for SessionBackupState enum in > > ResetXLogBackupActivity(). Is that okay? > > It's not great, but it's not *that* bad, ISTM, mainly because > xlog_internal.h will affect less stuff than xlog.h. Moved them to xlog_internal.h without xlogbackup.h included, please see below. > > SessionBackupState and it needs to be set before we release WAL insert > > locks, see the comment [1]. > > I see. Maybe we could keep that enum in xlog.h, instead. It's not required now, please see below. > While looking at how that works: I think calling a local variable > "session_backup_state" is super confusing, seeing that we have a > file-global variable called sessionBackupState. I recommend naming the > local "newstate" or something along those lines instead. > > I wonder why does pg_backup_start_callback() not change the backup state > before your patch. This seems a gratuitous difference, or is it? If > you change that code so that it also sets the status to BACKUP_NONE, > then you can pass a bare SessionBackupState to ResetXLogBackupActivity > rather than a pointer to one, which is a very strange arrangement that > exists only so that you can have a third state (NULL) meaning "don't > change state" -- that looks quite weird. > > Alternatively, if you don't want or can't change > pg_backup_start_callback to pass a valid state value, another solution > might be to pass a separate boolean "change state". > > But I would look at having another patch before your series that changes > pg_backup_start_callback to make the code identical for the three > callers, then you can simplify the patched code. The pg_backup_start_callback() can just go ahead and reset sessionBackupState. However, it leads us to the complete removal of pg_backup_start_callback() itself and use do_pg_abort_backup() consistently across, saving 20 LOC attached as v5-0001. With this, the other patches would get simplified a bit too, xlogbackup.h footprint got reduced now. Please find the v5 patch-set. 0002-0004 moves the backup code to xlogbackup.c/.h. -- Bharath Rupireddy PostgreSQL Contributors Team RDS Open Source Databases Amazon Web Services: https://aws.amazon.com
From 5b2e8f86e388fbbe9f7eb276ca137b14ac0e2fa3 Mon Sep 17 00:00:00 2001 From: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com> Date: Thu, 13 Oct 2022 12:18:46 +0000 Subject: [PATCH v5] Use do_pg_abort_backup() consistently across the backup code Backup code uses another abort callback pg_backup_start_callback() when it has do_pg_abort_backup(). These two are before_shmem_exit() callbacks more or less do the same thing. The only difference between them is resetting sessionBackupState to SESSION_BACKUP_NONE. Actually, it is safe for us to reset sessionBackupState on aborts. This leads us to remove the pg_backup_start_callback() and use do_pg_abort_backup() consistently across. Author: Bharath Rupireddy per suggestion from Alvaro Herrera. Discussion: https://www.postgresql.org/message-id/20221013111330.564fk5tkwe3ha77l%40alvherre.pgsql --- src/backend/access/transam/xlog.c | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index 27085b15a8..c9c1c6f287 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -679,8 +679,6 @@ static void ReadControlFile(void); static void UpdateControlFile(void); static char *str_time(pg_time_t tnow); -static void pg_backup_start_callback(int code, Datum arg); - static int get_sync_bit(int method); static void CopyXLogRecordToWAL(int write_len, bool isLogSwitch, @@ -8321,7 +8319,7 @@ do_pg_backup_start(const char *backupidstr, bool fast, List **tablespaces, WALInsertLockRelease(); /* Ensure we release forcePageWrites if fail below */ - PG_ENSURE_ERROR_CLEANUP(pg_backup_start_callback, (Datum) 0); + PG_ENSURE_ERROR_CLEANUP(do_pg_abort_backup, DatumGetBool(false)); { bool gotUniqueStartpoint = false; DIR *tblspcdir; @@ -8531,7 +8529,7 @@ do_pg_backup_start(const char *backupidstr, bool fast, List **tablespaces, state->starttime = (pg_time_t) time(NULL); } - PG_END_ENSURE_ERROR_CLEANUP(pg_backup_start_callback, (Datum) 0); + PG_END_ENSURE_ERROR_CLEANUP(do_pg_abort_backup, DatumGetBool(false)); state->started_in_recovery = backup_started_in_recovery; @@ -8541,23 +8539,6 @@ do_pg_backup_start(const char *backupidstr, bool fast, List **tablespaces, sessionBackupState = SESSION_BACKUP_RUNNING; } -/* Error cleanup callback for pg_backup_start */ -static void -pg_backup_start_callback(int code, Datum arg) -{ - /* Update backup counters and forcePageWrites on failure */ - WALInsertLockAcquireExclusive(); - - Assert(XLogCtl->Insert.runningBackups > 0); - XLogCtl->Insert.runningBackups--; - - if (XLogCtl->Insert.runningBackups == 0) - { - XLogCtl->Insert.forcePageWrites = false; - } - WALInsertLockRelease(); -} - /* * Utility routine to fetch the session-level status of a backup running. */ -- 2.34.1
From 19558633522b64b92ac9491077273bd58507aec1 Mon Sep 17 00:00:00 2001 From: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com> Date: Thu, 13 Oct 2022 13:02:57 +0000 Subject: [PATCH v5] Add functions for xlogbackup.c to call back into xlog.c --- src/backend/access/transam/xlog.c | 194 +++++++++++++++++++---------- src/include/access/xlog.h | 1 + src/include/access/xlog_internal.h | 11 ++ 3 files changed, 141 insertions(+), 65 deletions(-) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index c9c1c6f287..4412817804 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -8313,10 +8313,7 @@ do_pg_backup_start(const char *backupidstr, bool fast, List **tablespaces, * forcePageWrites, to ensure adequate interlocking against * XLogInsertRecord(). */ - WALInsertLockAcquireExclusive(); - XLogCtl->Insert.runningBackups++; - XLogCtl->Insert.forcePageWrites = true; - WALInsertLockRelease(); + SetXLogBackupActivity(); /* Ensure we release forcePageWrites if fail below */ PG_ENSURE_ERROR_CLEANUP(do_pg_abort_backup, DatumGetBool(false)); @@ -8382,12 +8379,8 @@ do_pg_backup_start(const char *backupidstr, bool fast, List **tablespaces, * to restore starting from the checkpoint is precisely the REDO * pointer. */ - LWLockAcquire(ControlFileLock, LW_SHARED); - state->checkpointloc = ControlFile->checkPoint; - state->startpoint = ControlFile->checkPointCopy.redo; - state->starttli = ControlFile->checkPointCopy.ThisTimeLineID; - checkpointfpw = ControlFile->checkPointCopy.fullPageWrites; - LWLockRelease(ControlFileLock); + GetCheckpointLocation(&state->checkpointloc, &state->startpoint, + &state->starttli, &checkpointfpw); if (backup_started_in_recovery) { @@ -8398,9 +8391,7 @@ do_pg_backup_start(const char *backupidstr, bool fast, List **tablespaces, * (i.e., since last restartpoint used as backup starting * checkpoint) contain full-page writes. */ - SpinLockAcquire(&XLogCtl->info_lck); - recptr = XLogCtl->lastFpwDisableRecPtr; - SpinLockRelease(&XLogCtl->info_lck); + recptr = GetlastFpwDisableRecPtr(); if (!checkpointfpw || state->startpoint <= recptr) ereport(ERROR, @@ -8433,13 +8424,7 @@ do_pg_backup_start(const char *backupidstr, bool fast, List **tablespaces, * taking a checkpoint right after another is not that expensive * either because only few buffers have been dirtied yet. */ - WALInsertLockAcquireExclusive(); - if (XLogCtl->Insert.lastBackupStart < state->startpoint) - { - XLogCtl->Insert.lastBackupStart = state->startpoint; - gotUniqueStartpoint = true; - } - WALInsertLockRelease(); + gotUniqueStartpoint = SetlastBackupStart(state->startpoint); } while (!gotUniqueStartpoint); /* @@ -8548,6 +8533,15 @@ get_backup_status(void) return sessionBackupState; } +/* + * Utility routine to reset the session-level status of a backup running. + */ +void +reset_backup_status(void) +{ + sessionBackupState = SESSION_BACKUP_NONE; +} + /* * do_pg_backup_stop * @@ -8594,33 +8588,11 @@ do_pg_backup_stop(BackupState *state, bool waitforarchive) * Note that CHECK_FOR_INTERRUPTS() must not occur while updating them. * Otherwise they can be updated inconsistently, and which might cause * do_pg_abort_backup() to fail. - */ - WALInsertLockAcquireExclusive(); - - /* - * It is expected that each do_pg_backup_start() call is matched by - * exactly one do_pg_backup_stop() call. - */ - Assert(XLogCtl->Insert.runningBackups > 0); - XLogCtl->Insert.runningBackups--; - - if (XLogCtl->Insert.runningBackups == 0) - { - XLogCtl->Insert.forcePageWrites = false; - } - - /* - * Clean up session-level lock. * - * You might think that WALInsertLockRelease() can be called before - * cleaning up session-level lock because session-level lock doesn't need - * to be protected with WAL insertion lock. But since - * CHECK_FOR_INTERRUPTS() can occur in it, session-level lock must be - * cleaned up before it. + * It is expected that each do_pg_backup_start() call is matched by exactly + * one do_pg_backup_stop() call. */ - sessionBackupState = SESSION_BACKUP_NONE; - - WALInsertLockRelease(); + ResetXLogBackupActivity(); /* * If we are taking an online backup from the standby, we confirm that the @@ -8670,9 +8642,7 @@ do_pg_backup_stop(BackupState *state, bool waitforarchive) * Check to see if all WAL replayed during online backup contain * full-page writes. */ - SpinLockAcquire(&XLogCtl->info_lck); - recptr = XLogCtl->lastFpwDisableRecPtr; - SpinLockRelease(&XLogCtl->info_lck); + recptr = GetlastFpwDisableRecPtr(); if (state->startpoint <= recptr) ereport(ERROR, @@ -8684,11 +8654,7 @@ do_pg_backup_stop(BackupState *state, bool waitforarchive) "Enable full_page_writes and run CHECKPOINT on the primary, " "and then try an online backup again."))); - - LWLockAcquire(ControlFileLock, LW_SHARED); - state->stoppoint = ControlFile->minRecoveryPoint; - state->stoptli = ControlFile->minRecoveryPointTLI; - LWLockRelease(ControlFileLock); + GetminRecoveryPoint(&state->stoppoint, &state->stoptli); } else { @@ -8706,7 +8672,7 @@ do_pg_backup_stop(BackupState *state, bool waitforarchive) * Given that we're not in recovery, InsertTimeLineID is set and can't * change, so we can read it without a lock. */ - state->stoptli = XLogCtl->InsertTimeLineID; + state->stoptli = GetWALInsertionTimeLine(); /* * Force a switch to a new xlog segment file, so that the backup is @@ -8848,17 +8814,7 @@ do_pg_abort_backup(int code, Datum arg) if (sessionBackupState != SESSION_BACKUP_RUNNING) return; - WALInsertLockAcquireExclusive(); - Assert(XLogCtl->Insert.runningBackups > 0); - XLogCtl->Insert.runningBackups--; - - if (XLogCtl->Insert.runningBackups == 0) - { - XLogCtl->Insert.forcePageWrites = false; - } - - sessionBackupState = SESSION_BACKUP_NONE; - WALInsertLockRelease(); + ResetXLogBackupActivity(); if (emit_warning) ereport(WARNING, @@ -8880,6 +8836,114 @@ register_persistent_abort_backup_handler(void) already_done = true; } +/* + * Get the checkpoint location. + */ +void +GetCheckpointLocation(XLogRecPtr *loc, XLogRecPtr *redoloc, + TimeLineID *tli, bool *fpw) +{ + LWLockAcquire(ControlFileLock, LW_SHARED); + *loc = ControlFile->checkPoint; + *redoloc = ControlFile->checkPointCopy.redo; + *tli = ControlFile->checkPointCopy.ThisTimeLineID; + *fpw = ControlFile->checkPointCopy.fullPageWrites; + LWLockRelease(ControlFileLock); +} + +/* + * Get the minRecoveryPoint and minRecoveryPointTLI. + */ +void +GetminRecoveryPoint(XLogRecPtr *loc, TimeLineID *tli) +{ + LWLockAcquire(ControlFileLock, LW_SHARED); + *loc = ControlFile->minRecoveryPoint; + *tli = ControlFile->minRecoveryPointTLI; + LWLockRelease(ControlFileLock); +} + +/* + * Get the lastFpwDisableRecPtr. + */ +XLogRecPtr +GetlastFpwDisableRecPtr(void) +{ + XLogRecPtr recptr; + + SpinLockAcquire(&XLogCtl->info_lck); + recptr = XLogCtl->lastFpwDisableRecPtr; + SpinLockRelease(&XLogCtl->info_lck); + + return recptr; +} + +/* + * Set the lastBackupStar only if it is less than passed-in rectpr and return + * true. Otherwise return false. + * + * Note: For those who want to set lastBackupStar unconditionally, pass rectpr + * value as PG_UINT64_MAX, which is higher than any real XLogRecPtr value. + */ +bool +SetlastBackupStart(XLogRecPtr recptr) +{ + bool is_set = false; + + WALInsertLockAcquireExclusive(); + if (XLogCtl->Insert.lastBackupStart < recptr) + { + XLogCtl->Insert.lastBackupStart = recptr; + is_set = true; + } + WALInsertLockRelease(); + + return is_set; +} + +/* + * Reset backup activity such as runningBackups, forcePageWrites and + * session-level status. + */ +void +ResetXLogBackupActivity(void) +{ + WALInsertLockAcquireExclusive(); + + Assert(XLogCtl->Insert.runningBackups > 0); + XLogCtl->Insert.runningBackups--; + + if (XLogCtl->Insert.runningBackups == 0) + { + XLogCtl->Insert.forcePageWrites = false; + } + + /* + * Reset session-level lock. + * + * You might think that WALInsertLockRelease() can be called before + * cleaning up session-level lock because session-level lock doesn't need + * to be protected with WAL insertion lock. But since + * CHECK_FOR_INTERRUPTS() can occur in it, session-level lock must be + * cleaned up before it. + */ + reset_backup_status(); + + WALInsertLockRelease(); +} + +/* + * Set backup activity such as runningBackups and forcePageWrites. + */ +void +SetXLogBackupActivity(void) +{ + WALInsertLockAcquireExclusive(); + XLogCtl->Insert.runningBackups++; + XLogCtl->Insert.forcePageWrites = true; + WALInsertLockRelease(); +} + /* * Get latest WAL insert pointer */ diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h index 1fbd48fbda..b46adca291 100644 --- a/src/include/access/xlog.h +++ b/src/include/access/xlog.h @@ -285,6 +285,7 @@ extern void do_pg_backup_stop(BackupState *state, bool waitforarchive); extern void do_pg_abort_backup(int code, Datum arg); extern void register_persistent_abort_backup_handler(void); extern SessionBackupState get_backup_status(void); +extern void reset_backup_status(void); /* File path names (all relative to $PGDATA) */ #define RECOVERY_SIGNAL_FILE "recovery.signal" diff --git a/src/include/access/xlog_internal.h b/src/include/access/xlog_internal.h index 44291b337b..d1f53d2512 100644 --- a/src/include/access/xlog_internal.h +++ b/src/include/access/xlog_internal.h @@ -401,4 +401,15 @@ extern PGDLLIMPORT bool InArchiveRecovery; extern PGDLLIMPORT bool StandbyMode; extern PGDLLIMPORT char *recoveryRestoreCommand; +/* + * Routines used by xlogbackup.c to call back into xlog.c during backup. + */ +extern void GetCheckpointLocation(XLogRecPtr *loc, XLogRecPtr *redoloc, + TimeLineID *tli, bool *fpw); +extern void GetminRecoveryPoint(XLogRecPtr *loc, TimeLineID *tli); +extern XLogRecPtr GetlastFpwDisableRecPtr(void); +extern bool SetlastBackupStart(XLogRecPtr recptr); +extern void ResetXLogBackupActivity(void); +extern void SetXLogBackupActivity(void); + #endif /* XLOG_INTERNAL_H */ -- 2.34.1
From 646cb33370779a03153184ef77de60de2cd682f7 Mon Sep 17 00:00:00 2001 From: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com> Date: Thu, 13 Oct 2022 13:36:50 +0000 Subject: [PATCH v5] Move backup-related code from xlog.c to xlogbackup.c --- src/backend/access/transam/xlog.c | 643 +---------------------- src/backend/access/transam/xlogbackup.c | 653 ++++++++++++++++++++++++ src/include/access/xlog.h | 30 -- src/include/access/xlogbackup.h | 30 ++ 4 files changed, 685 insertions(+), 671 deletions(-) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index 4412817804..7a5aa3db7c 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -57,20 +57,20 @@ #include "access/twophase.h" #include "access/xact.h" #include "access/xlog_internal.h" +#include "access/xlogbackup.h" #include "access/xlogarchive.h" #include "access/xloginsert.h" #include "access/xlogprefetcher.h" #include "access/xlogreader.h" #include "access/xlogrecovery.h" #include "access/xlogutils.h" -#include "backup/basebackup.h" #include "catalog/catversion.h" -#include "catalog/pg_control.h" #include "catalog/pg_database.h" #include "common/controldata_utils.h" #include "common/file_utils.h" #include "executor/instrument.h" #include "miscadmin.h" +#include "nodes/pg_list.h" #include "pg_trace.h" #include "pgstat.h" #include "port/atomics.h" @@ -86,7 +86,6 @@ #include "replication/walsender.h" #include "storage/bufmgr.h" #include "storage/fd.h" -#include "storage/ipc.h" #include "storage/large_object.h" #include "storage/latch.h" #include "storage/pmsignal.h" @@ -393,12 +392,6 @@ typedef union WALInsertLockPadded char pad[PG_CACHE_LINE_SIZE]; } WALInsertLockPadded; -/* - * Session status of running backup, used for sanity checks in SQL-callable - * functions to start and stop backups. - */ -static SessionBackupState sessionBackupState = SESSION_BACKUP_NONE; - /* * Shared state data for WAL insertion. */ @@ -670,7 +663,6 @@ static void RemoveXlogFile(const struct dirent *segment_de, TimeLineID insertTLI); static void UpdateLastRemovedPtr(char *filename); static void ValidateXLOGDirectoryStructure(void); -static void CleanupBackupHistory(void); static void UpdateMinRecoveryPoint(XLogRecPtr lsn, bool force); static bool PerformRecoveryXLogAction(void); static void InitControlFile(uint64 sysidentifier); @@ -3819,38 +3811,6 @@ ValidateXLOGDirectoryStructure(void) } } -/* - * Remove previous backup history files. This also retries creation of - * .ready files for any backup history files for which XLogArchiveNotify - * failed earlier. - */ -static void -CleanupBackupHistory(void) -{ - DIR *xldir; - struct dirent *xlde; - char path[MAXPGPATH + sizeof(XLOGDIR)]; - - xldir = AllocateDir(XLOGDIR); - - while ((xlde = ReadDir(xldir, XLOGDIR)) != NULL) - { - if (IsBackupHistoryFileName(xlde->d_name)) - { - if (XLogArchiveCheckDone(xlde->d_name)) - { - elog(DEBUG2, "removing WAL backup history file \"%s\"", - xlde->d_name); - snprintf(path, sizeof(path), XLOGDIR "/%s", xlde->d_name); - unlink(path); - XLogArchiveCleanup(xlde->d_name); - } - } - } - - FreeDir(xldir); -} - /* * I/O routines for pg_control * @@ -8237,605 +8197,6 @@ issue_xlog_fsync(int fd, XLogSegNo segno, TimeLineID tli) PendingWalStats.wal_sync++; } -/* - * do_pg_backup_start is the workhorse of the user-visible pg_backup_start() - * function. It creates the necessary starting checkpoint and constructs the - * backup state and tablespace map. - * - * Input parameters are "state" (the backup state), "fast" (if true, we do - * the checkpoint in immediate mode to make it faster), and "tablespaces" - * (if non-NULL, indicates a list of tablespaceinfo structs describing the - * cluster's tablespaces.). - * - * The tablespace map contents are appended to passed-in parameter - * tablespace_map and the caller is responsible for including it in the backup - * archive as 'tablespace_map'. The tablespace_map file is required mainly for - * tar format in windows as native windows utilities are not able to create - * symlinks while extracting files from tar. However for consistency and - * platform-independence, we do it the same way everywhere. - * - * It fills in "state" with the information required for the backup, such - * as the minimum WAL location that must be present to restore from this - * backup (starttli) and the corresponding timeline ID (starttli). - * - * Every successfully started backup must be stopped by calling - * do_pg_backup_stop() or do_pg_abort_backup(). There can be many - * backups active at the same time. - * - * It is the responsibility of the caller of this function to verify the - * permissions of the calling user! - */ -void -do_pg_backup_start(const char *backupidstr, bool fast, List **tablespaces, - BackupState *state, StringInfo tblspcmapfile) -{ - bool backup_started_in_recovery = false; - - Assert(state != NULL); - backup_started_in_recovery = RecoveryInProgress(); - - /* - * During recovery, we don't need to check WAL level. Because, if WAL - * level is not sufficient, it's impossible to get here during recovery. - */ - if (!backup_started_in_recovery && !XLogIsNeeded()) - ereport(ERROR, - (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("WAL level not sufficient for making an online backup"), - errhint("wal_level must be set to \"replica\" or \"logical\" at server start."))); - - if (strlen(backupidstr) > MAXPGPATH) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("backup label too long (max %d bytes)", - MAXPGPATH))); - - memcpy(state->name, backupidstr, strlen(backupidstr)); - - /* - * Mark backup active in shared memory. We must do full-page WAL writes - * during an on-line backup even if not doing so at other times, because - * it's quite possible for the backup dump to obtain a "torn" (partially - * written) copy of a database page if it reads the page concurrently with - * our write to the same page. This can be fixed as long as the first - * write to the page in the WAL sequence is a full-page write. Hence, we - * turn on forcePageWrites and then force a CHECKPOINT, to ensure there - * are no dirty pages in shared memory that might get dumped while the - * backup is in progress without having a corresponding WAL record. (Once - * the backup is complete, we need not force full-page writes anymore, - * since we expect that any pages not modified during the backup interval - * must have been correctly captured by the backup.) - * - * Note that forcePageWrites has no effect during an online backup from - * the standby. - * - * We must hold all the insertion locks to change the value of - * forcePageWrites, to ensure adequate interlocking against - * XLogInsertRecord(). - */ - SetXLogBackupActivity(); - - /* Ensure we release forcePageWrites if fail below */ - PG_ENSURE_ERROR_CLEANUP(do_pg_abort_backup, DatumGetBool(false)); - { - bool gotUniqueStartpoint = false; - DIR *tblspcdir; - struct dirent *de; - tablespaceinfo *ti; - int datadirpathlen; - - /* - * Force an XLOG file switch before the checkpoint, to ensure that the - * WAL segment the checkpoint is written to doesn't contain pages with - * old timeline IDs. That would otherwise happen if you called - * pg_backup_start() right after restoring from a PITR archive: the - * first WAL segment containing the startup checkpoint has pages in - * the beginning with the old timeline ID. That can cause trouble at - * recovery: we won't have a history file covering the old timeline if - * pg_wal directory was not included in the base backup and the WAL - * archive was cleared too before starting the backup. - * - * This also ensures that we have emitted a WAL page header that has - * XLP_BKP_REMOVABLE off before we emit the checkpoint record. - * Therefore, if a WAL archiver (such as pglesslog) is trying to - * compress out removable backup blocks, it won't remove any that - * occur after this point. - * - * During recovery, we skip forcing XLOG file switch, which means that - * the backup taken during recovery is not available for the special - * recovery case described above. - */ - if (!backup_started_in_recovery) - RequestXLogSwitch(false); - - do - { - bool checkpointfpw; - - /* - * Force a CHECKPOINT. Aside from being necessary to prevent torn - * page problems, this guarantees that two successive backup runs - * will have different checkpoint positions and hence different - * history file names, even if nothing happened in between. - * - * During recovery, establish a restartpoint if possible. We use - * the last restartpoint as the backup starting checkpoint. This - * means that two successive backup runs can have same checkpoint - * positions. - * - * Since the fact that we are executing do_pg_backup_start() - * during recovery means that checkpointer is running, we can use - * RequestCheckpoint() to establish a restartpoint. - * - * We use CHECKPOINT_IMMEDIATE only if requested by user (via - * passing fast = true). Otherwise this can take awhile. - */ - RequestCheckpoint(CHECKPOINT_FORCE | CHECKPOINT_WAIT | - (fast ? CHECKPOINT_IMMEDIATE : 0)); - - /* - * Now we need to fetch the checkpoint record location, and also - * its REDO pointer. The oldest point in WAL that would be needed - * to restore starting from the checkpoint is precisely the REDO - * pointer. - */ - GetCheckpointLocation(&state->checkpointloc, &state->startpoint, - &state->starttli, &checkpointfpw); - - if (backup_started_in_recovery) - { - XLogRecPtr recptr; - - /* - * Check to see if all WAL replayed during online backup - * (i.e., since last restartpoint used as backup starting - * checkpoint) contain full-page writes. - */ - recptr = GetlastFpwDisableRecPtr(); - - if (!checkpointfpw || state->startpoint <= recptr) - ereport(ERROR, - (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("WAL generated with full_page_writes=off was replayed " - "since last restartpoint"), - errhint("This means that the backup being taken on the standby " - "is corrupt and should not be used. " - "Enable full_page_writes and run CHECKPOINT on the primary, " - "and then try an online backup again."))); - - /* - * During recovery, since we don't use the end-of-backup WAL - * record and don't write the backup history file, the - * starting WAL location doesn't need to be unique. This means - * that two base backups started at the same time might use - * the same checkpoint as starting locations. - */ - gotUniqueStartpoint = true; - } - - /* - * If two base backups are started at the same time (in WAL sender - * processes), we need to make sure that they use different - * checkpoints as starting locations, because we use the starting - * WAL location as a unique identifier for the base backup in the - * end-of-backup WAL record and when we write the backup history - * file. Perhaps it would be better generate a separate unique ID - * for each backup instead of forcing another checkpoint, but - * taking a checkpoint right after another is not that expensive - * either because only few buffers have been dirtied yet. - */ - gotUniqueStartpoint = SetlastBackupStart(state->startpoint); - } while (!gotUniqueStartpoint); - - /* - * Construct tablespace_map file. - */ - datadirpathlen = strlen(DataDir); - - /* Collect information about all tablespaces */ - tblspcdir = AllocateDir("pg_tblspc"); - while ((de = ReadDir(tblspcdir, "pg_tblspc")) != NULL) - { - char fullpath[MAXPGPATH + 10]; - char linkpath[MAXPGPATH]; - char *relpath = NULL; - int rllen; - StringInfoData escapedpath; - char *s; - - /* Skip anything that doesn't look like a tablespace */ - if (strspn(de->d_name, "0123456789") != strlen(de->d_name)) - continue; - - snprintf(fullpath, sizeof(fullpath), "pg_tblspc/%s", de->d_name); - - /* - * Skip anything that isn't a symlink/junction. For testing only, - * we sometimes use allow_in_place_tablespaces to create - * directories directly under pg_tblspc, which would fail below. - */ - if (get_dirent_type(fullpath, de, false, ERROR) != PGFILETYPE_LNK) - continue; - - rllen = readlink(fullpath, linkpath, sizeof(linkpath)); - if (rllen < 0) - { - ereport(WARNING, - (errmsg("could not read symbolic link \"%s\": %m", - fullpath))); - continue; - } - else if (rllen >= sizeof(linkpath)) - { - ereport(WARNING, - (errmsg("symbolic link \"%s\" target is too long", - fullpath))); - continue; - } - linkpath[rllen] = '\0'; - - /* - * Build a backslash-escaped version of the link path to include - * in the tablespace map file. - */ - initStringInfo(&escapedpath); - for (s = linkpath; *s; s++) - { - if (*s == '\n' || *s == '\r' || *s == '\\') - appendStringInfoChar(&escapedpath, '\\'); - appendStringInfoChar(&escapedpath, *s); - } - - /* - * Relpath holds the relative path of the tablespace directory - * when it's located within PGDATA, or NULL if it's located - * elsewhere. - */ - if (rllen > datadirpathlen && - strncmp(linkpath, DataDir, datadirpathlen) == 0 && - IS_DIR_SEP(linkpath[datadirpathlen])) - relpath = linkpath + datadirpathlen + 1; - - ti = palloc(sizeof(tablespaceinfo)); - ti->oid = pstrdup(de->d_name); - ti->path = pstrdup(linkpath); - ti->rpath = relpath ? pstrdup(relpath) : NULL; - ti->size = -1; - - if (tablespaces) - *tablespaces = lappend(*tablespaces, ti); - - appendStringInfo(tblspcmapfile, "%s %s\n", - ti->oid, escapedpath.data); - - pfree(escapedpath.data); - } - FreeDir(tblspcdir); - - state->starttime = (pg_time_t) time(NULL); - } - PG_END_ENSURE_ERROR_CLEANUP(do_pg_abort_backup, DatumGetBool(false)); - - state->started_in_recovery = backup_started_in_recovery; - - /* - * Mark that the start phase has correctly finished for the backup. - */ - sessionBackupState = SESSION_BACKUP_RUNNING; -} - -/* - * Utility routine to fetch the session-level status of a backup running. - */ -SessionBackupState -get_backup_status(void) -{ - return sessionBackupState; -} - -/* - * Utility routine to reset the session-level status of a backup running. - */ -void -reset_backup_status(void) -{ - sessionBackupState = SESSION_BACKUP_NONE; -} - -/* - * do_pg_backup_stop - * - * Utility function called at the end of an online backup. It creates history - * file (if required), resets sessionBackupState and so on. It can optionally - * wait for WAL segments to be archived. - * - * "state" is filled with the information necessary to restore from this - * backup with its stop LSN (stoppoint), its timeline ID (stoptli), etc. - * - * It is the responsibility of the caller of this function to verify the - * permissions of the calling user! - */ -void -do_pg_backup_stop(BackupState *state, bool waitforarchive) -{ - bool backup_stopped_in_recovery = false; - char histfilepath[MAXPGPATH]; - char lastxlogfilename[MAXFNAMELEN]; - char histfilename[MAXFNAMELEN]; - XLogSegNo _logSegNo; - FILE *fp; - int seconds_before_warning; - int waits = 0; - bool reported_waiting = false; - - Assert(state != NULL); - - backup_stopped_in_recovery = RecoveryInProgress(); - - /* - * During recovery, we don't need to check WAL level. Because, if WAL - * level is not sufficient, it's impossible to get here during recovery. - */ - if (!backup_stopped_in_recovery && !XLogIsNeeded()) - ereport(ERROR, - (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("WAL level not sufficient for making an online backup"), - errhint("wal_level must be set to \"replica\" or \"logical\" at server start."))); - - /* - * OK to update backup counters, forcePageWrites, and session-level lock. - * - * Note that CHECK_FOR_INTERRUPTS() must not occur while updating them. - * Otherwise they can be updated inconsistently, and which might cause - * do_pg_abort_backup() to fail. - * - * It is expected that each do_pg_backup_start() call is matched by exactly - * one do_pg_backup_stop() call. - */ - ResetXLogBackupActivity(); - - /* - * If we are taking an online backup from the standby, we confirm that the - * standby has not been promoted during the backup. - */ - if (state->started_in_recovery && !backup_stopped_in_recovery) - ereport(ERROR, - (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("the standby was promoted during online backup"), - errhint("This means that the backup being taken is corrupt " - "and should not be used. " - "Try taking another online backup."))); - - /* - * During recovery, we don't write an end-of-backup record. We assume that - * pg_control was backed up last and its minimum recovery point can be - * available as the backup end location. Since we don't have an - * end-of-backup record, we use the pg_control value to check whether - * we've reached the end of backup when starting recovery from this - * backup. We have no way of checking if pg_control wasn't backed up last - * however. - * - * We don't force a switch to new WAL file but it is still possible to - * wait for all the required files to be archived if waitforarchive is - * true. This is okay if we use the backup to start a standby and fetch - * the missing WAL using streaming replication. But in the case of an - * archive recovery, a user should set waitforarchive to true and wait for - * them to be archived to ensure that all the required files are - * available. - * - * We return the current minimum recovery point as the backup end - * location. Note that it can be greater than the exact backup end - * location if the minimum recovery point is updated after the backup of - * pg_control. This is harmless for current uses. - * - * XXX currently a backup history file is for informational and debug - * purposes only. It's not essential for an online backup. Furthermore, - * even if it's created, it will not be archived during recovery because - * an archiver is not invoked. So it doesn't seem worthwhile to write a - * backup history file during recovery. - */ - if (backup_stopped_in_recovery) - { - XLogRecPtr recptr; - - /* - * Check to see if all WAL replayed during online backup contain - * full-page writes. - */ - recptr = GetlastFpwDisableRecPtr(); - - if (state->startpoint <= recptr) - ereport(ERROR, - (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("WAL generated with full_page_writes=off was replayed " - "during online backup"), - errhint("This means that the backup being taken on the standby " - "is corrupt and should not be used. " - "Enable full_page_writes and run CHECKPOINT on the primary, " - "and then try an online backup again."))); - - GetminRecoveryPoint(&state->stoppoint, &state->stoptli); - } - else - { - char *history_file; - - /* - * Write the backup-end xlog record - */ - XLogBeginInsert(); - XLogRegisterData((char *) (&state->startpoint), - sizeof(state->startpoint)); - state->stoppoint = XLogInsert(RM_XLOG_ID, XLOG_BACKUP_END); - - /* - * Given that we're not in recovery, InsertTimeLineID is set and can't - * change, so we can read it without a lock. - */ - state->stoptli = GetWALInsertionTimeLine(); - - /* - * Force a switch to a new xlog segment file, so that the backup is - * valid as soon as archiver moves out the current segment file. - */ - RequestXLogSwitch(false); - - state->stoptime = (pg_time_t) time(NULL); - - /* - * Write the backup history file - */ - XLByteToSeg(state->startpoint, _logSegNo, wal_segment_size); - BackupHistoryFilePath(histfilepath, state->stoptli, _logSegNo, - state->startpoint, wal_segment_size); - fp = AllocateFile(histfilepath, "w"); - if (!fp) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not create file \"%s\": %m", - histfilepath))); - - /* Build and save the contents of the backup history file */ - history_file = build_backup_content(state, true); - fprintf(fp, "%s", history_file); - pfree(history_file); - - if (fflush(fp) || ferror(fp) || FreeFile(fp)) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not write file \"%s\": %m", - histfilepath))); - - /* - * Clean out any no-longer-needed history files. As a side effect, - * this will post a .ready file for the newly created history file, - * notifying the archiver that history file may be archived - * immediately. - */ - CleanupBackupHistory(); - } - - /* - * If archiving is enabled, wait for all the required WAL files to be - * archived before returning. If archiving isn't enabled, the required WAL - * needs to be transported via streaming replication (hopefully with - * wal_keep_size set high enough), or some more exotic mechanism like - * polling and copying files from pg_wal with script. We have no knowledge - * of those mechanisms, so it's up to the user to ensure that he gets all - * the required WAL. - * - * We wait until both the last WAL file filled during backup and the - * history file have been archived, and assume that the alphabetic sorting - * property of the WAL files ensures any earlier WAL files are safely - * archived as well. - * - * We wait forever, since archive_command is supposed to work and we - * assume the admin wanted his backup to work completely. If you don't - * wish to wait, then either waitforarchive should be passed in as false, - * or you can set statement_timeout. Also, some notices are issued to - * clue in anyone who might be doing this interactively. - */ - - if (waitforarchive && - ((!backup_stopped_in_recovery && XLogArchivingActive()) || - (backup_stopped_in_recovery && XLogArchivingAlways()))) - { - XLByteToPrevSeg(state->stoppoint, _logSegNo, wal_segment_size); - XLogFileName(lastxlogfilename, state->stoptli, _logSegNo, - wal_segment_size); - - XLByteToSeg(state->startpoint, _logSegNo, wal_segment_size); - BackupHistoryFileName(histfilename, state->stoptli, _logSegNo, - state->startpoint, wal_segment_size); - - seconds_before_warning = 60; - waits = 0; - - while (XLogArchiveIsBusy(lastxlogfilename) || - XLogArchiveIsBusy(histfilename)) - { - CHECK_FOR_INTERRUPTS(); - - if (!reported_waiting && waits > 5) - { - ereport(NOTICE, - (errmsg("base backup done, waiting for required WAL segments to be archived"))); - reported_waiting = true; - } - - (void) WaitLatch(MyLatch, - WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, - 1000L, - WAIT_EVENT_BACKUP_WAIT_WAL_ARCHIVE); - ResetLatch(MyLatch); - - if (++waits >= seconds_before_warning) - { - seconds_before_warning *= 2; /* This wraps in >10 years... */ - ereport(WARNING, - (errmsg("still waiting for all required WAL segments to be archived (%d seconds elapsed)", - waits), - errhint("Check that your archive_command is executing properly. " - "You can safely cancel this backup, " - "but the database backup will not be usable without all the WAL segments."))); - } - } - - ereport(NOTICE, - (errmsg("all required WAL segments have been archived"))); - } - else if (waitforarchive) - ereport(NOTICE, - (errmsg("WAL archiving is not enabled; you must ensure that all required WAL segments are copied through other means to complete the backup"))); -} - - -/* - * do_pg_abort_backup: abort a running backup - * - * This does just the most basic steps of do_pg_backup_stop(), by taking the - * system out of backup mode, thus making it a lot more safe to call from - * an error handler. - * - * The caller can pass 'arg' as 'true' or 'false' to control whether a warning - * is emitted. - * - * NB: This gets used as a before_shmem_exit handler, hence the odd-looking - * signature. - */ -void -do_pg_abort_backup(int code, Datum arg) -{ - bool emit_warning = DatumGetBool(arg); - - /* - * Quick exit if session does not have a running backup. - */ - if (sessionBackupState != SESSION_BACKUP_RUNNING) - return; - - ResetXLogBackupActivity(); - - if (emit_warning) - ereport(WARNING, - (errmsg("aborting backup due to backend exiting before pg_backup_stop was called"))); -} - -/* - * Register a handler that will warn about unterminated backups at end of - * session, unless this has already been done. - */ -void -register_persistent_abort_backup_handler(void) -{ - static bool already_done = false; - - if (already_done) - return; - before_shmem_exit(do_pg_abort_backup, DatumGetBool(true)); - already_done = true; -} - /* * Get the checkpoint location. */ diff --git a/src/backend/access/transam/xlogbackup.c b/src/backend/access/transam/xlogbackup.c index 90b5273b02..085ca93928 100644 --- a/src/backend/access/transam/xlogbackup.c +++ b/src/backend/access/transam/xlogbackup.c @@ -13,9 +13,32 @@ #include "postgres.h" +#include <time.h> +#include <unistd.h> + #include "access/xlog.h" #include "access/xlog_internal.h" +#include "access/xlogarchive.h" #include "access/xlogbackup.h" +#include "access/xloginsert.h" +#include "backup/basebackup.h" +#include "catalog/pg_control.h" +#include "common/file_utils.h" +#include "miscadmin.h" +#include "postmaster/bgwriter.h" +#include "storage/fd.h" +#include "storage/ipc.h" +#include "storage/latch.h" +#include "storage/lwlock.h" +#include "utils/wait_event.h" + +/* + * Session status of running backup, used for sanity checks in SQL-callable + * functions to start and stop backups. + */ +static SessionBackupState sessionBackupState = SESSION_BACKUP_NONE; + +static void CleanupBackupHistory(void); /* * Build contents for backup_label or backup history file. @@ -82,3 +105,633 @@ build_backup_content(BackupState *state, bool ishistoryfile) return data; } + +/* + * Utility routine to fetch the session-level status of a backup running. + */ +SessionBackupState +get_backup_status(void) +{ + return sessionBackupState; +} + +/* + * Utility routine to reset the session-level status of a backup running. + */ +void +reset_backup_status(void) +{ + sessionBackupState = SESSION_BACKUP_NONE; +} + +/* + * Remove previous backup history files. This also retries creation of + * .ready files for any backup history files for which XLogArchiveNotify + * failed earlier. + */ +static void +CleanupBackupHistory(void) +{ + DIR *xldir; + struct dirent *xlde; + char path[MAXPGPATH + sizeof(XLOGDIR)]; + + xldir = AllocateDir(XLOGDIR); + + while ((xlde = ReadDir(xldir, XLOGDIR)) != NULL) + { + if (IsBackupHistoryFileName(xlde->d_name)) + { + if (XLogArchiveCheckDone(xlde->d_name)) + { + elog(DEBUG2, "removing WAL backup history file \"%s\"", + xlde->d_name); + snprintf(path, sizeof(path), XLOGDIR "/%s", xlde->d_name); + unlink(path); + XLogArchiveCleanup(xlde->d_name); + } + } + } + + FreeDir(xldir); +} + +/* + * do_pg_backup_start is the workhorse of the user-visible pg_backup_start() + * function. It creates the necessary starting checkpoint and constructs the + * backup state and tablespace map. + * + * Input parameters are "state" (the backup state), "fast" (if true, we do + * the checkpoint in immediate mode to make it faster), and "tablespaces" + * (if non-NULL, indicates a list of tablespaceinfo structs describing the + * cluster's tablespaces.). + * + * The tablespace map contents are appended to passed-in parameter + * tablespace_map and the caller is responsible for including it in the backup + * archive as 'tablespace_map'. The tablespace_map file is required mainly for + * tar format in windows as native windows utilities are not able to create + * symlinks while extracting files from tar. However for consistency and + * platform-independence, we do it the same way everywhere. + * + * It fills in "state" with the information required for the backup, such + * as the minimum WAL location that must be present to restore from this + * backup (starttli) and the corresponding timeline ID (starttli). + * + * Every successfully started backup must be stopped by calling + * do_pg_backup_stop() or do_pg_abort_backup(). There can be many + * backups active at the same time. + * + * It is the responsibility of the caller of this function to verify the + * permissions of the calling user! + */ +void +do_pg_backup_start(const char *backupidstr, bool fast, List **tablespaces, + BackupState *state, StringInfo tblspcmapfile) +{ + bool backup_started_in_recovery = false; + + Assert(state != NULL); + backup_started_in_recovery = RecoveryInProgress(); + + /* + * During recovery, we don't need to check WAL level. Because, if WAL + * level is not sufficient, it's impossible to get here during recovery. + */ + if (!backup_started_in_recovery && !XLogIsNeeded()) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("WAL level not sufficient for making an online backup"), + errhint("wal_level must be set to \"replica\" or \"logical\" at server start."))); + + if (strlen(backupidstr) > MAXPGPATH) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("backup label too long (max %d bytes)", + MAXPGPATH))); + + memcpy(state->name, backupidstr, strlen(backupidstr)); + + /* + * Mark backup active in shared memory. We must do full-page WAL writes + * during an on-line backup even if not doing so at other times, because + * it's quite possible for the backup dump to obtain a "torn" (partially + * written) copy of a database page if it reads the page concurrently with + * our write to the same page. This can be fixed as long as the first + * write to the page in the WAL sequence is a full-page write. Hence, we + * turn on forcePageWrites and then force a CHECKPOINT, to ensure there + * are no dirty pages in shared memory that might get dumped while the + * backup is in progress without having a corresponding WAL record. (Once + * the backup is complete, we need not force full-page writes anymore, + * since we expect that any pages not modified during the backup interval + * must have been correctly captured by the backup.) + * + * Note that forcePageWrites has no effect during an online backup from + * the standby. + * + * We must hold all the insertion locks to change the value of + * forcePageWrites, to ensure adequate interlocking against + * XLogInsertRecord(). + */ + SetXLogBackupActivity(); + + /* Ensure we release forcePageWrites if fail below */ + PG_ENSURE_ERROR_CLEANUP(do_pg_abort_backup, DatumGetBool(false)); + { + bool gotUniqueStartpoint = false; + DIR *tblspcdir; + struct dirent *de; + tablespaceinfo *ti; + int datadirpathlen; + + /* + * Force an XLOG file switch before the checkpoint, to ensure that the + * WAL segment the checkpoint is written to doesn't contain pages with + * old timeline IDs. That would otherwise happen if you called + * pg_backup_start() right after restoring from a PITR archive: the + * first WAL segment containing the startup checkpoint has pages in + * the beginning with the old timeline ID. That can cause trouble at + * recovery: we won't have a history file covering the old timeline if + * pg_wal directory was not included in the base backup and the WAL + * archive was cleared too before starting the backup. + * + * This also ensures that we have emitted a WAL page header that has + * XLP_BKP_REMOVABLE off before we emit the checkpoint record. + * Therefore, if a WAL archiver (such as pglesslog) is trying to + * compress out removable backup blocks, it won't remove any that + * occur after this point. + * + * During recovery, we skip forcing XLOG file switch, which means that + * the backup taken during recovery is not available for the special + * recovery case described above. + */ + if (!backup_started_in_recovery) + RequestXLogSwitch(false); + + do + { + bool checkpointfpw; + + /* + * Force a CHECKPOINT. Aside from being necessary to prevent torn + * page problems, this guarantees that two successive backup runs + * will have different checkpoint positions and hence different + * history file names, even if nothing happened in between. + * + * During recovery, establish a restartpoint if possible. We use + * the last restartpoint as the backup starting checkpoint. This + * means that two successive backup runs can have same checkpoint + * positions. + * + * Since the fact that we are executing do_pg_backup_start() + * during recovery means that checkpointer is running, we can use + * RequestCheckpoint() to establish a restartpoint. + * + * We use CHECKPOINT_IMMEDIATE only if requested by user (via + * passing fast = true). Otherwise this can take awhile. + */ + RequestCheckpoint(CHECKPOINT_FORCE | CHECKPOINT_WAIT | + (fast ? CHECKPOINT_IMMEDIATE : 0)); + + /* + * Now we need to fetch the checkpoint record location, and also + * its REDO pointer. The oldest point in WAL that would be needed + * to restore starting from the checkpoint is precisely the REDO + * pointer. + */ + GetCheckpointLocation(&state->checkpointloc, &state->startpoint, + &state->starttli, &checkpointfpw); + + if (backup_started_in_recovery) + { + XLogRecPtr recptr; + + /* + * Check to see if all WAL replayed during online backup + * (i.e., since last restartpoint used as backup starting + * checkpoint) contain full-page writes. + */ + recptr = GetlastFpwDisableRecPtr(); + + if (!checkpointfpw || state->startpoint <= recptr) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("WAL generated with full_page_writes=off was replayed " + "since last restartpoint"), + errhint("This means that the backup being taken on the standby " + "is corrupt and should not be used. " + "Enable full_page_writes and run CHECKPOINT on the primary, " + "and then try an online backup again."))); + + /* + * During recovery, since we don't use the end-of-backup WAL + * record and don't write the backup history file, the + * starting WAL location doesn't need to be unique. This means + * that two base backups started at the same time might use + * the same checkpoint as starting locations. + */ + gotUniqueStartpoint = true; + } + + /* + * If two base backups are started at the same time (in WAL sender + * processes), we need to make sure that they use different + * checkpoints as starting locations, because we use the starting + * WAL location as a unique identifier for the base backup in the + * end-of-backup WAL record and when we write the backup history + * file. Perhaps it would be better generate a separate unique ID + * for each backup instead of forcing another checkpoint, but + * taking a checkpoint right after another is not that expensive + * either because only few buffers have been dirtied yet. + */ + gotUniqueStartpoint = SetlastBackupStart(state->startpoint); + } while (!gotUniqueStartpoint); + + /* + * Construct tablespace_map file. + */ + datadirpathlen = strlen(DataDir); + + /* Collect information about all tablespaces */ + tblspcdir = AllocateDir("pg_tblspc"); + while ((de = ReadDir(tblspcdir, "pg_tblspc")) != NULL) + { + char fullpath[MAXPGPATH + 10]; + char linkpath[MAXPGPATH]; + char *relpath = NULL; + int rllen; + StringInfoData escapedpath; + char *s; + + /* Skip anything that doesn't look like a tablespace */ + if (strspn(de->d_name, "0123456789") != strlen(de->d_name)) + continue; + + snprintf(fullpath, sizeof(fullpath), "pg_tblspc/%s", de->d_name); + + /* + * Skip anything that isn't a symlink/junction. For testing only, + * we sometimes use allow_in_place_tablespaces to create + * directories directly under pg_tblspc, which would fail below. + */ + if (get_dirent_type(fullpath, de, false, ERROR) != PGFILETYPE_LNK) + continue; + + rllen = readlink(fullpath, linkpath, sizeof(linkpath)); + if (rllen < 0) + { + ereport(WARNING, + (errmsg("could not read symbolic link \"%s\": %m", + fullpath))); + continue; + } + else if (rllen >= sizeof(linkpath)) + { + ereport(WARNING, + (errmsg("symbolic link \"%s\" target is too long", + fullpath))); + continue; + } + linkpath[rllen] = '\0'; + + /* + * Build a backslash-escaped version of the link path to include + * in the tablespace map file. + */ + initStringInfo(&escapedpath); + for (s = linkpath; *s; s++) + { + if (*s == '\n' || *s == '\r' || *s == '\\') + appendStringInfoChar(&escapedpath, '\\'); + appendStringInfoChar(&escapedpath, *s); + } + + /* + * Relpath holds the relative path of the tablespace directory + * when it's located within PGDATA, or NULL if it's located + * elsewhere. + */ + if (rllen > datadirpathlen && + strncmp(linkpath, DataDir, datadirpathlen) == 0 && + IS_DIR_SEP(linkpath[datadirpathlen])) + relpath = linkpath + datadirpathlen + 1; + + ti = palloc(sizeof(tablespaceinfo)); + ti->oid = pstrdup(de->d_name); + ti->path = pstrdup(linkpath); + ti->rpath = relpath ? pstrdup(relpath) : NULL; + ti->size = -1; + + if (tablespaces) + *tablespaces = lappend(*tablespaces, ti); + + appendStringInfo(tblspcmapfile, "%s %s\n", + ti->oid, escapedpath.data); + + pfree(escapedpath.data); + } + FreeDir(tblspcdir); + + state->starttime = (pg_time_t) time(NULL); + } + PG_END_ENSURE_ERROR_CLEANUP(do_pg_abort_backup, DatumGetBool(false)); + + state->started_in_recovery = backup_started_in_recovery; + + /* + * Mark that the start phase has correctly finished for the backup. + */ + sessionBackupState = SESSION_BACKUP_RUNNING; +} + +/* + * do_pg_backup_stop + * + * Utility function called at the end of an online backup. It creates history + * file (if required), resets sessionBackupState and so on. It can optionally + * wait for WAL segments to be archived. + * + * "state" is filled with the information necessary to restore from this + * backup with its stop LSN (stoppoint), its timeline ID (stoptli), etc. + * + * It is the responsibility of the caller of this function to verify the + * permissions of the calling user! + */ +void +do_pg_backup_stop(BackupState *state, bool waitforarchive) +{ + bool backup_stopped_in_recovery = false; + char histfilepath[MAXPGPATH]; + char lastxlogfilename[MAXFNAMELEN]; + char histfilename[MAXFNAMELEN]; + XLogSegNo _logSegNo; + FILE *fp; + int seconds_before_warning; + int waits = 0; + bool reported_waiting = false; + + Assert(state != NULL); + + backup_stopped_in_recovery = RecoveryInProgress(); + + /* + * During recovery, we don't need to check WAL level. Because, if WAL + * level is not sufficient, it's impossible to get here during recovery. + */ + if (!backup_stopped_in_recovery && !XLogIsNeeded()) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("WAL level not sufficient for making an online backup"), + errhint("wal_level must be set to \"replica\" or \"logical\" at server start."))); + + /* + * OK to update backup counters, forcePageWrites, and session-level lock. + * + * Note that CHECK_FOR_INTERRUPTS() must not occur while updating them. + * Otherwise they can be updated inconsistently, and which might cause + * do_pg_abort_backup() to fail. + * + * It is expected that each do_pg_backup_start() call is matched by exactly + * one do_pg_backup_stop() call. + */ + ResetXLogBackupActivity(); + + /* + * If we are taking an online backup from the standby, we confirm that the + * standby has not been promoted during the backup. + */ + if (state->started_in_recovery && !backup_stopped_in_recovery) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("the standby was promoted during online backup"), + errhint("This means that the backup being taken is corrupt " + "and should not be used. " + "Try taking another online backup."))); + + /* + * During recovery, we don't write an end-of-backup record. We assume that + * pg_control was backed up last and its minimum recovery point can be + * available as the backup end location. Since we don't have an + * end-of-backup record, we use the pg_control value to check whether + * we've reached the end of backup when starting recovery from this + * backup. We have no way of checking if pg_control wasn't backed up last + * however. + * + * We don't force a switch to new WAL file but it is still possible to + * wait for all the required files to be archived if waitforarchive is + * true. This is okay if we use the backup to start a standby and fetch + * the missing WAL using streaming replication. But in the case of an + * archive recovery, a user should set waitforarchive to true and wait for + * them to be archived to ensure that all the required files are + * available. + * + * We return the current minimum recovery point as the backup end + * location. Note that it can be greater than the exact backup end + * location if the minimum recovery point is updated after the backup of + * pg_control. This is harmless for current uses. + * + * XXX currently a backup history file is for informational and debug + * purposes only. It's not essential for an online backup. Furthermore, + * even if it's created, it will not be archived during recovery because + * an archiver is not invoked. So it doesn't seem worthwhile to write a + * backup history file during recovery. + */ + if (backup_stopped_in_recovery) + { + XLogRecPtr recptr; + + /* + * Check to see if all WAL replayed during online backup contain + * full-page writes. + */ + recptr = GetlastFpwDisableRecPtr(); + + if (state->startpoint <= recptr) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("WAL generated with full_page_writes=off was replayed " + "during online backup"), + errhint("This means that the backup being taken on the standby " + "is corrupt and should not be used. " + "Enable full_page_writes and run CHECKPOINT on the primary, " + "and then try an online backup again."))); + + GetminRecoveryPoint(&state->stoppoint, &state->stoptli); + } + else + { + char *history_file; + + /* + * Write the backup-end xlog record + */ + XLogBeginInsert(); + XLogRegisterData((char *) (&state->startpoint), + sizeof(state->startpoint)); + state->stoppoint = XLogInsert(RM_XLOG_ID, XLOG_BACKUP_END); + + /* + * Given that we're not in recovery, InsertTimeLineID is set and can't + * change, so we can read it without a lock. + */ + state->stoptli = GetWALInsertionTimeLine(); + + /* + * Force a switch to a new xlog segment file, so that the backup is + * valid as soon as archiver moves out the current segment file. + */ + RequestXLogSwitch(false); + + state->stoptime = (pg_time_t) time(NULL); + + /* + * Write the backup history file + */ + XLByteToSeg(state->startpoint, _logSegNo, wal_segment_size); + BackupHistoryFilePath(histfilepath, state->stoptli, _logSegNo, + state->startpoint, wal_segment_size); + fp = AllocateFile(histfilepath, "w"); + if (!fp) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not create file \"%s\": %m", + histfilepath))); + + /* Build and save the contents of the backup history file */ + history_file = build_backup_content(state, true); + fprintf(fp, "%s", history_file); + pfree(history_file); + + if (fflush(fp) || ferror(fp) || FreeFile(fp)) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not write file \"%s\": %m", + histfilepath))); + + /* + * Clean out any no-longer-needed history files. As a side effect, + * this will post a .ready file for the newly created history file, + * notifying the archiver that history file may be archived + * immediately. + */ + CleanupBackupHistory(); + } + + /* + * If archiving is enabled, wait for all the required WAL files to be + * archived before returning. If archiving isn't enabled, the required WAL + * needs to be transported via streaming replication (hopefully with + * wal_keep_size set high enough), or some more exotic mechanism like + * polling and copying files from pg_wal with script. We have no knowledge + * of those mechanisms, so it's up to the user to ensure that he gets all + * the required WAL. + * + * We wait until both the last WAL file filled during backup and the + * history file have been archived, and assume that the alphabetic sorting + * property of the WAL files ensures any earlier WAL files are safely + * archived as well. + * + * We wait forever, since archive_command is supposed to work and we + * assume the admin wanted his backup to work completely. If you don't + * wish to wait, then either waitforarchive should be passed in as false, + * or you can set statement_timeout. Also, some notices are issued to + * clue in anyone who might be doing this interactively. + */ + + if (waitforarchive && + ((!backup_stopped_in_recovery && XLogArchivingActive()) || + (backup_stopped_in_recovery && XLogArchivingAlways()))) + { + XLByteToPrevSeg(state->stoppoint, _logSegNo, wal_segment_size); + XLogFileName(lastxlogfilename, state->stoptli, _logSegNo, + wal_segment_size); + + XLByteToSeg(state->startpoint, _logSegNo, wal_segment_size); + BackupHistoryFileName(histfilename, state->stoptli, _logSegNo, + state->startpoint, wal_segment_size); + + seconds_before_warning = 60; + waits = 0; + + while (XLogArchiveIsBusy(lastxlogfilename) || + XLogArchiveIsBusy(histfilename)) + { + CHECK_FOR_INTERRUPTS(); + + if (!reported_waiting && waits > 5) + { + ereport(NOTICE, + (errmsg("base backup done, waiting for required WAL segments to be archived"))); + reported_waiting = true; + } + + (void) WaitLatch(MyLatch, + WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, + 1000L, + WAIT_EVENT_BACKUP_WAIT_WAL_ARCHIVE); + ResetLatch(MyLatch); + + if (++waits >= seconds_before_warning) + { + seconds_before_warning *= 2; /* This wraps in >10 years... */ + ereport(WARNING, + (errmsg("still waiting for all required WAL segments to be archived (%d seconds elapsed)", + waits), + errhint("Check that your archive_command is executing properly. " + "You can safely cancel this backup, " + "but the database backup will not be usable without all the WAL segments."))); + } + } + + ereport(NOTICE, + (errmsg("all required WAL segments have been archived"))); + } + else if (waitforarchive) + ereport(NOTICE, + (errmsg("WAL archiving is not enabled; you must ensure that all required WAL segments are copied through other means to complete the backup"))); +} + +/* + * do_pg_abort_backup: abort a running backup + * + * This does just the most basic steps of do_pg_backup_stop(), by taking the + * system out of backup mode, thus making it a lot more safe to call from + * an error handler. + * + * The caller can pass 'arg' as 'true' or 'false' to control whether a warning + * is emitted. + * + * NB: This gets used as a before_shmem_exit handler, hence the odd-looking + * signature. + */ +void +do_pg_abort_backup(int code, Datum arg) +{ + bool emit_warning = DatumGetBool(arg); + + /* + * Quick exit if session does not have a running backup. + */ + if (sessionBackupState != SESSION_BACKUP_RUNNING) + return; + + ResetXLogBackupActivity(); + + if (emit_warning) + ereport(WARNING, + (errmsg("aborting backup due to backend exiting before pg_backup_stop was called"))); +} + +/* + * Register a handler that will warn about unterminated backups at end of + * session, unless this has already been done. + */ +void +register_persistent_abort_backup_handler(void) +{ + static bool already_done = false; + + if (already_done) + return; + before_shmem_exit(do_pg_abort_backup, DatumGetBool(true)); + already_done = true; +} diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h index b46adca291..d9eff0bf9a 100644 --- a/src/include/access/xlog.h +++ b/src/include/access/xlog.h @@ -11,7 +11,6 @@ #ifndef XLOG_H #define XLOG_H -#include "access/xlogbackup.h" #include "access/xlogdefs.h" #include "datatype/timestamp.h" #include "lib/stringinfo.h" @@ -258,35 +257,6 @@ extern void SetInstallXLogFileSegmentActive(void); extern bool IsInstallXLogFileSegmentActive(void); extern void XLogShutdownWalRcv(void); -/* - * Routines to start, stop, and get status of a base backup. - */ - -/* - * Session-level status of base backups - * - * This is used in parallel with the shared memory status to control parallel - * execution of base backup functions for a given session, be it a backend - * dedicated to replication or a normal backend connected to a database. The - * update of the session-level status happens at the same time as the shared - * memory counters to keep a consistent global and local state of the backups - * running. - */ -typedef enum SessionBackupState -{ - SESSION_BACKUP_NONE, - SESSION_BACKUP_RUNNING, -} SessionBackupState; - -extern void do_pg_backup_start(const char *backupidstr, bool fast, - List **tablespaces, BackupState *state, - StringInfo tblspcmapfile); -extern void do_pg_backup_stop(BackupState *state, bool waitforarchive); -extern void do_pg_abort_backup(int code, Datum arg); -extern void register_persistent_abort_backup_handler(void); -extern SessionBackupState get_backup_status(void); -extern void reset_backup_status(void); - /* File path names (all relative to $PGDATA) */ #define RECOVERY_SIGNAL_FILE "recovery.signal" #define STANDBY_SIGNAL_FILE "standby.signal" diff --git a/src/include/access/xlogbackup.h b/src/include/access/xlogbackup.h index 8ec3d88b0a..f888d5602d 100644 --- a/src/include/access/xlogbackup.h +++ b/src/include/access/xlogbackup.h @@ -15,6 +15,7 @@ #define XLOG_BACKUP_H #include "access/xlogdefs.h" +#include "nodes/pg_list.h" #include "pgtime.h" /* Structure to hold backup state. */ @@ -38,4 +39,33 @@ typedef struct BackupState extern char *build_backup_content(BackupState *state, bool ishistoryfile); +/* + * Routines to start, stop, and get status of a base backup. + */ + +/* + * Session-level status of base backups + * + * This is used in parallel with the shared memory status to control parallel + * execution of base backup functions for a given session, be it a backend + * dedicated to replication or a normal backend connected to a database. The + * update of the session-level status happens at the same time as the shared + * memory counters to keep a consistent global and local state of the backups + * running. + */ +typedef enum SessionBackupState +{ + SESSION_BACKUP_NONE, + SESSION_BACKUP_RUNNING, +} SessionBackupState; + +extern void do_pg_backup_start(const char *backupidstr, bool fast, + List **tablespaces, BackupState *state, + StringInfo tblspcmapfile); +extern void do_pg_backup_stop(BackupState *state, bool waitforarchive); +extern void do_pg_abort_backup(int code, Datum arg); +extern void register_persistent_abort_backup_handler(void); +extern SessionBackupState get_backup_status(void); +extern void reset_backup_status(void); + #endif /* XLOG_BACKUP_H */ -- 2.34.1
From efa1393e63d44931322c9efc33823f917438bf17 Mon Sep 17 00:00:00 2001 From: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com> Date: Thu, 13 Oct 2022 13:17:14 +0000 Subject: [PATCH v5] Move backup-related code from xlogfuncs.c to xlogbackup.c --- src/backend/access/transam/xlogbackup.c | 134 +++++++++++++++++++++++- src/backend/access/transam/xlogfuncs.c | 131 ----------------------- 2 files changed, 133 insertions(+), 132 deletions(-) diff --git a/src/backend/access/transam/xlogbackup.c b/src/backend/access/transam/xlogbackup.c index 085ca93928..be378f97ad 100644 --- a/src/backend/access/transam/xlogbackup.c +++ b/src/backend/access/transam/xlogbackup.c @@ -1,7 +1,7 @@ /*------------------------------------------------------------------------- * * xlogbackup.c - * Internal routines for base backups. + * Internal routines and SQL-callable functions for base backups. * * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California @@ -24,12 +24,16 @@ #include "backup/basebackup.h" #include "catalog/pg_control.h" #include "common/file_utils.h" +#include "funcapi.h" #include "miscadmin.h" #include "postmaster/bgwriter.h" #include "storage/fd.h" #include "storage/ipc.h" #include "storage/latch.h" #include "storage/lwlock.h" +#include "utils/builtins.h" +#include "utils/memutils.h" +#include "utils/pg_lsn.h" #include "utils/wait_event.h" /* @@ -38,6 +42,12 @@ */ static SessionBackupState sessionBackupState = SESSION_BACKUP_NONE; +/* + * Session-level backup-related variables for SQL-callable functions. + */ +static BackupState *backup_state = NULL; +static StringInfo tablespace_map = NULL; + static void CleanupBackupHistory(void); /* @@ -735,3 +745,125 @@ register_persistent_abort_backup_handler(void) before_shmem_exit(do_pg_abort_backup, DatumGetBool(true)); already_done = true; } + +/* + * pg_backup_start: set up for taking an on-line backup dump + * + * Essentially what this does is to create the contents required for the + * backup_label file and the tablespace map. + * + * Permission checking for this function is managed through the normal + * GRANT system. + */ +Datum +pg_backup_start(PG_FUNCTION_ARGS) +{ + text *backupid = PG_GETARG_TEXT_PP(0); + bool fast = PG_GETARG_BOOL(1); + char *backupidstr; + SessionBackupState status = get_backup_status(); + MemoryContext oldcontext; + + backupidstr = text_to_cstring(backupid); + + if (status == SESSION_BACKUP_RUNNING) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("a backup is already in progress in this session"))); + + /* + * backup_state and tablespace_map need to be long-lived as they are used + * in pg_backup_stop(). + */ + oldcontext = MemoryContextSwitchTo(TopMemoryContext); + + /* Allocate backup state or reset it, if it comes from a previous run */ + if (backup_state == NULL) + backup_state = (BackupState *) palloc0(sizeof(BackupState)); + else + MemSet(backup_state, 0, sizeof(BackupState)); + + /* + * tablespace_map may have been created in a previous backup, so take this + * occasion to clean it. + */ + if (tablespace_map != NULL) + { + pfree(tablespace_map->data); + pfree(tablespace_map); + tablespace_map = NULL; + } + + tablespace_map = makeStringInfo(); + MemoryContextSwitchTo(oldcontext); + + register_persistent_abort_backup_handler(); + do_pg_backup_start(backupidstr, fast, NULL, backup_state, tablespace_map); + + PG_RETURN_LSN(backup_state->startpoint); +} + +/* + * pg_backup_stop: finish taking an on-line backup. + * + * The first parameter (variable 'waitforarchive'), which is optional, + * allows the user to choose if they want to wait for the WAL to be archived + * or if we should just return as soon as the WAL record is written. + * + * This function stops an in-progress backup, creates backup_label contents and + * it returns the backup stop LSN, backup_label and tablespace_map contents. + * + * The backup_label contains the user-supplied label string (typically this + * would be used to tell where the backup dump will be stored), the starting + * time, starting WAL location for the dump and so on. It is the caller's + * responsibility to write the backup_label and tablespace_map files in the + * data folder that will be restored from this backup. + * + * Permission checking for this function is managed through the normal + * GRANT system. + */ +Datum +pg_backup_stop(PG_FUNCTION_ARGS) +{ +#define PG_BACKUP_STOP_V2_COLS 3 + TupleDesc tupdesc; + Datum values[PG_BACKUP_STOP_V2_COLS] = {0}; + bool nulls[PG_BACKUP_STOP_V2_COLS] = {0}; + bool waitforarchive = PG_GETARG_BOOL(0); + char *backup_label; + SessionBackupState status = get_backup_status(); + + /* Initialize attributes information in the tuple descriptor */ + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + if (status != SESSION_BACKUP_RUNNING) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("backup is not in progress"), + errhint("Did you call pg_backup_start()?"))); + + Assert(backup_state != NULL); + Assert(tablespace_map != NULL); + + /* Stop the backup */ + do_pg_backup_stop(backup_state, waitforarchive); + + /* Build the contents of backup_label */ + backup_label = build_backup_content(backup_state, false); + + values[0] = LSNGetDatum(backup_state->stoppoint); + values[1] = CStringGetTextDatum(backup_label); + values[2] = CStringGetTextDatum(tablespace_map->data); + + /* Deallocate backup-related variables */ + pfree(backup_state); + backup_state = NULL; + pfree(tablespace_map->data); + pfree(tablespace_map); + tablespace_map = NULL; + pfree(backup_label); + + /* Returns the record as Datum */ + PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); +} diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c index a801a94fe8..bd049d0e6d 100644 --- a/src/backend/access/transam/xlogfuncs.c +++ b/src/backend/access/transam/xlogfuncs.c @@ -20,7 +20,6 @@ #include "access/htup_details.h" #include "access/xlog_internal.h" -#include "access/xlogbackup.h" #include "access/xlogrecovery.h" #include "access/xlogutils.h" #include "catalog/pg_type.h" @@ -33,141 +32,11 @@ #include "storage/smgr.h" #include "utils/builtins.h" #include "utils/guc.h" -#include "utils/memutils.h" #include "utils/numeric.h" #include "utils/pg_lsn.h" #include "utils/timestamp.h" #include "utils/tuplestore.h" -/* - * Backup-related variables. - */ -static BackupState *backup_state = NULL; -static StringInfo tablespace_map = NULL; - -/* - * pg_backup_start: set up for taking an on-line backup dump - * - * Essentially what this does is to create the contents required for the - * backup_label file and the tablespace map. - * - * Permission checking for this function is managed through the normal - * GRANT system. - */ -Datum -pg_backup_start(PG_FUNCTION_ARGS) -{ - text *backupid = PG_GETARG_TEXT_PP(0); - bool fast = PG_GETARG_BOOL(1); - char *backupidstr; - SessionBackupState status = get_backup_status(); - MemoryContext oldcontext; - - backupidstr = text_to_cstring(backupid); - - if (status == SESSION_BACKUP_RUNNING) - ereport(ERROR, - (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("a backup is already in progress in this session"))); - - /* - * backup_state and tablespace_map need to be long-lived as they are used - * in pg_backup_stop(). - */ - oldcontext = MemoryContextSwitchTo(TopMemoryContext); - - /* Allocate backup state or reset it, if it comes from a previous run */ - if (backup_state == NULL) - backup_state = (BackupState *) palloc0(sizeof(BackupState)); - else - MemSet(backup_state, 0, sizeof(BackupState)); - - /* - * tablespace_map may have been created in a previous backup, so take this - * occasion to clean it. - */ - if (tablespace_map != NULL) - { - pfree(tablespace_map->data); - pfree(tablespace_map); - tablespace_map = NULL; - } - - tablespace_map = makeStringInfo(); - MemoryContextSwitchTo(oldcontext); - - register_persistent_abort_backup_handler(); - do_pg_backup_start(backupidstr, fast, NULL, backup_state, tablespace_map); - - PG_RETURN_LSN(backup_state->startpoint); -} - - -/* - * pg_backup_stop: finish taking an on-line backup. - * - * The first parameter (variable 'waitforarchive'), which is optional, - * allows the user to choose if they want to wait for the WAL to be archived - * or if we should just return as soon as the WAL record is written. - * - * This function stops an in-progress backup, creates backup_label contents and - * it returns the backup stop LSN, backup_label and tablespace_map contents. - * - * The backup_label contains the user-supplied label string (typically this - * would be used to tell where the backup dump will be stored), the starting - * time, starting WAL location for the dump and so on. It is the caller's - * responsibility to write the backup_label and tablespace_map files in the - * data folder that will be restored from this backup. - * - * Permission checking for this function is managed through the normal - * GRANT system. - */ -Datum -pg_backup_stop(PG_FUNCTION_ARGS) -{ -#define PG_BACKUP_STOP_V2_COLS 3 - TupleDesc tupdesc; - Datum values[PG_BACKUP_STOP_V2_COLS] = {0}; - bool nulls[PG_BACKUP_STOP_V2_COLS] = {0}; - bool waitforarchive = PG_GETARG_BOOL(0); - char *backup_label; - SessionBackupState status = get_backup_status(); - - /* Initialize attributes information in the tuple descriptor */ - if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) - elog(ERROR, "return type must be a row type"); - - if (status != SESSION_BACKUP_RUNNING) - ereport(ERROR, - (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("backup is not in progress"), - errhint("Did you call pg_backup_start()?"))); - - Assert(backup_state != NULL); - Assert(tablespace_map != NULL); - - /* Stop the backup */ - do_pg_backup_stop(backup_state, waitforarchive); - - /* Build the contents of backup_label */ - backup_label = build_backup_content(backup_state, false); - - values[0] = LSNGetDatum(backup_state->stoppoint); - values[1] = CStringGetTextDatum(backup_label); - values[2] = CStringGetTextDatum(tablespace_map->data); - - /* Deallocate backup-related variables */ - pfree(backup_state); - backup_state = NULL; - pfree(tablespace_map->data); - pfree(tablespace_map); - tablespace_map = NULL; - pfree(backup_label); - - /* Returns the record as Datum */ - PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); -} - /* * pg_switch_wal: switch to next xlog file * -- 2.34.1