Skip to content

Commit 60a8b63

Browse files
committed
Fix an assertion failure related to an exclusive backup.
Previously multiple sessions could execute pg_start_backup() and pg_stop_backup() to start and stop an exclusive backup at the same time. This could trigger the assertion failure of "FailedAssertion("!(XLogCtl->Insert.exclusiveBackup)". This happend because, even while pg_start_backup() was starting an exclusive backup, other session could run pg_stop_backup() concurrently and mark the backup as not-in-progress unconditionally. This patch introduces ExclusiveBackupState indicating the state of an exclusive backup. This state is used to ensure that there is only one session running pg_start_backup() or pg_stop_backup() at the same time, to avoid the assertion failure. Back-patch to all supported versions. Author: Michael Paquier Reviewed-By: Kyotaro Horiguchi and me Reported-By: Andreas Seltenreich Discussion: <[email protected]>
1 parent 8a70d8a commit 60a8b63

File tree

1 file changed

+150
-73
lines changed
  • src/backend/access/transam

1 file changed

+150
-73
lines changed

src/backend/access/transam/xlog.c

Lines changed: 150 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,29 @@ typedef union WALInsertLockPadded
459459
char pad[PG_CACHE_LINE_SIZE];
460460
} WALInsertLockPadded;
461461

462+
/*
463+
* State of an exclusive backup, necessary to control concurrent activities
464+
* across sessions when working on exclusive backups.
465+
*
466+
* EXCLUSIVE_BACKUP_NONE means that there is no exclusive backup actually
467+
* running, to be more precise pg_start_backup() is not being executed for
468+
* an exclusive backup and there is no exclusive backup in progress.
469+
* EXCLUSIVE_BACKUP_STARTING means that pg_start_backup() is starting an
470+
* exclusive backup.
471+
* EXCLUSIVE_BACKUP_IN_PROGRESS means that pg_start_backup() has finished
472+
* running and an exclusive backup is in progress. pg_stop_backup() is
473+
* needed to finish it.
474+
* EXCLUSIVE_BACKUP_STOPPING means that pg_stop_backup() is stopping an
475+
* exclusive backup.
476+
*/
477+
typedef enum ExclusiveBackupState
478+
{
479+
EXCLUSIVE_BACKUP_NONE = 0,
480+
EXCLUSIVE_BACKUP_STARTING,
481+
EXCLUSIVE_BACKUP_IN_PROGRESS,
482+
EXCLUSIVE_BACKUP_STOPPING
483+
} ExclusiveBackupState;
484+
462485
/*
463486
* Shared state data for WAL insertion.
464487
*/
@@ -500,13 +523,15 @@ typedef struct XLogCtlInsert
500523
bool fullPageWrites;
501524

502525
/*
503-
* exclusiveBackup is true if a backup started with pg_start_backup() is
504-
* in progress, and nonExclusiveBackups is a counter indicating the number
505-
* of streaming base backups currently in progress. forcePageWrites is set
506-
* to true when either of these is non-zero. lastBackupStart is the latest
507-
* checkpoint redo location used as a starting point for an online backup.
526+
* exclusiveBackupState indicates the state of an exclusive backup
527+
* (see comments of ExclusiveBackupState for more details).
528+
* nonExclusiveBackups is a counter indicating the number of streaming
529+
* base backups currently in progress. forcePageWrites is set to true
530+
* when either of these is non-zero. lastBackupStart is the latest
531+
* checkpoint redo location used as a starting point for an online
532+
* backup.
508533
*/
509-
bool exclusiveBackup;
534+
ExclusiveBackupState exclusiveBackupState;
510535
int nonExclusiveBackups;
511536
XLogRecPtr lastBackupStart;
512537

