Now that we have a basic over-the-wire base backup capability in
walsender, it would be nice to allow taking multiple base backups at the
same time. It might not seem very useful at first, but it makes it
easier to set up standbys for small databases. At the moment, if you
want to set up two standbys, you have to either take a single base
backup and distribute it to both standbys, or somehow coordinate that
they don't try to take the base backup at the same time. Also, you don't
want initializing a standby to conflict with a nightly backup cron script.
So, this patch modifies the internal do_pg_start/stop_backup functions
so that in addition to the traditional mode of operation, where a
backup_label file is created in the data directory where it's backed up
along with all other files, the backup label file is be returned to the
caller, and the caller is responsible for including it in the backup.
The code in replication/basebackup.c includes it in the tar file that's
streamed the client, as "backup_label".
To make that safe, I've changed forcePageWrites into an integer.
Whenever a backup is started, it's incremented, and when one ends, it's
decremented. When forcePageWrites == 0, there's no backup in progress.
The user-visible pg_start_backup() function is not changed. You can only
have one backup started that way in progress at a time. But you can do
streaming base backups at the same time with traditional pg_start_backup().
I implemented this in two ways, and can't decide which I like better:
1. The contents of the backup label file are returned to the caller of
do_pg_start_backup() as a palloc'd string.
2. do_pg_start_backup() creates a temporary file that the backup label
is written to (instead of "backup_label").
Implementation 1 changes more code, as pg_start/stop_backup() need to be
changed to write/read from memory instead of file, but the result isn't
any more complicated. Nevertheless, I somehow feel more comfortable with 2.
Patches for both approaches attached. They're also available in my
github repository at g...@github.com:hlinnaka/postgres.git.
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 5b6a230..cc4d46e 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -338,7 +338,8 @@ typedef struct XLogCtlInsert
XLogPageHeader currpage; /* points to header of block in cache */
char *currpos; /* current insertion point in cache */
XLogRecPtr RedoRecPtr; /* current redo point for insertions */
- bool forcePageWrites; /* forcing full-page writes for PITR? */
+ int forcePageWrites; /* forcing full-page writes for PITR? */
+ bool exclusiveBackup; /* a backup was started with pg_start_backup() */
} XLogCtlInsert;
/*
@@ -8313,7 +8314,7 @@ pg_start_backup(PG_FUNCTION_ARGS)
backupidstr = text_to_cstring(backupid);
- startpoint = do_pg_start_backup(backupidstr, fast);
+ startpoint = do_pg_start_backup(backupidstr, fast, true, NULL);
snprintf(startxlogstr, sizeof(startxlogstr), "%X/%X",
startpoint.xlogid, startpoint.xrecoff);
@@ -8321,7 +8322,7 @@ pg_start_backup(PG_FUNCTION_ARGS)
}
XLogRecPtr
-do_pg_start_backup(const char *backupidstr, bool fast)
+do_pg_start_backup(const char *backupidstr, bool fast, bool exclusive, char **labelfile)
{
XLogRecPtr checkpointloc;
XLogRecPtr startpoint;
@@ -8332,6 +8333,7 @@ do_pg_start_backup(const char *backupidstr, bool fast)
uint32 _logSeg;
struct stat stat_buf;
FILE *fp;
+ StringInfoData labelfbuf;
if (!superuser() && !is_authenticated_user_replication_role())
ereport(ERROR,
@@ -8368,15 +8370,19 @@ do_pg_start_backup(const char *backupidstr, bool fast)
* ensure adequate interlocking against XLogInsert().
*/
LWLockAcquire(WALInsertLock, LW_EXCLUSIVE);
- if (XLogCtl->Insert.forcePageWrites)
+ if (exclusive)
{
- LWLockRelease(WALInsertLock);
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("a backup is already in progress"),
- errhint("Run pg_stop_backup() and try again.")));
+ if (XLogCtl->Insert.exclusiveBackup)
+ {
+ LWLockRelease(WALInsertLock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("a backup is already in progress"),
+ errhint("Run pg_stop_backup() and try again.")));
+ }
+ XLogCtl->Insert.exclusiveBackup = true;
}
- XLogCtl->Insert.forcePageWrites = true;
+ XLogCtl->Insert.forcePageWrites++;
LWLockRelease(WALInsertLock);
/*
@@ -8393,7 +8399,7 @@ do_pg_start_backup(const char *backupidstr, bool fast)
RequestXLogSwitch();
/* Ensure we release forcePageWrites if fail below */
- PG_ENSURE_ERROR_CLEANUP(pg_start_backup_callback, (Datum) 0);
+ PG_ENSURE_ERROR_CLEANUP(pg_start_backup_callback, (Datum) BoolGetDatum(exclusive));
{
/*
* Force a CHECKPOINT. Aside from being necessary to prevent torn
@@ -8420,54 +8426,67 @@ do_pg_start_backup(const char *backupidstr, bool fast)
XLByteToSeg(startpoint, _logId, _logSeg);
XLogFileName(xlogfilename, ThisTimeLineID, _logId, _logSeg);
+ /*
+ * Construct backup label file
+ */
+ initStringInfo(&labelfbuf);
+
/* Use the log timezone here, not the session timezone */
stamp_time = (pg_time_t) time(NULL);
pg_strftime(strfbuf, sizeof(strfbuf),
"%Y-%m-%d %H:%M:%S %Z",
pg_localtime(&stamp_time, log_timezone));
+ appendStringInfo(&labelfbuf, "START WAL LOCATION: %X/%X (file %s)\n",
+ startpoint.xlogid, startpoint.xrecoff, xlogfilename);
+ appendStringInfo(&labelfbuf, "CHECKPOINT LOCATION: %X/%X\n",
+ checkpointloc.xlogid, checkpointloc.xrecoff);
+ appendStringInfo(&labelfbuf, "START TIME: %s\n", strfbuf);
+ appendStringInfo(&labelfbuf, "LABEL: %s\n", backupidstr);
/*
- * Check for existing backup label --- implies a backup is already
- * running. (XXX given that we checked forcePageWrites above, maybe
- * it would be OK to just unlink any such label file?)
+ * Okay, write the file, or return it to caller.
*/
- if (stat(BACKUP_LABEL_FILE, &stat_buf) != 0)
+ if (exclusive)
{
- if (errno != ENOENT)
+ /*
+ * 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)
+ {
+ if (errno != ENOENT)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m",
+ BACKUP_LABEL_FILE)));
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("a backup is already in progress"),
+ errhint("If you're sure there is no backup in progress, remove file \"%s\" and try again.",
+ BACKUP_LABEL_FILE)));
+
+ fp = AllocateFile(BACKUP_LABEL_FILE, "w");
+
+ if (!fp)
ereport(ERROR,
(errcode_for_file_access(),
- errmsg("could not stat file \"%s\": %m",
+ errmsg("could not create file \"%s\": %m",
BACKUP_LABEL_FILE)));
+ fwrite(labelfbuf.data, labelfbuf.len, 1, fp);
+ if (fflush(fp) || ferror(fp) || FreeFile(fp))
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not write file \"%s\": %m",
+ BACKUP_LABEL_FILE)));
+ pfree(labelfbuf.data);
}
else
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("a backup is already in progress"),
- errhint("If you're sure there is no backup in progress, remove file \"%s\" and try again.",
- BACKUP_LABEL_FILE)));
-
- /*
- * Okay, write the file
- */
- fp = AllocateFile(BACKUP_LABEL_FILE, "w");
- if (!fp)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not create file \"%s\": %m",
- BACKUP_LABEL_FILE)));
- fprintf(fp, "START WAL LOCATION: %X/%X (file %s)\n",
- startpoint.xlogid, startpoint.xrecoff, xlogfilename);
- fprintf(fp, "CHECKPOINT LOCATION: %X/%X\n",
- checkpointloc.xlogid, checkpointloc.xrecoff);
- fprintf(fp, "START TIME: %s\n", strfbuf);
- fprintf(fp, "LABEL: %s\n", backupidstr);
- if (fflush(fp) || ferror(fp) || FreeFile(fp))
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not write file \"%s\": %m",
- BACKUP_LABEL_FILE)));
+ *labelfile = labelfbuf.data;
}
- PG_END_ENSURE_ERROR_CLEANUP(pg_start_backup_callback, (Datum) 0);
+ PG_END_ENSURE_ERROR_CLEANUP(pg_start_backup_callback, (Datum) BoolGetDatum(exclusive));
/*
* We're done. As a convenience, return the starting WAL location.
@@ -8479,8 +8498,15 @@ do_pg_start_backup(const char *backupidstr, bool fast)
static void
pg_start_backup_callback(int code, Datum arg)
{
- /* Turn off forcePageWrites on failure */
+ bool exclusive = DatumGetBool(arg);
+
+ /* Decrement forcePageWrites on failure */
LWLockAcquire(WALInsertLock, LW_EXCLUSIVE);
+ if (exclusive)
+ {
+ Assert(XLogCtl->Insert.exclusiveBackup);
+ XLogCtl->Insert.exclusiveBackup = false;
+ }
XLogCtl->Insert.forcePageWrites = false;
LWLockRelease(WALInsertLock);
}
@@ -8504,16 +8530,20 @@ pg_stop_backup(PG_FUNCTION_ARGS)
XLogRecPtr stoppoint;
char stopxlogstr[MAXFNAMELEN];
- stoppoint = do_pg_stop_backup();
+ stoppoint = do_pg_stop_backup(NULL);
snprintf(stopxlogstr, sizeof(stopxlogstr), "%X/%X",
stoppoint.xlogid, stoppoint.xrecoff);
PG_RETURN_TEXT_P(cstring_to_text(stopxlogstr));
}
+/*
+ * If labelfile is NULL, this stops an exclusive backup.
+ */
XLogRecPtr
-do_pg_stop_backup(void)
+do_pg_stop_backup(char *labelfile)
{
+ bool exclusive = (labelfile == NULL);
XLogRecPtr startpoint;
XLogRecPtr stoppoint;
XLogRecData rdata;
@@ -8529,10 +8559,10 @@ do_pg_stop_backup(void)
FILE *lfp;
FILE *fp;
char ch;
- int ich;
int seconds_before_warning;
int waits = 0;
bool reported_waiting = false;
+ char *remaining;
if (!superuser() && !is_authenticated_user_replication_role())
ereport(ERROR,
@@ -8555,35 +8585,71 @@ do_pg_stop_backup(void)
* OK to clear forcePageWrites
*/
LWLockAcquire(WALInsertLock, LW_EXCLUSIVE);
- XLogCtl->Insert.forcePageWrites = false;
+ if (exclusive)
+ {
+ if (XLogCtl->Insert.exclusiveBackup)
+ {
+ XLogCtl->Insert.forcePageWrites--;
+ XLogCtl->Insert.exclusiveBackup = false;
+ }
+ }
+ XLogCtl->Insert.forcePageWrites--;
LWLockRelease(WALInsertLock);
- /*
- * Open the existing label file
- */
- lfp = AllocateFile(BACKUP_LABEL_FILE, "r");
- if (!lfp)
+ if (!labelfile)
{
- if (errno != ENOENT)
+ int len;
+
+ /*
+ * Read the existing label file
+ */
+ lfp = AllocateFile(BACKUP_LABEL_FILE, "r");
+ if (!lfp)
+ {
+ if (errno != ENOENT)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not read file \"%s\": %m",
+ labelfile)));
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("a backup is not in progress")));
+ }
+
+ /* The label file should be small */
+ labelfile = palloc(1024);
+ len = fread(labelfile, 1, 1023, lfp);
+ if (len >= 1023)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("backup_label file \"%s\" is too long", BACKUP_LABEL_FILE)));
+
+ /*
+ * Close and remove the backup label file
+ */
+ if (ferror(lfp) || FreeFile(lfp))
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not read file \"%s\": %m",
BACKUP_LABEL_FILE)));
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("a backup is not in progress")));
+ if (unlink(BACKUP_LABEL_FILE) != 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not remove file \"%s\": %m",
+ BACKUP_LABEL_FILE)));
}
/*
* Read and parse the START WAL LOCATION line (this code is pretty crude,
* but we are not expecting any variability in the file format).
*/
- if (fscanf(lfp, "START WAL LOCATION: %X/%X (file %24s)%c",
+ if (sscanf(labelfile, "START WAL LOCATION: %X/%X (file %24s)%c",
&startpoint.xlogid, &startpoint.xrecoff, startxlogfilename,
&ch) != 4 || ch != '\n')
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("invalid data in file \"%s\"", BACKUP_LABEL_FILE)));
+ remaining = strchr(labelfile, '\n') + 1;
/*
* Write the backup-end xlog record
@@ -8626,8 +8692,7 @@ do_pg_stop_backup(void)
fprintf(fp, "STOP WAL LOCATION: %X/%X (file %s)\n",
stoppoint.xlogid, stoppoint.xrecoff, stopxlogfilename);
/* transfer remaining lines from label to history file */
- while ((ich = fgetc(lfp)) != EOF)
- fputc(ich, fp);
+ fwrite(remaining, strlen(remaining), 1, fp);
fprintf(fp, "STOP TIME: %s\n", strfbuf);
if (fflush(fp) || ferror(fp) || FreeFile(fp))
ereport(ERROR,
@@ -8636,20 +8701,6 @@ do_pg_stop_backup(void)
histfilepath)));
/*
- * Close and remove the backup label file
- */
- if (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)));
-
- /*
* 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.
@@ -8730,9 +8781,13 @@ do_pg_stop_backup(void)
/*
* do_pg_abort_backup: abort a running backup
*
- * This does just the most basic steps of pg_stop_backup(), by taking the
+ * This does just the most basic steps of do_pg_stop_backup(), by taking the
* system out of backup mode, thus making it a lot more safe to call from
* an error handler.
+ *
+ * NB: This is only for aborting a non-exclusive backup that doesn't write
+ * backup_label. A backup started with pg_stop_backup() needs to be finished
+ * with pg_stop_backup().
*/
void
do_pg_abort_backup(void)
@@ -8741,17 +8796,14 @@ do_pg_abort_backup(void)
* OK to clear forcePageWrites
*/
LWLockAcquire(WALInsertLock, LW_EXCLUSIVE);
- XLogCtl->Insert.forcePageWrites = false;
- LWLockRelease(WALInsertLock);
-
/*
- * Remove backup label file
+ * The caller should take care to not call us only if a backup, but
+ * be extra paranoid to avoid wrapping forcePageWrites around if the
+ * caller screws up.
*/
- if (unlink(BACKUP_LABEL_FILE) != 0)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not remove file \"%s\": %m",
- BACKUP_LABEL_FILE)));
+ if (XLogCtl->Insert.forcePageWrites > 0)
+ XLogCtl->Insert.forcePageWrites--;
+ LWLockRelease(WALInsertLock);
}
/*
diff --git a/src/backend/replication/basebackup.c b/src/backend/replication/basebackup.c
index de5fa22..c31b091 100644
--- a/src/backend/replication/basebackup.c
+++ b/src/backend/replication/basebackup.c
@@ -31,13 +31,17 @@
#include "utils/memutils.h"
#include "utils/ps_status.h"
+/* XXX: copied from xlog.c */
+#define BACKUP_LABEL_FILE "backup_label"
+
static int64 sendDir(char *path, int basepathlen, bool sizeonly);
-static void sendFile(char *path, int basepathlen, struct stat * statbuf);
+static void sendFile(char *readfilename, char *tarfilename,
+ struct stat * statbuf);
static void _tarWriteHeader(char *filename, char *linktarget,
struct stat * statbuf);
static void send_int8_string(StringInfoData *buf, int64 intval);
static void SendBackupHeader(List *tablespaces);
-static void SendBackupDirectory(char *location, char *spcoid);
+static void SendBackupDirectories(List *tablespaces, char *labelfilepath);
static void base_backup_cleanup(int code, Datum arg);
typedef struct
@@ -75,6 +79,7 @@ SendBaseBackup(const char *options)
tablespaceinfo *ti;
MemoryContext backup_context;
MemoryContext old_context;
+ char *labelfile;
backup_context = AllocSetContextCreate(CurrentMemoryContext,
"Streaming base backup context",
@@ -145,26 +150,19 @@ SendBaseBackup(const char *options)
}
FreeDir(dir);
- do_pg_start_backup(backup_label, true);
+ do_pg_start_backup(backup_label, true, false, &labelfile);
PG_ENSURE_ERROR_CLEANUP(base_backup_cleanup, (Datum) 0);
{
- ListCell *lc;
-
/* Send tablespace header */
SendBackupHeader(tablespaces);
- /* Send off our tablespaces one by one */
- foreach(lc, tablespaces)
- {
- ti = (tablespaceinfo *) lfirst(lc);
-
- SendBackupDirectory(ti->path, ti->oid);
- }
+ /* Send the tars */
+ SendBackupDirectories(tablespaces, labelfile);
}
PG_END_ENSURE_ERROR_CLEANUP(base_backup_cleanup, (Datum) 0);
- do_pg_stop_backup();
+ do_pg_stop_backup(labelfile);
MemoryContextSwitchTo(old_context);
MemoryContextDelete(backup_context);
@@ -250,20 +248,65 @@ SendBackupHeader(List *tablespaces)
}
static void
-SendBackupDirectory(char *location, char *spcoid)
+SendBackupDirectories(List *tablespaces, char *labelfile)
{
StringInfoData buf;
+ ListCell *lc;
- /* Send CopyOutResponse message */
- pq_beginmessage(&buf, 'H');
- pq_sendbyte(&buf, 0); /* overall format */
- pq_sendint(&buf, 0, 2); /* natts */
- pq_endmessage(&buf);
+ /* Send off our tablespaces one by one */
+ foreach(lc, tablespaces)
+ {
+ tablespaceinfo *ti = (tablespaceinfo *) lfirst(lc);
+ char *location = ti->path;
+
+ /* Send CopyOutResponse message */
+ pq_beginmessage(&buf, 'H');
+ pq_sendbyte(&buf, 0); /* overall format */
+ pq_sendint(&buf, 0, 2); /* natts */
+ pq_endmessage(&buf);
+
+ /*
+ * tar up the data directory if NULL, otherwise the tablespace
+ * In the main tar, also include the backup_label.
+ */
+ if (location == NULL)
+ {
+ struct stat statbuf;
+ int pad, len;
- /* tar up the data directory if NULL, otherwise the tablespace */
- sendDir(location == NULL ? "." : location,
- location == NULL ? 1 : strlen(location),
- false);
+ len = strlen(labelfile);
+
+ /*
+ * We use PG_VERSION as a template for the uid and gid and mode
+ * to use for the backup_label file. XXX: would "." be better?
+ * Or something else?
+ */
+ if (lstat("PG_VERSION", &statbuf) != 0)
+ ereport(ERROR,
+ (errcode(errcode_for_file_access()),
+ errmsg("could not stat file \"%s\": %m",
+ "PG_VERSION")));
+ statbuf.st_mtime = time(NULL);
+ statbuf.st_size = len;
+
+ _tarWriteHeader(BACKUP_LABEL_FILE, NULL, &statbuf);
+ /* Send the contents as a CopyData message */
+ pq_putmessage('d', labelfile, len);
+
+ /* Pad to 512 byte boundary, per tar format requirements */
+ pad = ((len + 511) & ~511) - len;
+ if (pad > 0)
+ {
+ char buf[512];
+ MemSet(buf, 0, pad);
+ pq_putmessage('d', buf, pad);
+ }
+
+ location = ".";
+ }
+
+ sendDir(location, strlen(location), false);
+ }
/* Send CopyDone message */
pq_putemptymessage('c');
@@ -360,7 +403,7 @@ sendDir(char *path, int basepathlen, bool sizeonly)
/* Add size, rounded up to 512byte block */
size += ((statbuf.st_size + 511) & ~511);
if (!sizeonly)
- sendFile(pathbuf, basepathlen, &statbuf);
+ sendFile(pathbuf, pathbuf + basepathlen + 1, &statbuf);
size += 512; /* Size of the header of the file */
}
else
@@ -418,7 +461,7 @@ _tarChecksum(char *header)
/* Given the member, write the TAR header & send the file */
static void
-sendFile(char *filename, int basepathlen, struct stat * statbuf)
+sendFile(char *readfilename, char *tarfilename, struct stat * statbuf)
{
FILE *fp;
char buf[32768];
@@ -426,11 +469,11 @@ sendFile(char *filename, int basepathlen, struct stat * statbuf)
pgoff_t len = 0;
size_t pad;
- fp = AllocateFile(filename, "rb");
+ fp = AllocateFile(readfilename, "rb");
if (fp == NULL)
ereport(ERROR,
(errcode(errcode_for_file_access()),
- errmsg("could not open file \"%s\": %m", filename)));
+ errmsg("could not open file \"%s\": %m", readfilename)));
/*
* Some compilers will throw a warning knowing this test can never be true
@@ -439,9 +482,9 @@ sendFile(char *filename, int basepathlen, struct stat * statbuf)
if (statbuf->st_size > MAX_TAR_MEMBER_FILELEN)
ereport(ERROR,
(errmsg("archive member \"%s\" too large for tar format",
- filename)));
+ tarfilename)));
- _tarWriteHeader(filename + basepathlen + 1, NULL, statbuf);
+ _tarWriteHeader(tarfilename, NULL, statbuf);
while ((cnt = fread(buf, 1, Min(sizeof(buf), statbuf->st_size - len), fp)) > 0)
{
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index 74d3427..babaaa6 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -312,8 +312,8 @@ extern void HandleStartupProcInterrupts(void);
extern void StartupProcessMain(void);
extern void WakeupRecovery(void);
-extern XLogRecPtr do_pg_start_backup(const char *backupidstr, bool fast);
-extern XLogRecPtr do_pg_stop_backup(void);
+extern XLogRecPtr do_pg_start_backup(const char *backupidstr, bool fast, bool exclusive, char **labelfile);
+extern XLogRecPtr do_pg_stop_backup(char *labelfile);
extern void do_pg_abort_backup(void);
#endif /* XLOG_H */
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 5b6a230..3d0f813 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -338,7 +338,8 @@ typedef struct XLogCtlInsert
XLogPageHeader currpage; /* points to header of block in cache */
char *currpos; /* current insertion point in cache */
XLogRecPtr RedoRecPtr; /* current redo point for insertions */
- bool forcePageWrites; /* forcing full-page writes for PITR? */
+ int forcePageWrites; /* forcing full-page writes for PITR? */
+ bool exclusiveBackup; /* a backup was started with pg_start_backup() */
} XLogCtlInsert;
/*
@@ -8313,7 +8314,7 @@ pg_start_backup(PG_FUNCTION_ARGS)
backupidstr = text_to_cstring(backupid);
- startpoint = do_pg_start_backup(backupidstr, fast);
+ startpoint = do_pg_start_backup(backupidstr, fast, true, NULL);
snprintf(startxlogstr, sizeof(startxlogstr), "%X/%X",
startpoint.xlogid, startpoint.xrecoff);
@@ -8321,7 +8322,7 @@ pg_start_backup(PG_FUNCTION_ARGS)
}
XLogRecPtr
-do_pg_start_backup(const char *backupidstr, bool fast)
+do_pg_start_backup(const char *backupidstr, bool fast, bool exclusive, char **labelfilename)
{
XLogRecPtr checkpointloc;
XLogRecPtr startpoint;
@@ -8368,15 +8369,19 @@ do_pg_start_backup(const char *backupidstr, bool fast)
* ensure adequate interlocking against XLogInsert().
*/
LWLockAcquire(WALInsertLock, LW_EXCLUSIVE);
- if (XLogCtl->Insert.forcePageWrites)
+ if (exclusive)
{
- LWLockRelease(WALInsertLock);
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("a backup is already in progress"),
- errhint("Run pg_stop_backup() and try again.")));
+ if (XLogCtl->Insert.exclusiveBackup)
+ {
+ LWLockRelease(WALInsertLock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("a backup is already in progress"),
+ errhint("Run pg_stop_backup() and try again.")));
+ }
+ XLogCtl->Insert.exclusiveBackup = true;
}
- XLogCtl->Insert.forcePageWrites = true;
+ XLogCtl->Insert.forcePageWrites++;
LWLockRelease(WALInsertLock);
/*
@@ -8393,7 +8398,7 @@ do_pg_start_backup(const char *backupidstr, bool fast)
RequestXLogSwitch();
/* Ensure we release forcePageWrites if fail below */
- PG_ENSURE_ERROR_CLEANUP(pg_start_backup_callback, (Datum) 0);
+ PG_ENSURE_ERROR_CLEANUP(pg_start_backup_callback, (Datum) BoolGetDatum(exclusive));
{
/*
* Force a CHECKPOINT. Aside from being necessary to prevent torn
@@ -8427,29 +8432,55 @@ do_pg_start_backup(const char *backupidstr, bool fast)
pg_localtime(&stamp_time, log_timezone));
/*
- * Check for existing backup label --- implies a backup is already
- * running. (XXX given that we checked forcePageWrites above, maybe
- * it would be OK to just unlink any such label file?)
+ * Okay, write the file
*/
- if (stat(BACKUP_LABEL_FILE, &stat_buf) != 0)
+ if (exclusive)
{
- if (errno != ENOENT)
+ /*
+ * 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)
+ {
+ if (errno != ENOENT)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m",
+ BACKUP_LABEL_FILE)));
+ }
+ else
ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not stat file \"%s\": %m",
- BACKUP_LABEL_FILE)));
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("a backup is already in progress"),
+ errhint("If you're sure there is no backup in progress, remove file \"%s\" and try again.",
+ BACKUP_LABEL_FILE)));
+
+ fp = AllocateFile(BACKUP_LABEL_FILE, "w");
}
else
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("a backup is already in progress"),
- errhint("If you're sure there is no backup in progress, remove file \"%s\" and try again.",
- BACKUP_LABEL_FILE)));
+ {
+ char historyfilename[MAXFNAMELEN];
+ /* Calculate name for temp file */
+ XLByteToSeg(startpoint, _logId, _logSeg);
+ BackupHistoryFileName(historyfilename, ThisTimeLineID,
+ _logId, _logSeg, startpoint.xrecoff % XLogSegSize);
+
+ *labelfilename = palloc(MAXPGPATH);
+ snprintf(*labelfilename, MAXPGPATH, "%s/%s.%s",
+ PG_TEMP_FILES_DIR, PG_TEMP_FILE_PREFIX,
+ historyfilename);
+
+ /* Open file */
+ fp = AllocateFile(*labelfilename, PG_BINARY_W);
+ if (!fp)
+ {
+ /* As in OpenTemporaryFile, try to make the temp-file directory */
+ mkdir(PG_TEMP_FILES_DIR, S_IRWXU);
- /*
- * Okay, write the file
- */
- fp = AllocateFile(BACKUP_LABEL_FILE, "w");
+ fp = AllocateFile(*labelfilename, PG_BINARY_W);
+ }
+ }
if (!fp)
ereport(ERROR,
(errcode_for_file_access(),
@@ -8467,7 +8498,7 @@ do_pg_start_backup(const char *backupidstr, bool fast)
errmsg("could not write file \"%s\": %m",
BACKUP_LABEL_FILE)));
}
- PG_END_ENSURE_ERROR_CLEANUP(pg_start_backup_callback, (Datum) 0);
+ PG_END_ENSURE_ERROR_CLEANUP(pg_start_backup_callback, (Datum) BoolGetDatum(exclusive));
/*
* We're done. As a convenience, return the starting WAL location.
@@ -8479,8 +8510,15 @@ do_pg_start_backup(const char *backupidstr, bool fast)
static void
pg_start_backup_callback(int code, Datum arg)
{
- /* Turn off forcePageWrites on failure */
+ bool exclusive = DatumGetBool(arg);
+
+ /* Decrement forcePageWrites on failure */
LWLockAcquire(WALInsertLock, LW_EXCLUSIVE);
+ if (exclusive)
+ {
+ Assert(XLogCtl->Insert.exclusiveBackup);
+ XLogCtl->Insert.exclusiveBackup = false;
+ }
XLogCtl->Insert.forcePageWrites = false;
LWLockRelease(WALInsertLock);
}
@@ -8504,16 +8542,20 @@ pg_stop_backup(PG_FUNCTION_ARGS)
XLogRecPtr stoppoint;
char stopxlogstr[MAXFNAMELEN];
- stoppoint = do_pg_stop_backup();
+ stoppoint = do_pg_stop_backup(NULL);
snprintf(stopxlogstr, sizeof(stopxlogstr), "%X/%X",
stoppoint.xlogid, stoppoint.xrecoff);
PG_RETURN_TEXT_P(cstring_to_text(stopxlogstr));
}
+/*
+ * If labelfile is NULL, this stops an exclusive backup.
+ */
XLogRecPtr
-do_pg_stop_backup(void)
+do_pg_stop_backup(const char *labelfile)
{
+ bool exclusive;
XLogRecPtr startpoint;
XLogRecPtr stoppoint;
XLogRecData rdata;
@@ -8534,6 +8576,14 @@ do_pg_stop_backup(void)
int waits = 0;
bool reported_waiting = false;
+ if (labelfile == NULL)
+ {
+ exclusive = true;
+ labelfile = BACKUP_LABEL_FILE;
+ }
+ else
+ exclusive = false;
+
if (!superuser() && !is_authenticated_user_replication_role())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -8555,20 +8605,28 @@ do_pg_stop_backup(void)
* OK to clear forcePageWrites
*/
LWLockAcquire(WALInsertLock, LW_EXCLUSIVE);
- XLogCtl->Insert.forcePageWrites = false;
+ if (exclusive)
+ {
+ if (XLogCtl->Insert.exclusiveBackup)
+ {
+ XLogCtl->Insert.forcePageWrites--;
+ XLogCtl->Insert.exclusiveBackup = false;
+ }
+ }
+ XLogCtl->Insert.forcePageWrites--;
LWLockRelease(WALInsertLock);
/*
* Open the existing label file
*/
- lfp = AllocateFile(BACKUP_LABEL_FILE, "r");
+ lfp = AllocateFile(labelfile, "r");
if (!lfp)
{
if (errno != ENOENT)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not read file \"%s\": %m",
- BACKUP_LABEL_FILE)));
+ labelfile)));
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("a backup is not in progress")));
@@ -8643,7 +8701,7 @@ do_pg_stop_backup(void)
(errcode_for_file_access(),
errmsg("could not read file \"%s\": %m",
BACKUP_LABEL_FILE)));
- if (unlink(BACKUP_LABEL_FILE) != 0)
+ if (unlink(labelfile) != 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not remove file \"%s\": %m",
@@ -8730,9 +8788,13 @@ do_pg_stop_backup(void)
/*
* do_pg_abort_backup: abort a running backup
*
- * This does just the most basic steps of pg_stop_backup(), by taking the
+ * This does just the most basic steps of do_pg_stop_backup(), by taking the
* system out of backup mode, thus making it a lot more safe to call from
* an error handler.
+ *
+ * NB: This is only for aborting a non-exclusive backup that doesn't write
+ * backup_label. A backup started with pg_stop_backup() needs to be finished
+ * with pg_stop_backup().
*/
void
do_pg_abort_backup(void)
@@ -8741,17 +8803,14 @@ do_pg_abort_backup(void)
* OK to clear forcePageWrites
*/
LWLockAcquire(WALInsertLock, LW_EXCLUSIVE);
- XLogCtl->Insert.forcePageWrites = false;
- LWLockRelease(WALInsertLock);
-
/*
- * Remove backup label file
+ * The caller should take care to not call us only if a backup, but
+ * be extra paranoid to avoid wrapping forcePageWrites around if the
+ * caller screws up.
*/
- if (unlink(BACKUP_LABEL_FILE) != 0)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not remove file \"%s\": %m",
- BACKUP_LABEL_FILE)));
+ if (XLogCtl->Insert.forcePageWrites > 0)
+ XLogCtl->Insert.forcePageWrites--;
+ LWLockRelease(WALInsertLock);
}
/*
diff --git a/src/backend/replication/basebackup.c b/src/backend/replication/basebackup.c
index de5fa22..a3d4e5c 100644
--- a/src/backend/replication/basebackup.c
+++ b/src/backend/replication/basebackup.c
@@ -31,13 +31,17 @@
#include "utils/memutils.h"
#include "utils/ps_status.h"
+/* XXX: copied from xlog.c */
+#define BACKUP_LABEL_FILE "backup_label"
+
static int64 sendDir(char *path, int basepathlen, bool sizeonly);
-static void sendFile(char *path, int basepathlen, struct stat * statbuf);
+static void sendFile(char *readfilename, char *tarfilename,
+ struct stat * statbuf);
static void _tarWriteHeader(char *filename, char *linktarget,
struct stat * statbuf);
static void send_int8_string(StringInfoData *buf, int64 intval);
static void SendBackupHeader(List *tablespaces);
-static void SendBackupDirectory(char *location, char *spcoid);
+static void SendBackupDirectories(List *tablespaces, char *labelfilepath);
static void base_backup_cleanup(int code, Datum arg);
typedef struct
@@ -75,6 +79,7 @@ SendBaseBackup(const char *options)
tablespaceinfo *ti;
MemoryContext backup_context;
MemoryContext old_context;
+ char *labelfilepath;
backup_context = AllocSetContextCreate(CurrentMemoryContext,
"Streaming base backup context",
@@ -145,26 +150,19 @@ SendBaseBackup(const char *options)
}
FreeDir(dir);
- do_pg_start_backup(backup_label, true);
+ do_pg_start_backup(backup_label, true, false, &labelfilepath);
PG_ENSURE_ERROR_CLEANUP(base_backup_cleanup, (Datum) 0);
{
- ListCell *lc;
-
/* Send tablespace header */
SendBackupHeader(tablespaces);
- /* Send off our tablespaces one by one */
- foreach(lc, tablespaces)
- {
- ti = (tablespaceinfo *) lfirst(lc);
-
- SendBackupDirectory(ti->path, ti->oid);
- }
+ /* Send the tars */
+ SendBackupDirectories(tablespaces, labelfilepath);
}
PG_END_ENSURE_ERROR_CLEANUP(base_backup_cleanup, (Datum) 0);
- do_pg_stop_backup();
+ do_pg_stop_backup(labelfilepath);
MemoryContextSwitchTo(old_context);
MemoryContextDelete(backup_context);
@@ -250,20 +248,44 @@ SendBackupHeader(List *tablespaces)
}
static void
-SendBackupDirectory(char *location, char *spcoid)
+SendBackupDirectories(List *tablespaces, char *labelfilepath)
{
StringInfoData buf;
+ ListCell *lc;
- /* Send CopyOutResponse message */
- pq_beginmessage(&buf, 'H');
- pq_sendbyte(&buf, 0); /* overall format */
- pq_sendint(&buf, 0, 2); /* natts */
- pq_endmessage(&buf);
+ /* Send off our tablespaces one by one */
+ foreach(lc, tablespaces)
+ {
+ tablespaceinfo *ti = (tablespaceinfo *) lfirst(lc);
+ char *location = ti->path;
+
+ /* Send CopyOutResponse message */
+ pq_beginmessage(&buf, 'H');
+ pq_sendbyte(&buf, 0); /* overall format */
+ pq_sendint(&buf, 0, 2); /* natts */
+ pq_endmessage(&buf);
+
+ /*
+ * tar up the data directory if NULL, otherwise the tablespace
+ * In the main tar, also include the backup_label.
+ */
+ if (location == NULL)
+ {
+ struct stat statbuf;
+
+ if (lstat(labelfilepath, &statbuf) != 0)
+ ereport(ERROR,
+ (errcode(errcode_for_file_access()),
+ errmsg("could not read temporary backup label file \"%s\": %m",
+ labelfilepath)));
- /* tar up the data directory if NULL, otherwise the tablespace */
- sendDir(location == NULL ? "." : location,
- location == NULL ? 1 : strlen(location),
- false);
+ sendFile(labelfilepath, BACKUP_LABEL_FILE, &statbuf);
+
+ location = ".";
+ }
+
+ sendDir(location, strlen(location), false);
+ }
/* Send CopyDone message */
pq_putemptymessage('c');
@@ -360,7 +382,7 @@ sendDir(char *path, int basepathlen, bool sizeonly)
/* Add size, rounded up to 512byte block */
size += ((statbuf.st_size + 511) & ~511);
if (!sizeonly)
- sendFile(pathbuf, basepathlen, &statbuf);
+ sendFile(pathbuf, pathbuf + basepathlen + 1, &statbuf);
size += 512; /* Size of the header of the file */
}
else
@@ -418,7 +440,7 @@ _tarChecksum(char *header)
/* Given the member, write the TAR header & send the file */
static void
-sendFile(char *filename, int basepathlen, struct stat * statbuf)
+sendFile(char *readfilename, char *tarfilename, struct stat * statbuf)
{
FILE *fp;
char buf[32768];
@@ -426,11 +448,13 @@ sendFile(char *filename, int basepathlen, struct stat * statbuf)
pgoff_t len = 0;
size_t pad;
- fp = AllocateFile(filename, "rb");
+ pg_usleep(100000);
+
+ fp = AllocateFile(readfilename, "rb");
if (fp == NULL)
ereport(ERROR,
(errcode(errcode_for_file_access()),
- errmsg("could not open file \"%s\": %m", filename)));
+ errmsg("could not open file \"%s\": %m", readfilename)));
/*
* Some compilers will throw a warning knowing this test can never be true
@@ -439,9 +463,9 @@ sendFile(char *filename, int basepathlen, struct stat * statbuf)
if (statbuf->st_size > MAX_TAR_MEMBER_FILELEN)
ereport(ERROR,
(errmsg("archive member \"%s\" too large for tar format",
- filename)));
+ tarfilename)));
- _tarWriteHeader(filename + basepathlen + 1, NULL, statbuf);
+ _tarWriteHeader(tarfilename, NULL, statbuf);
while ((cnt = fread(buf, 1, Min(sizeof(buf), statbuf->st_size - len), fp)) > 0)
{
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index 74d3427..fb8d715 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -312,8 +312,8 @@ extern void HandleStartupProcInterrupts(void);
extern void StartupProcessMain(void);
extern void WakeupRecovery(void);
-extern XLogRecPtr do_pg_start_backup(const char *backupidstr, bool fast);
-extern XLogRecPtr do_pg_stop_backup(void);
+extern XLogRecPtr do_pg_start_backup(const char *backupidstr, bool fast, bool exclusive, char **labelfilepath);
+extern XLogRecPtr do_pg_stop_backup(const char *labelfile);
extern void do_pg_abort_backup(void);
#endif /* XLOG_H */
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers