@@ -460,6 +460,29 @@ typedef union WALInsertLockPadded
460460char pad [PG_CACHE_LINE_SIZE ];
461461}WALInsertLockPadded ;
462462
463+ /*
464+ * State of an exclusive backup, necessary to control concurrent activities
465+ * across sessions when working on exclusive backups.
466+ *
467+ * EXCLUSIVE_BACKUP_NONE means that there is no exclusive backup actually
468+ * running, to be more precise pg_start_backup() is not being executed for
469+ * an exclusive backup and there is no exclusive backup in progress.
470+ * EXCLUSIVE_BACKUP_STARTING means that pg_start_backup() is starting an
471+ * exclusive backup.
472+ * EXCLUSIVE_BACKUP_IN_PROGRESS means that pg_start_backup() has finished
473+ * running and an exclusive backup is in progress. pg_stop_backup() is
474+ * needed to finish it.
475+ * EXCLUSIVE_BACKUP_STOPPING means that pg_stop_backup() is stopping an
476+ * exclusive backup.
477+ */
478+ typedef enum ExclusiveBackupState
479+ {
480+ EXCLUSIVE_BACKUP_NONE = 0 ,
481+ EXCLUSIVE_BACKUP_STARTING ,
482+ EXCLUSIVE_BACKUP_IN_PROGRESS ,
483+ EXCLUSIVE_BACKUP_STOPPING
484+ }ExclusiveBackupState ;
485+
463486/*
464487 * Shared state data for WAL insertion.
465488 */
@@ -501,13 +524,15 @@ typedef struct XLogCtlInsert
501524bool fullPageWrites ;
502525
503526/*
504- * exclusiveBackup is true if a backup started with pg_start_backup() is
505- * in progress, and nonExclusiveBackups is a counter indicating the number
506- * of streaming base backups currently in progress. forcePageWrites is set
507- * to true when either of these is non-zero. lastBackupStart is the latest
508- * checkpoint redo location used as a starting point for an online backup.
527+ * exclusiveBackupState indicates the state of an exclusive backup
528+ * (see comments of ExclusiveBackupState for more details).
529+ * nonExclusiveBackups is a counter indicating the number of streaming
530+ * base backups currently in progress. forcePageWrites is set to true
531+ * when either of these is non-zero. lastBackupStart is the latest
532+ * checkpoint redo location used as a starting point for an online
533+ * backup.
509534 */
510- bool exclusiveBackup ;
535+ ExclusiveBackupState exclusiveBackupState ;
511536int nonExclusiveBackups ;
512537XLogRecPtr lastBackupStart ;
513538
@@ -846,6 +871,7 @@ static void xlog_outrec(StringInfo buf, XLogReaderState *record);
846871#endif
847872static void xlog_outdesc (StringInfo buf ,XLogReaderState * record );
848873static void pg_start_backup_callback (int code ,Datum arg );
874+ static void pg_stop_backup_callback (int code ,Datum arg );
849875static bool read_backup_label (XLogRecPtr * checkPointLoc ,
850876bool * backupEndRequired ,bool * backupFromStandby );
851877static bool read_tablespace_map (List * * tablespaces );
@@ -9890,15 +9916,20 @@ do_pg_start_backup(const char *backupidstr, bool fast, TimeLineID *starttli_p,
98909916WALInsertLockAcquireExclusive ();
98919917if (exclusive )
98929918{
9893- if (XLogCtl -> Insert .exclusiveBackup )
9919+ /*
9920+ * At first, mark that we're now starting an exclusive backup,
9921+ * to ensure that there are no other sessions currently running
9922+ * pg_start_backup() or pg_stop_backup().
9923+ */
9924+ if (XLogCtl -> Insert .exclusiveBackupState != EXCLUSIVE_BACKUP_NONE )
98949925{
98959926WALInsertLockRelease ();
98969927ereport (ERROR ,
98979928(errcode (ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE ),
98989929errmsg ("a backup is already in progress" ),
98999930errhint ("Run pg_stop_backup() and try again." )));
99009931}
9901- XLogCtl -> Insert .exclusiveBackup = true ;
9932+ XLogCtl -> Insert .exclusiveBackupState = EXCLUSIVE_BACKUP_STARTING ;
99029933}
99039934else
99049935XLogCtl -> Insert .nonExclusiveBackups ++ ;
@@ -10153,7 +10184,7 @@ do_pg_start_backup(const char *backupidstr, bool fast, TimeLineID *starttli_p,
1015310184{
1015410185/*
1015510186 * Check for existing backup label --- implies a backup is already
10156- * running. (XXX given that we checkedexclusiveBackup above,
10187+ * running. (XXX given that we checkedexclusiveBackupState above,
1015710188 * maybe it would be OK to just unlink any such label file?)
1015810189 */
1015910190if (stat (BACKUP_LABEL_FILE ,& stat_buf )!= 0 )
@@ -10234,6 +10265,16 @@ do_pg_start_backup(const char *backupidstr, bool fast, TimeLineID *starttli_p,
1023410265}
1023510266PG_END_ENSURE_ERROR_CLEANUP (pg_start_backup_callback , (Datum )BoolGetDatum (exclusive ));
1023610267
10268+ /*
10269+ * Mark that start phase has correctly finished for an exclusive backup.
10270+ */
10271+ if (exclusive )
10272+ {
10273+ WALInsertLockAcquireExclusive ();
10274+ XLogCtl -> Insert .exclusiveBackupState = EXCLUSIVE_BACKUP_IN_PROGRESS ;
10275+ WALInsertLockRelease ();
10276+ }
10277+
1023710278/*
1023810279 * We're done. As a convenience, return the starting WAL location.
1023910280 */
@@ -10252,23 +10293,41 @@ pg_start_backup_callback(int code, Datum arg)
1025210293WALInsertLockAcquireExclusive ();
1025310294if (exclusive )
1025410295{
10255- Assert (XLogCtl -> Insert .exclusiveBackup );
10256- XLogCtl -> Insert .exclusiveBackup = false ;
10296+ Assert (XLogCtl -> Insert .exclusiveBackupState == EXCLUSIVE_BACKUP_STARTING );
10297+ XLogCtl -> Insert .exclusiveBackupState = EXCLUSIVE_BACKUP_NONE ;
1025710298}
1025810299else
1025910300{
1026010301Assert (XLogCtl -> Insert .nonExclusiveBackups > 0 );
1026110302XLogCtl -> Insert .nonExclusiveBackups -- ;
1026210303}
1026310304
10264- if (! XLogCtl -> Insert .exclusiveBackup &&
10305+ if (XLogCtl -> Insert .exclusiveBackupState == EXCLUSIVE_BACKUP_NONE &&
1026510306XLogCtl -> Insert .nonExclusiveBackups == 0 )
1026610307{
1026710308XLogCtl -> Insert .forcePageWrites = false;
1026810309}
1026910310WALInsertLockRelease ();
1027010311}
1027110312
10313+ /*
10314+ * Error cleanup callback for pg_stop_backup
10315+ */
10316+ static void
10317+ pg_stop_backup_callback (int code ,Datum arg )
10318+ {
10319+ bool exclusive = DatumGetBool (arg );
10320+
10321+ /* Update backup status on failure */
10322+ WALInsertLockAcquireExclusive ();
10323+ if (exclusive )
10324+ {
10325+ Assert (XLogCtl -> Insert .exclusiveBackupState == EXCLUSIVE_BACKUP_STOPPING );
10326+ XLogCtl -> Insert .exclusiveBackupState = EXCLUSIVE_BACKUP_IN_PROGRESS ;
10327+ }
10328+ WALInsertLockRelease ();
10329+ }
10330+
1027210331/*
1027310332 * do_pg_stop_backup is the workhorse of the user-visible pg_stop_backup()
1027410333 * function.
@@ -10331,20 +10390,91 @@ do_pg_stop_backup(char *labelfile, bool waitforarchive, TimeLineID *stoptli_p)
1033110390errmsg ("WAL level not sufficient for making an online backup" ),
1033210391errhint ("wal_level must be set to \"replica\" or \"logical\" at server start." )));
1033310392
10334- /*
10335- * OK to update backup counters and forcePageWrites
10336- */
10337- WALInsertLockAcquireExclusive ();
1033810393if (exclusive )
1033910394{
10340- if (!XLogCtl -> Insert .exclusiveBackup )
10395+ /*
10396+ * At first, mark that we're now stopping an exclusive backup,
10397+ * to ensure that there are no other sessions currently running
10398+ * pg_start_backup() or pg_stop_backup().
10399+ */
10400+ WALInsertLockAcquireExclusive ();
10401+ if (XLogCtl -> Insert .exclusiveBackupState != EXCLUSIVE_BACKUP_IN_PROGRESS )
1034110402{
1034210403WALInsertLockRelease ();
1034310404ereport (ERROR ,
1034410405(errcode (ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE ),
1034510406errmsg ("exclusive backup not in progress" )));
1034610407}
10347- XLogCtl -> Insert .exclusiveBackup = false;
10408+ XLogCtl -> Insert .exclusiveBackupState = EXCLUSIVE_BACKUP_STOPPING ;
10409+ WALInsertLockRelease ();
10410+
10411+ /*
10412+ * Remove backup_label. In case of failure, the state for an exclusive
10413+ * backup is switched back to in-progress.
10414+ */
10415+ PG_ENSURE_ERROR_CLEANUP (pg_stop_backup_callback , (Datum )BoolGetDatum (exclusive ));
10416+ {
10417+ /*
10418+ * Read the existing label file into memory.
10419+ */
10420+ struct stat statbuf ;
10421+ int r ;
10422+
10423+ if (stat (BACKUP_LABEL_FILE ,& statbuf ))
10424+ {
10425+ /* should not happen per the upper checks */
10426+ if (errno != ENOENT )
10427+ ereport (ERROR ,
10428+ (errcode_for_file_access (),
10429+ errmsg ("could not stat file \"%s\": %m" ,
10430+ BACKUP_LABEL_FILE )));
10431+ ereport (ERROR ,
10432+ (errcode (ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE ),
10433+ errmsg ("a backup is not in progress" )));
10434+ }
10435+
10436+ lfp = AllocateFile (BACKUP_LABEL_FILE ,"r" );
10437+ if (!lfp )
10438+ {
10439+ ereport (ERROR ,
10440+ (errcode_for_file_access (),
10441+ errmsg ("could not read file \"%s\": %m" ,
10442+ BACKUP_LABEL_FILE )));
10443+ }
10444+ labelfile = palloc (statbuf .st_size + 1 );
10445+ r = fread (labelfile ,statbuf .st_size ,1 ,lfp );
10446+ labelfile [statbuf .st_size ]= '\0' ;
10447+
10448+ /*
10449+ * Close and remove the backup label file
10450+ */
10451+ if (r != 1 || ferror (lfp )|| FreeFile (lfp ))
10452+ ereport (ERROR ,
10453+ (errcode_for_file_access (),
10454+ errmsg ("could not read file \"%s\": %m" ,
10455+ BACKUP_LABEL_FILE )));
10456+ if (unlink (BACKUP_LABEL_FILE )!= 0 )
10457+ ereport (ERROR ,
10458+ (errcode_for_file_access (),
10459+ errmsg ("could not remove file \"%s\": %m" ,
10460+ BACKUP_LABEL_FILE )));
10461+
10462+ /*
10463+ * Remove tablespace_map file if present, it is created only if there
10464+ * are tablespaces.
10465+ */
10466+ unlink (TABLESPACE_MAP );
10467+ }
10468+ PG_END_ENSURE_ERROR_CLEANUP (pg_stop_backup_callback , (Datum )BoolGetDatum (exclusive ));
10469+ }
10470+
10471+ /*
10472+ * OK to update backup counters and forcePageWrites
10473+ */
10474+ WALInsertLockAcquireExclusive ();
10475+ if (exclusive )
10476+ {
10477+ XLogCtl -> Insert .exclusiveBackupState = EXCLUSIVE_BACKUP_NONE ;
1034810478}
1034910479else
1035010480{
@@ -10358,66 +10488,13 @@ do_pg_stop_backup(char *labelfile, bool waitforarchive, TimeLineID *stoptli_p)
1035810488XLogCtl -> Insert .nonExclusiveBackups -- ;
1035910489}
1036010490
10361- if (! XLogCtl -> Insert .exclusiveBackup &&
10491+ if (XLogCtl -> Insert .exclusiveBackupState == EXCLUSIVE_BACKUP_NONE &&
1036210492XLogCtl -> Insert .nonExclusiveBackups == 0 )
1036310493{
1036410494XLogCtl -> Insert .forcePageWrites = false;
1036510495}
1036610496WALInsertLockRelease ();
1036710497
10368- if (exclusive )
10369- {
10370- /*
10371- * Read the existing label file into memory.
10372- */
10373- struct stat statbuf ;
10374- int r ;
10375-
10376- if (stat (BACKUP_LABEL_FILE ,& statbuf ))
10377- {
10378- if (errno != ENOENT )
10379- ereport (ERROR ,
10380- (errcode_for_file_access (),
10381- errmsg ("could not stat file \"%s\": %m" ,
10382- BACKUP_LABEL_FILE )));
10383- ereport (ERROR ,
10384- (errcode (ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE ),
10385- errmsg ("a backup is not in progress" )));
10386- }
10387-
10388- lfp = AllocateFile (BACKUP_LABEL_FILE ,"r" );
10389- if (!lfp )
10390- {
10391- ereport (ERROR ,
10392- (errcode_for_file_access (),
10393- errmsg ("could not read file \"%s\": %m" ,
10394- BACKUP_LABEL_FILE )));
10395- }
10396- labelfile = palloc (statbuf .st_size + 1 );
10397- r = fread (labelfile ,statbuf .st_size ,1 ,lfp );
10398- labelfile [statbuf .st_size ]= '\0' ;
10399-
10400- /*
10401- * Close and remove the backup label file
10402- */
10403- if (r != 1 || ferror (lfp )|| FreeFile (lfp ))
10404- ereport (ERROR ,
10405- (errcode_for_file_access (),
10406- errmsg ("could not read file \"%s\": %m" ,
10407- BACKUP_LABEL_FILE )));
10408- if (unlink (BACKUP_LABEL_FILE )!= 0 )
10409- ereport (ERROR ,
10410- (errcode_for_file_access (),
10411- errmsg ("could not remove file \"%s\": %m" ,
10412- BACKUP_LABEL_FILE )));
10413-
10414- /*
10415- * Remove tablespace_map file if present, it is created only if there
10416- * are tablespaces.
10417- */
10418- unlink (TABLESPACE_MAP );
10419- }
10420-
1042110498/*
1042210499 * Read and parse the START WAL LOCATION line (this code is pretty crude,
1042310500 * but we are not expecting any variability in the file format).
@@ -10654,7 +10731,7 @@ do_pg_abort_backup(void)
1065410731Assert (XLogCtl -> Insert .nonExclusiveBackups > 0 );
1065510732XLogCtl -> Insert .nonExclusiveBackups -- ;
1065610733
10657- if (! XLogCtl -> Insert .exclusiveBackup &&
10734+ if (XLogCtl -> Insert .exclusiveBackupState == EXCLUSIVE_BACKUP_NONE &&
1065810735XLogCtl -> Insert .nonExclusiveBackups == 0 )
1065910736{
1066010737XLogCtl -> Insert .forcePageWrites = false;