*** a/src/backend/access/transam/xlog.c --- b/src/backend/access/transam/xlog.c *************** *** 463,468 **** typedef union WALInsertLockPadded --- 463,491 ---- } WALInsertLockPadded; /* + * State of an exclusive backup, necessary to control concurrent activities + * across sessions when working on exclusive backups. + * + * EXCLUSIVE_BACKUP_NONE means that there is no exclusive backup actually + * running, to be more precise pg_start_backup() is not being executed for + * an exclusive backup and there is no exclusive backup in progress. + * EXCLUSIVE_BACKUP_STARTING means that pg_start_backup() is starting an + * exclusive backup. + * EXCLUSIVE_BACKUP_IN_PROGRESS means that pg_start_backup() has finished + * running and an exclusive backup is in progress. pg_stop_backup() is + * needed to finish it. + * EXCLUSIVE_BACKUP_STOPPING means that pg_stop_backup() is stopping an + * exclusive backup. + */ + typedef enum ExclusiveBackupState + { + EXCLUSIVE_BACKUP_NONE = 0, + EXCLUSIVE_BACKUP_STARTING, + EXCLUSIVE_BACKUP_IN_PROGRESS, + EXCLUSIVE_BACKUP_STOPPING + } ExclusiveBackupState; + + /* * Shared state data for WAL insertion. */ typedef struct XLogCtlInsert *************** *** 503,515 **** typedef struct XLogCtlInsert bool fullPageWrites; /* ! * exclusiveBackup is true if a backup started with pg_start_backup() is ! * in progress, and nonExclusiveBackups is a counter indicating the number * of streaming base backups currently in progress. forcePageWrites is set * to true when either of these is non-zero. lastBackupStart is the latest * checkpoint redo location used as a starting point for an online backup. */ ! bool exclusiveBackup; int nonExclusiveBackups; XLogRecPtr lastBackupStart; --- 526,539 ---- bool fullPageWrites; /* ! * exclusiveBackupState indicates the state of an exclusive backup ! * (see comments for ExclusiveBackupState for more detail). ! * nonExclusiveBackups is a counter indicating the number * of streaming base backups currently in progress. forcePageWrites is set * to true when either of these is non-zero. lastBackupStart is the latest * checkpoint redo location used as a starting point for an online backup. */ ! ExclusiveBackupState exclusiveBackupState; int nonExclusiveBackups; XLogRecPtr lastBackupStart; *************** *** 848,853 **** static void xlog_outrec(StringInfo buf, XLogReaderState *record); --- 872,878 ---- #endif static void xlog_outdesc(StringInfo buf, XLogReaderState *record); static void pg_start_backup_callback(int code, Datum arg); + static void pg_stop_backup_callback(int code, Datum arg); static bool read_backup_label(XLogRecPtr *checkPointLoc, bool *backupEndRequired, bool *backupFromStandby); static bool read_tablespace_map(List **tablespaces); *************** *** 9957,9963 **** do_pg_start_backup(const char *backupidstr, bool fast, TimeLineID *starttli_p, WALInsertLockAcquireExclusive(); if (exclusive) { ! if (XLogCtl->Insert.exclusiveBackup) { WALInsertLockRelease(); ereport(ERROR, --- 9982,9993 ---- WALInsertLockAcquireExclusive(); if (exclusive) { ! /* ! * At first, mark that we're now starting an exclusive backup, ! * to ensure that there are no other sessions currently running ! * pg_start_backup() or pg_stop_backup(). ! */ ! if (XLogCtl->Insert.exclusiveBackupState != EXCLUSIVE_BACKUP_NONE) { WALInsertLockRelease(); ereport(ERROR, *************** *** 9965,9971 **** do_pg_start_backup(const char *backupidstr, bool fast, TimeLineID *starttli_p, errmsg("a backup is already in progress"), errhint("Run pg_stop_backup() and try again."))); } ! XLogCtl->Insert.exclusiveBackup = true; } else XLogCtl->Insert.nonExclusiveBackups++; --- 9995,10001 ---- errmsg("a backup is already in progress"), errhint("Run pg_stop_backup() and try again."))); } ! XLogCtl->Insert.exclusiveBackupState = EXCLUSIVE_BACKUP_STARTING; } else XLogCtl->Insert.nonExclusiveBackups++; *************** *** 10220,10226 **** do_pg_start_backup(const char *backupidstr, bool fast, TimeLineID *starttli_p, { /* * Check for existing backup label --- implies a backup is already ! * running. (XXX given that we checked exclusiveBackup above, * maybe it would be OK to just unlink any such label file?) */ if (stat(BACKUP_LABEL_FILE, &stat_buf) != 0) --- 10250,10256 ---- { /* * Check for existing backup label --- implies a backup is already ! * running. (XXX given that we checked exclusiveBackupState above, * maybe it would be OK to just unlink any such label file?) */ if (stat(BACKUP_LABEL_FILE, &stat_buf) != 0) *************** *** 10302,10307 **** do_pg_start_backup(const char *backupidstr, bool fast, TimeLineID *starttli_p, --- 10332,10347 ---- PG_END_ENSURE_ERROR_CLEANUP(pg_start_backup_callback, (Datum) BoolGetDatum(exclusive)); /* + * Mark that start phase has correctly finished for an exclusive backup. + */ + if (exclusive) + { + WALInsertLockAcquireExclusive(); + XLogCtl->Insert.exclusiveBackupState = EXCLUSIVE_BACKUP_IN_PROGRESS; + WALInsertLockRelease(); + } + + /* * We're done. As a convenience, return the starting WAL location. */ if (starttli_p) *************** *** 10319,10326 **** pg_start_backup_callback(int code, Datum arg) WALInsertLockAcquireExclusive(); if (exclusive) { ! Assert(XLogCtl->Insert.exclusiveBackup); ! XLogCtl->Insert.exclusiveBackup = false; } else { --- 10359,10366 ---- WALInsertLockAcquireExclusive(); if (exclusive) { ! Assert(XLogCtl->Insert.exclusiveBackupState == EXCLUSIVE_BACKUP_STARTING); ! XLogCtl->Insert.exclusiveBackupState = EXCLUSIVE_BACKUP_NONE; } else { *************** *** 10328,10334 **** pg_start_backup_callback(int code, Datum arg) XLogCtl->Insert.nonExclusiveBackups--; } ! if (!XLogCtl->Insert.exclusiveBackup && XLogCtl->Insert.nonExclusiveBackups == 0) { XLogCtl->Insert.forcePageWrites = false; --- 10368,10374 ---- XLogCtl->Insert.nonExclusiveBackups--; } ! if (XLogCtl->Insert.exclusiveBackupState == EXCLUSIVE_BACKUP_NONE && XLogCtl->Insert.nonExclusiveBackups == 0) { XLogCtl->Insert.forcePageWrites = false; *************** *** 10337,10342 **** pg_start_backup_callback(int code, Datum arg) --- 10377,10400 ---- } /* + * Error cleanup callback for pg_stop_backup + */ + static void + pg_stop_backup_callback(int code, Datum arg) + { + bool exclusive = DatumGetBool(arg); + + /* Update backup status on failure */ + WALInsertLockAcquireExclusive(); + if (exclusive) + { + Assert(XLogCtl->Insert.exclusiveBackupState == EXCLUSIVE_BACKUP_STOPPING); + XLogCtl->Insert.exclusiveBackupState = EXCLUSIVE_BACKUP_IN_PROGRESS; + } + WALInsertLockRelease(); + } + + /* * do_pg_stop_backup is the workhorse of the user-visible pg_stop_backup() * function. * *************** *** 10398,10417 **** do_pg_stop_backup(char *labelfile, bool waitforarchive, TimeLineID *stoptli_p) 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 and forcePageWrites - */ - WALInsertLockAcquireExclusive(); if (exclusive) { ! if (!XLogCtl->Insert.exclusiveBackup) { WALInsertLockRelease(); ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("exclusive backup not in progress"))); } ! XLogCtl->Insert.exclusiveBackup = false; } else { --- 10456,10546 ---- errmsg("WAL level not sufficient for making an online backup"), errhint("wal_level must be set to \"replica\" or \"logical\" at server start."))); if (exclusive) { ! /* ! * At first, mark that we're now stopping an exclusive backup, ! * to ensure that there are no other sessions currently running ! * pg_start_backup() or pg_stop_backup(). ! */ ! WALInsertLockAcquireExclusive(); ! if (XLogCtl->Insert.exclusiveBackupState != EXCLUSIVE_BACKUP_IN_PROGRESS) { WALInsertLockRelease(); ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("exclusive backup not in progress"))); } ! XLogCtl->Insert.exclusiveBackupState = EXCLUSIVE_BACKUP_STOPPING; ! WALInsertLockRelease(); ! ! /* ! * Remove backup_label. In case of failure, the state for an exclusive ! * backup is switched back to in-progress. ! */ ! PG_ENSURE_ERROR_CLEANUP(pg_stop_backup_callback, (Datum) BoolGetDatum(exclusive)); ! { ! /* ! * Read the existing label file into memory. ! */ ! struct stat statbuf; ! int r; ! ! if (stat(BACKUP_LABEL_FILE, &statbuf)) ! { ! /* should not happen per the upper checks */ ! if (errno != ENOENT) ! ereport(ERROR, ! (errcode_for_file_access(), ! errmsg("could not stat file \"%s\": %m", ! BACKUP_LABEL_FILE))); ! ereport(ERROR, ! (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), ! errmsg("a backup is not in progress"))); ! } ! ! lfp = AllocateFile(BACKUP_LABEL_FILE, "r"); ! if (!lfp) ! { ! ereport(ERROR, ! (errcode_for_file_access(), ! errmsg("could not read file \"%s\": %m", ! BACKUP_LABEL_FILE))); ! } ! labelfile = palloc(statbuf.st_size + 1); ! r = fread(labelfile, statbuf.st_size, 1, lfp); ! labelfile[statbuf.st_size] = '\0'; ! ! /* ! * Close and remove the backup label file ! */ ! if (r != 1 || ferror(lfp) || FreeFile(lfp)) ! ereport(ERROR, ! (errcode_for_file_access(), ! errmsg("could not read file \"%s\": %m", ! BACKUP_LABEL_FILE))); ! if (unlink(BACKUP_LABEL_FILE) != 0) ! ereport(ERROR, ! (errcode_for_file_access(), ! errmsg("could not remove file \"%s\": %m", ! BACKUP_LABEL_FILE))); ! ! /* ! * Remove tablespace_map file if present, it is created only if there ! * are tablespaces. ! */ ! unlink(TABLESPACE_MAP); ! } ! PG_END_ENSURE_ERROR_CLEANUP(pg_stop_backup_callback, (Datum) BoolGetDatum(exclusive)); ! } ! ! /* ! * OK to update backup counters and forcePageWrites ! */ ! WALInsertLockAcquireExclusive(); ! if (exclusive) ! { ! XLogCtl->Insert.exclusiveBackupState = EXCLUSIVE_BACKUP_NONE; } else { *************** *** 10425,10490 **** do_pg_stop_backup(char *labelfile, bool waitforarchive, TimeLineID *stoptli_p) XLogCtl->Insert.nonExclusiveBackups--; } ! if (!XLogCtl->Insert.exclusiveBackup && XLogCtl->Insert.nonExclusiveBackups == 0) { XLogCtl->Insert.forcePageWrites = false; } WALInsertLockRelease(); - if (exclusive) - { - /* - * Read the existing label file into memory. - */ - struct stat statbuf; - int r; - - if (stat(BACKUP_LABEL_FILE, &statbuf)) - { - if (errno != ENOENT) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not stat file \"%s\": %m", - BACKUP_LABEL_FILE))); - ereport(ERROR, - (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("a backup is not in progress"))); - } - - lfp = AllocateFile(BACKUP_LABEL_FILE, "r"); - if (!lfp) - { - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not read file \"%s\": %m", - BACKUP_LABEL_FILE))); - } - labelfile = palloc(statbuf.st_size + 1); - r = fread(labelfile, statbuf.st_size, 1, lfp); - labelfile[statbuf.st_size] = '\0'; - - /* - * Close and remove the backup label file - */ - if (r != 1 || ferror(lfp) || FreeFile(lfp)) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not read file \"%s\": %m", - BACKUP_LABEL_FILE))); - if (unlink(BACKUP_LABEL_FILE) != 0) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not remove file \"%s\": %m", - BACKUP_LABEL_FILE))); - - /* - * Remove tablespace_map file if present, it is created only if there - * are tablespaces. - */ - unlink(TABLESPACE_MAP); - } - /* * Read and parse the START WAL LOCATION line (this code is pretty crude, * but we are not expecting any variability in the file format). --- 10554,10566 ---- XLogCtl->Insert.nonExclusiveBackups--; } ! if (XLogCtl->Insert.exclusiveBackupState == EXCLUSIVE_BACKUP_NONE && XLogCtl->Insert.nonExclusiveBackups == 0) { XLogCtl->Insert.forcePageWrites = false; } WALInsertLockRelease(); /* * Read and parse the START WAL LOCATION line (this code is pretty crude, * but we are not expecting any variability in the file format). *************** *** 10721,10727 **** do_pg_abort_backup(void) Assert(XLogCtl->Insert.nonExclusiveBackups > 0); XLogCtl->Insert.nonExclusiveBackups--; ! if (!XLogCtl->Insert.exclusiveBackup && XLogCtl->Insert.nonExclusiveBackups == 0) { XLogCtl->Insert.forcePageWrites = false; --- 10797,10803 ---- Assert(XLogCtl->Insert.nonExclusiveBackups > 0); XLogCtl->Insert.nonExclusiveBackups--; ! if (XLogCtl->Insert.exclusiveBackupState == EXCLUSIVE_BACKUP_NONE && XLogCtl->Insert.nonExclusiveBackups == 0) { XLogCtl->Insert.forcePageWrites = false;