@@ -845,6 +870,7 @@ static void xlog_outrec(StringInfo buf, XLogReaderState *record);
845870
#endif
846871
static void xlog_outdesc(StringInfo buf, XLogReaderState *record);
847872
static void pg_start_backup_callback(int code, Datum arg);
873+
static void pg_stop_backup_callback(int code, Datum arg);
848874
static bool read_backup_label(XLogRecPtr *checkPointLoc,
849875
bool *backupEndRequired, bool *backupFromStandby);
850876
static bool read_tablespace_map(List **tablespaces);
@@ -9872,15 +9898,20 @@ do_pg_start_backup(const char *backupidstr, bool fast, TimeLineID *starttli_p,
98729898
WALInsertLockAcquireExclusive();
98739899
if (exclusive)
98749900
{
9875-
if (XLogCtl->Insert.exclusiveBackup)
9901+
/*
9902+
* At first, mark that we're now starting an exclusive backup,
9903+
* to ensure that there are no other sessions currently running
9904+
* pg_start_backup() or pg_stop_backup().
9905+
*/
9906+
if (XLogCtl->Insert.exclusiveBackupState != EXCLUSIVE_BACKUP_NONE)
98769907
{
98779908
WALInsertLockRelease();
98789909
ereport(ERROR,
98799910
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
98809911
errmsg("a backup is already in progress"),
98819912
errhint("Run pg_stop_backup() and try again.")));
98829913
}
9883-
XLogCtl->Insert.exclusiveBackup = true;
9914+
XLogCtl->Insert.exclusiveBackupState = EXCLUSIVE_BACKUP_STARTING;
98849915
}
98859916
else
98869917
XLogCtl->Insert.nonExclusiveBackups++;
@@ -10135,7 +10166,7 @@ do_pg_start_backup(const char *backupidstr, bool fast, TimeLineID *starttli_p,
1013510166
{
1013610167
/*
1013710168
* Check for existing backup label --- implies a backup is already
10138-
* running. (XXX given that we checked exclusiveBackup above,
10169+
* running. (XXX given that we checked exclusiveBackupState above,
1013910170
* maybe it would be OK to just unlink any such label file?)
1014010171
*/
1014110172
if (stat(BACKUP_LABEL_FILE, &stat_buf) != 0)
@@ -10216,6 +10247,16 @@ do_pg_start_backup(const char *backupidstr, bool fast, TimeLineID *starttli_p,
1021610247
}
1021710248
PG_END_ENSURE_ERROR_CLEANUP(pg_start_backup_callback, (Datum) BoolGetDatum(exclusive));
1021810249

10250+
/*
10251+
* Mark that start phase has correctly finished for an exclusive backup.
10252+
*/
10253+
if (exclusive)
10254+
{
10255+
WALInsertLockAcquireExclusive();
10256+
XLogCtl->Insert.exclusiveBackupState = EXCLUSIVE_BACKUP_IN_PROGRESS;
10257+
WALInsertLockRelease();
10258+
}
10259+
1021910260
/*
1022010261
* We're done. As a convenience, return the starting WAL location.
1022110262
*/
@@ -10234,23 +10275,41 @@ pg_start_backup_callback(int code, Datum arg)
1023410275
WALInsertLockAcquireExclusive();
1023510276
if (exclusive)
1023610277
{
10237-
Assert(XLogCtl->Insert.exclusiveBackup);
10238-
XLogCtl->Insert.exclusiveBackup = false;
10278+
Assert(XLogCtl->Insert.exclusiveBackupState == EXCLUSIVE_BACKUP_STARTING);
10279+
XLogCtl->Insert.exclusiveBackupState = EXCLUSIVE_BACKUP_NONE;
1023910280
}
1024010281
else
1024110282
{
1024210283
Assert(XLogCtl->Insert.nonExclusiveBackups > 0);
1024310284
XLogCtl->Insert.nonExclusiveBackups--;
1024410285
}
1024510286

10246-
if (!XLogCtl->Insert.exclusiveBackup &&
10287+
if (XLogCtl->Insert.exclusiveBackupState == EXCLUSIVE_BACKUP_NONE &&
1024710288
XLogCtl->Insert.nonExclusiveBackups == 0)
1024810289
{
1024910290
XLogCtl->Insert.forcePageWrites = false;
1025010291
}
1025110292
WALInsertLockRelease();
1025210293
}
1025310294

10295+
/*
10296+
* Error cleanup callback for pg_stop_backup
10297+
*/
10298+
static void
10299+
pg_stop_backup_callback(int code, Datum arg)
10300+
{
10301+
bool exclusive = DatumGetBool(arg);
10302+
10303+
/* Update backup status on failure */
10304+
WALInsertLockAcquireExclusive();
10305+
if (exclusive)
10306+
{
10307+
Assert(XLogCtl->Insert.exclusiveBackupState == EXCLUSIVE_BACKUP_STOPPING);
10308+
XLogCtl->Insert.exclusiveBackupState = EXCLUSIVE_BACKUP_IN_PROGRESS;
10309+
}
10310+
WALInsertLockRelease();
10311+
}
10312+
1025410313
/*
1025510314
* do_pg_stop_backup is the workhorse of the user-visible pg_stop_backup()
1025610315
* function.
@@ -10313,20 +10372,91 @@ do_pg_stop_backup(char *labelfile, bool waitforarchive, TimeLineID *stoptli_p)
1031310372
errmsg("WAL level not sufficient for making an online backup"),
1031410373
errhint("wal_level must be set to \"replica\" or \"logical\" at server start.")));
1031510374

10316-
/*
10317-
* OK to update backup counters and forcePageWrites
10318-
*/
10319-
WALInsertLockAcquireExclusive();
1032010375
if (exclusive)
1032110376
{
10322-
if (!XLogCtl->Insert.exclusiveBackup)
10377+
/*
10378+
* At first, mark that we're now stopping an exclusive backup,
10379+
* to ensure that there are no other sessions currently running
10380+
* pg_start_backup() or pg_stop_backup().
10381+
*/
10382+
WALInsertLockAcquireExclusive();
10383+
if (XLogCtl->Insert.exclusiveBackupState != EXCLUSIVE_BACKUP_IN_PROGRESS)
1032310384
{
1032410385
WALInsertLockRelease();
1032510386
ereport(ERROR,
1032610387
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
1032710388
errmsg("exclusive backup not in progress")));
1032810389
}
10329-
XLogCtl->Insert.exclusiveBackup = false;
10390+
XLogCtl->Insert.exclusiveBackupState = EXCLUSIVE_BACKUP_STOPPING;
10391+
WALInsertLockRelease();
10392+
10393+
/*
10394+
* Remove backup_label. In case of failure, the state for an exclusive
10395+
* backup is switched back to in-progress.
10396+
*/
10397+
PG_ENSURE_ERROR_CLEANUP(pg_stop_backup_callback, (Datum) BoolGetDatum(exclusive));
10398+
{
10399+
/*
10400+
* Read the existing label file into memory.
10401+
*/
10402+
struct stat statbuf;
10403+
int r;
10404+
10405+
if (stat(BACKUP_LABEL_FILE, &statbuf))
10406+
{
10407+
/* should not happen per the upper checks */
10408+
if (errno != ENOENT)
10409+
ereport(ERROR,
10410+
(errcode_for_file_access(),
10411+
errmsg("could not stat file \"%s\": %m",
10412+
BACKUP_LABEL_FILE)));
10413+
ereport(ERROR,
10414+
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
10415+
errmsg("a backup is not in progress")));
10416+
}
10417+
10418+
lfp = AllocateFile(BACKUP_LABEL_FILE, "r");
10419+
if (!lfp)
10420+
{
10421+
ereport(ERROR,
10422+
(errcode_for_file_access(),
10423+
errmsg("could not read file \"%s\": %m",
10424+
BACKUP_LABEL_FILE)));
10425+
}
10426+
labelfile = palloc(statbuf.st_size + 1);
10427+
r = fread(labelfile, statbuf.st_size, 1, lfp);
10428+
labelfile[statbuf.st_size] = '\0';
10429+
10430+
/*
10431+
* Close and remove the backup label file
10432+
*/
10433+
if (r != 1 || ferror(lfp) || FreeFile(lfp))
10434+
ereport(ERROR,
10435+
(errcode_for_file_access(),
10436+
errmsg("could not read file \"%s\": %m",
10437+
BACKUP_LABEL_FILE)));
10438+
if (unlink(BACKUP_LABEL_FILE) != 0)
10439+
ereport(ERROR,
10440+
(errcode_for_file_access(),
10441+
errmsg("could not remove file \"%s\": %m",
10442+
BACKUP_LABEL_FILE)));
10443+
10444+
/*
10445+
* Remove tablespace_map file if present, it is created only if there
10446+
* are tablespaces.
10447+
*/
10448+
unlink(TABLESPACE_MAP);
10449+
}
10450+
PG_END_ENSURE_ERROR_CLEANUP(pg_stop_backup_callback, (Datum) BoolGetDatum(exclusive));
10451+
}
10452+
10453+
/*
10454+
* OK to update backup counters and forcePageWrites
10455+
*/
10456+
WALInsertLockAcquireExclusive();
10457+
if (exclusive)
10458+
{
10459+
XLogCtl->Insert.exclusiveBackupState = EXCLUSIVE_BACKUP_NONE;
1033010460
}
1033110461
else
1033210462
{
@@ -10340,66 +10470,13 @@ do_pg_stop_backup(char *labelfile, bool waitforarchive, TimeLineID *stoptli_p)
1034010470
XLogCtl->Insert.nonExclusiveBackups--;
1034110471
}
1034210472

10343-
if (!XLogCtl->Insert.exclusiveBackup &&
10473+
if (XLogCtl->Insert.exclusiveBackupState == EXCLUSIVE_BACKUP_NONE &&
1034410474
XLogCtl->Insert.nonExclusiveBackups == 0)
1034510475
{
1034610476
XLogCtl->Insert.forcePageWrites = false;
1034710477
}
1034810478
WALInsertLockRelease();
1034910479

10350-
if (exclusive)
10351-
{
10352-
/*
10353-
* Read the existing label file into memory.
10354-
*/
10355-
struct stat statbuf;
10356-
int r;
10357-
10358-
if (stat(BACKUP_LABEL_FILE, &statbuf))
10359-
{
10360-
if (errno != ENOENT)
10361-
ereport(ERROR,
10362-
(errcode_for_file_access(),
10363-
errmsg("could not stat file \"%s\": %m",
10364-
BACKUP_LABEL_FILE)));
10365-
ereport(ERROR,
10366-
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
10367-
errmsg("a backup is not in progress")));
10368-
}
10369-
10370-
lfp = AllocateFile(BACKUP_LABEL_FILE, "r");
10371-
if (!lfp)
10372-
{
10373-
ereport(ERROR,
10374-
(errcode_for_file_access(),
10375-
errmsg("could not read file \"%s\": %m",
10376-
BACKUP_LABEL_FILE)));
10377-
}
10378-
labelfile = palloc(statbuf.st_size + 1);
10379-
r = fread(labelfile, statbuf.st_size, 1, lfp);
10380-
labelfile[statbuf.st_size] = '\0';
10381-
10382-
/*
10383-
* Close and remove the backup label file
10384-
*/
10385-
if (r != 1 || ferror(lfp) || FreeFile(lfp))
10386-
ereport(ERROR,
10387-
(errcode_for_file_access(),
10388-
errmsg("could not read file \"%s\": %m",
10389-
BACKUP_LABEL_FILE)));
10390-
if (unlink(BACKUP_LABEL_FILE) != 0)
10391-
ereport(ERROR,
10392-
(errcode_for_file_access(),
10393-
errmsg("could not remove file \"%s\": %m",
10394-
BACKUP_LABEL_FILE)));
10395-
10396-
/*
10397-
* Remove tablespace_map file if present, it is created only if there
10398-
* are tablespaces.
10399-
*/
10400-
unlink(TABLESPACE_MAP);
10401-
}
10402-
1040310480
/*
1040410481
* Read and parse the START WAL LOCATION line (this code is pretty crude,
1040510482
* but we are not expecting any variability in the file format).
@@ -10636,7 +10713,7 @@ do_pg_abort_backup(void)
1063610713
Assert(XLogCtl->Insert.nonExclusiveBackups > 0);
1063710714
XLogCtl->Insert.nonExclusiveBackups--;
1063810715

10639-
if (!XLogCtl->Insert.exclusiveBackup &&
10716+
if (XLogCtl->Insert.exclusiveBackupState == EXCLUSIVE_BACKUP_NONE &&
1064010717
XLogCtl->Insert.nonExclusiveBackups == 0)
1064110718
{
1064210719
XLogCtl->Insert.forcePageWrites = false;

0 commit comments

Comments
 (0)