synchrep_0915.patch
application/octet-stream
Filename: synchrep_0915.patch
Type: application/octet-stream
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: context
| File | + | − |
|---|---|---|
| doc/src/sgml/protocol.sgml | 38 | 2 |
| doc/src/sgml/recovery-config.sgml | 11 | 0 |
| src/backend/access/transam/recovery.conf.sample | 5 | 3 |
| src/backend/access/transam/twophase.c | 30 | 0 |
| src/backend/access/transam/xact.c | 12 | 0 |
| src/backend/access/transam/xlog.c | 51 | 0 |
| src/backend/libpq/hba.c | 36 | 3 |
| src/backend/Makefile | 3 | 0 |
| src/backend/port/unix_latch.c | 10 | 0 |
| src/backend/port/win32_latch.c | 24 | 0 |
| src/backend/postmaster/postmaster.c | 13 | 0 |
| src/backend/replication/libpqwalreceiver/libpqwalreceiver.c | 52 | 0 |
| src/backend/replication/standbys.conf.sample | 35 | 0 |
| src/backend/replication/walreceiver.c | 68 | 0 |
| src/backend/replication/walreceiverfuncs.c | 9 | 0 |
| src/backend/replication/walsender.c | 543 | 0 |
| src/backend/storage/ipc/procsignal.c | 21 | 0 |
| src/backend/storage/lmgr/proc.c | 4 | 0 |
| src/backend/utils/init/postinit.c | 21 | 0 |
| src/include/access/xlog.h | 38 | 0 |
| src/include/libpq/hba.h | 7 | 0 |
| src/include/replication/walprotocol.h | 10 | 0 |
| src/include/replication/walreceiver.h | 20 | 0 |
| src/include/replication/walsender.h | 34 | 0 |
| src/include/storage/latch.h | 5 | 0 |
| src/include/storage/lwlock.h | 1 | 0 |
| src/include/storage/proc.h | 7 | 0 |
| src/include/storage/procsignal.h | 4 | 0 |
| src/interfaces/libpq/fe-connect.c | 7 | 0 |
| src/interfaces/libpq/fe-exec.c | 5 | 0 |
| src/interfaces/libpq/fe-protocol3.c | 2 | 0 |
| src/interfaces/libpq/libpq-int.h | 1 | 0 |
*** a/doc/src/sgml/protocol.sgml
--- b/doc/src/sgml/protocol.sgml
***************
*** 1291,1298 ****
To initiate streaming replication, the frontend sends the
<literal>replication</> parameter in the startup message. This tells the
backend to go into walsender mode, wherein a small set of replication commands
! can be issued instead of SQL statements. Only the simple query protocol can be
! used in walsender mode.
The commands accepted in walsender mode are:
--- 1291,1299 ----
To initiate streaming replication, the frontend sends the
<literal>replication</> parameter in the startup message. This tells the
backend to go into walsender mode, wherein a small set of replication commands
! can be issued instead of SQL statements. Also the startup message includes
! <literal>standby_name</> parameter if it's supplied in <filename>recovery.conf</>.
! Only the simple query protocol can be used in walsender mode.
The commands accepted in walsender mode are:
***************
*** 1360,1365 **** The commands accepted in walsender mode are:
--- 1361,1401 ----
<variablelist>
<varlistentry>
<term>
+ XLogRecPtr (F)
+ </term>
+ <listitem>
+ <para>
+ <variablelist>
+ <varlistentry>
+ <term>
+ Byte1('l')
+ </term>
+ <listitem>
+ <para>
+ Identifies the message as an acknowledgment of replication.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ Byte8
+ </term>
+ <listitem>
+ <para>
+ The end of the WAL data replicated to the standby, given in
+ XLogRecPtr format.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ <variablelist>
+ <varlistentry>
+ <term>
XLogData (B)
</term>
<listitem>
*** a/doc/src/sgml/recovery-config.sgml
--- b/doc/src/sgml/recovery-config.sgml
***************
*** 243,248 **** restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
--- 243,259 ----
</para>
</listitem>
</varlistentry>
+ <varlistentry id="standby-name" xreflabel="standby_name">
+ <term><varname>standby_name</varname> (<type>string</type>)</term>
+ <indexterm>
+ <primary><varname>standby_name</> recovery parameter</primary>
+ </indexterm>
+ <listitem>
+ <para>
+ Specifies a name of the standby server.
+ </para>
+ </listitem>
+ </varlistentry>
<varlistentry id="primary-conninfo" xreflabel="primary_conninfo">
<term><varname>primary_conninfo</varname> (<type>string</type>)</term>
<indexterm>
*** a/src/backend/Makefile
--- b/src/backend/Makefile
***************
*** 208,213 **** endif
--- 208,214 ----
$(INSTALL_DATA) $(srcdir)/libpq/pg_ident.conf.sample '$(DESTDIR)$(datadir)/pg_ident.conf.sample'
$(INSTALL_DATA) $(srcdir)/utils/misc/postgresql.conf.sample '$(DESTDIR)$(datadir)/postgresql.conf.sample'
$(INSTALL_DATA) $(srcdir)/access/transam/recovery.conf.sample '$(DESTDIR)$(datadir)/recovery.conf.sample'
+ $(INSTALL_DATA) $(srcdir)/replication/standbys.conf.sample '$(DESTDIR)$(datadir)/standbys.conf.sample'
install-bin: postgres $(POSTGRES_IMP) installdirs
$(INSTALL_PROGRAM) postgres$(X) '$(DESTDIR)$(bindir)/postgres$(X)'
***************
*** 262,268 **** endif
rm -f '$(DESTDIR)$(datadir)/pg_hba.conf.sample' \
'$(DESTDIR)$(datadir)/pg_ident.conf.sample' \
'$(DESTDIR)$(datadir)/postgresql.conf.sample' \
! '$(DESTDIR)$(datadir)/recovery.conf.sample'
##########################################################################
--- 263,270 ----
rm -f '$(DESTDIR)$(datadir)/pg_hba.conf.sample' \
'$(DESTDIR)$(datadir)/pg_ident.conf.sample' \
'$(DESTDIR)$(datadir)/postgresql.conf.sample' \
! '$(DESTDIR)$(datadir)/recovery.conf.sample' \
! '$(DESTDIR)$(datadir)/standbys.conf.sample'
##########################################################################
*** a/src/backend/access/transam/recovery.conf.sample
--- b/src/backend/access/transam/recovery.conf.sample
***************
*** 91,102 ****
#---------------------------------------------------------------------------
#
# When standby_mode is enabled, the PostgreSQL server will work as
! # a standby. It tries to connect to the primary according to the
! # connection settings primary_conninfo, and receives XLOG records
! # continuously.
#
#standby_mode = 'off'
#
#primary_conninfo = '' # e.g. 'host=localhost port=5432'
#
#
--- 91,104 ----
#---------------------------------------------------------------------------
#
# When standby_mode is enabled, the PostgreSQL server will work as
! # a standby under the name of standby_name. It tries to connect to
! # the primary according to the connection settings primary_conninfo,
! # and receives XLOG records continuously.
#
#standby_mode = 'off'
#
+ #standby_name = ''
+ #
#primary_conninfo = '' # e.g. 'host=localhost port=5432'
#
#
*** a/src/backend/access/transam/twophase.c
--- b/src/backend/access/transam/twophase.c
***************
*** 1070,1075 **** EndPrepare(GlobalTransaction gxact)
--- 1070,1087 ----
END_CRIT_SECTION();
+ /*
+ * Wait for WAL to be replicated up to the PREPARE record
+ * if replication is enabled. This operation has to be performed
+ * after the PREPARE record is generated and before other
+ * transactions know that this one has already been prepared.
+ *
+ * XXX: Since the caller prevents cancel/die interrupt, we cannot
+ * process that while waiting. Should we remove this restriction?
+ */
+ if (max_wal_senders > 0)
+ WaitXLogSend(gxact->prepare_lsn);
+
records.tail = records.head = NULL;
}
***************
*** 2027,2032 **** RecordTransactionCommitPrepared(TransactionId xid,
--- 2039,2053 ----
MyProc->inCommit = false;
END_CRIT_SECTION();
+
+ /*
+ * Wait for WAL to be replicated up to the COMMIT PREPARED record
+ * if replication is enabled. This operation has to be performed
+ * after the COMMIT PREPARED record is generated and before other
+ * transactions know that this one has already been committed.
+ */
+ if (max_wal_senders > 0)
+ WaitXLogSend(recptr);
}
/*
***************
*** 2106,2109 **** RecordTransactionAbortPrepared(TransactionId xid,
--- 2127,2139 ----
TransactionIdAbortTree(xid, nchildren, children);
END_CRIT_SECTION();
+
+ /*
+ * Wait for WAL to be replicated up to the ABORT PREPARED record
+ * if replication is enabled. This operation has to be performed
+ * after the ABORT PREPARED record is generated and before other
+ * transactions know that this one has already been aborted.
+ */
+ if (max_wal_senders > 0)
+ WaitXLogSend(recptr);
}
*** a/src/backend/access/transam/xact.c
--- b/src/backend/access/transam/xact.c
***************
*** 1118,1123 **** RecordTransactionCommit(void)
--- 1118,1135 ----
/* Compute latestXid while we have the child XIDs handy */
latestXid = TransactionIdLatest(xid, nchildren, children);
+ /*
+ * Wait for WAL to be replicated up to the COMMIT record if replication
+ * is enabled. This operation has to be performed after the COMMIT record
+ * is generated and before other transactions know that this one has
+ * already been committed.
+ *
+ * XXX: Since the caller prevents cancel/die interrupt, we cannot
+ * process that while waiting. Should we remove this restriction?
+ */
+ if (max_wal_senders > 0)
+ WaitXLogSend(XactLastRecEnd);
+
/* Reset XactLastRecEnd until the next transaction writes something */
XactLastRecEnd.xrecoff = 0;
*** a/src/backend/access/transam/xlog.c
--- b/src/backend/access/transam/xlog.c
***************
*** 189,194 **** static TimestampTz recoveryTargetTime;
--- 189,195 ----
static bool StandbyMode = false;
static char *PrimaryConnInfo = NULL;
static char *TriggerFile = NULL;
+ static char *StandbyName = NULL;
/* if recoveryStopsHere returns true, it saves actual stop xid/time here */
static TransactionId recoveryStopXid;
***************
*** 532,537 **** typedef struct xl_parameter_change
--- 533,548 ----
int wal_level;
} xl_parameter_change;
+ /* Replication mode names */
+ const char *ReplicationModeNames[] = {
+ "async", /* REPLICATION_MODE_ASYNC */
+ "recv", /* REPLICATION_MODE_RECV */
+ "fsync", /* REPLICATION_MODE_FSYNC */
+ "replay" /* REPLICATION_MODE_REPLAY */
+ };
+
+ ReplicationMode rplMode = InvalidReplicationMode;
+
/*
* Flags set by interrupt handlers for later service in the redo loop.
*/
***************
*** 5258,5263 **** readRecoveryCommandFile(void)
--- 5269,5281 ----
(errmsg("trigger_file = '%s'",
TriggerFile)));
}
+ else if (strcmp(tok1, "standby_name") == 0)
+ {
+ StandbyName = pstrdup(tok2);
+ ereport(DEBUG2,
+ (errmsg("standby_name = '%s'",
+ StandbyName)));
+ }
else
ereport(FATAL,
(errmsg("unrecognized recovery parameter \"%s\"",
***************
*** 6867,6872 **** GetFlushRecPtr(void)
--- 6885,6907 ----
}
/*
+ * GetReplayRecPtr -- Returns the last replay position.
+ */
+ XLogRecPtr
+ GetReplayRecPtr(void)
+ {
+ /* use volatile pointer to prevent code rearrangement */
+ volatile XLogCtlData *xlogctl = XLogCtl;
+ XLogRecPtr recptr;
+
+ SpinLockAcquire(&xlogctl->info_lck);
+ recptr = xlogctl->recoveryLastRecPtr;
+ SpinLockRelease(&xlogctl->info_lck);
+
+ return recptr;
+ }
+
+ /*
* Get the time of the last xlog segment switch
*/
pg_time_t
***************
*** 8828,8842 **** pg_last_xlog_receive_location(PG_FUNCTION_ARGS)
Datum
pg_last_xlog_replay_location(PG_FUNCTION_ARGS)
{
- /* use volatile pointer to prevent code rearrangement */
- volatile XLogCtlData *xlogctl = XLogCtl;
XLogRecPtr recptr;
char location[MAXFNAMELEN];
! SpinLockAcquire(&xlogctl->info_lck);
! recptr = xlogctl->recoveryLastRecPtr;
! SpinLockRelease(&xlogctl->info_lck);
!
if (recptr.xlogid == 0 && recptr.xrecoff == 0)
PG_RETURN_NULL();
--- 8863,8872 ----
Datum
pg_last_xlog_replay_location(PG_FUNCTION_ARGS)
{
XLogRecPtr recptr;
char location[MAXFNAMELEN];
! recptr = GetReplayRecPtr();
if (recptr.xlogid == 0 && recptr.xrecoff == 0)
PG_RETURN_NULL();
***************
*** 9467,9473 **** retry:
{
RequestXLogStreaming(
fetching_ckpt ? RedoStartLSN : *RecPtr,
! PrimaryConnInfo);
continue;
}
}
--- 9497,9503 ----
{
RequestXLogStreaming(
fetching_ckpt ? RedoStartLSN : *RecPtr,
! PrimaryConnInfo, StandbyName);
continue;
}
}
***************
*** 9681,9683 **** CheckForStandbyTrigger(void)
--- 9711,9727 ----
}
return false;
}
+
+ /*
+ * Look up replication mode value by name.
+ */
+ ReplicationMode
+ ReplicationModeNameGetValue(char *name)
+ {
+ ReplicationMode mode;
+
+ for (mode = 0; mode <= MAXREPLICATIONMODE; mode++)
+ if (strcmp(ReplicationModeNames[mode], name) == 0)
+ return mode;
+ return InvalidReplicationMode;
+ }
*** a/src/backend/libpq/hba.c
--- b/src/backend/libpq/hba.c
***************
*** 38,46 ****
#define atooid(x) ((Oid) strtoul((x), NULL, 10))
#define atoxid(x) ((TransactionId) strtoul((x), NULL, 10))
- /* This is used to separate values in multi-valued column strings */
- #define MULTI_VALUE_SEP "\001"
-
#define MAX_TOKEN 256
/* callback data for check_network_callback */
--- 38,43 ----
***************
*** 54,59 **** typedef struct check_network_data
--- 51,59 ----
/* pre-parsed content of HBA config file: list of HbaLine structs */
static List *parsed_hba_lines = NIL;
+ static const char *hba_keywords[] = {"all", "sameuser", "samegroup", "samerole",
+ "replication", NULL};
+
/*
* These variables hold the pre-parsed contents of the ident usermap
* configuration file. ident_lines is a list of sublists, one sublist for
***************
*** 67,76 **** static List *ident_lines = NIL;
static List *ident_line_nums = NIL;
- static void tokenize_file(const char *filename, FILE *file,
- List **lines, List **line_nums);
static char *tokenize_inc_file(const char *outer_filename,
! const char *inc_filename);
/*
* isblank() exists in the ISO C99 spec, but it's not very portable yet,
--- 67,74 ----
static List *ident_line_nums = NIL;
static char *tokenize_inc_file(const char *outer_filename,
! const char *inc_filename, const char **keywords);
/*
* isblank() exists in the ISO C99 spec, but it's not very portable yet,
***************
*** 108,114 **** pg_isblank(const char c)
* token.
*/
static bool
! next_token(FILE *fp, char *buf, int bufsz, bool *initial_quote)
{
int c;
char *start_buf = buf;
--- 106,113 ----
* token.
*/
static bool
! next_token(const char *filename, FILE *fp, char *buf, int bufsz,
! bool *initial_quote, const char **keywords)
{
int c;
char *start_buf = buf;
***************
*** 155,162 **** next_token(FILE *fp, char *buf, int bufsz, bool *initial_quote)
*buf = '\0';
ereport(LOG,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
! errmsg("authentication file token too long, skipping: \"%s\"",
! start_buf)));
/* Discard remainder of line */
while ((c = getc(fp)) != EOF && c != '\n')
;
--- 154,161 ----
*buf = '\0';
ereport(LOG,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
! errmsg("configuration file \"%s\" token too long, skipping: \"%s\"",
! filename, start_buf)));
/* Discard remainder of line */
while ((c = getc(fp)) != EOF && c != '\n')
;
***************
*** 196,211 **** next_token(FILE *fp, char *buf, int bufsz, bool *initial_quote)
*buf = '\0';
! if (!saw_quote &&
! (strcmp(start_buf, "all") == 0 ||
! strcmp(start_buf, "sameuser") == 0 ||
! strcmp(start_buf, "samegroup") == 0 ||
! strcmp(start_buf, "samerole") == 0 ||
! strcmp(start_buf, "replication") == 0))
{
! /* append newline to a magical keyword */
! *buf++ = '\n';
! *buf = '\0';
}
return (saw_quote || buf > start_buf);
--- 195,214 ----
*buf = '\0';
! if (!saw_quote)
{
! const char **entry;
!
! for (entry = keywords; *entry != NULL; entry++)
! {
! if (strcmp(start_buf, *entry) == 0)
! {
! /* append newline to a magical keyword */
! *buf++ = '\n';
! *buf = '\0';
! break;
! }
! }
}
return (saw_quote || buf > start_buf);
***************
*** 219,225 **** next_token(FILE *fp, char *buf, int bufsz, bool *initial_quote)
* The result is a palloc'd string, or NULL if we have reached EOL.
*/
static char *
! next_token_expand(const char *filename, FILE *file)
{
char buf[MAX_TOKEN];
char *comma_str = pstrdup("");
--- 222,228 ----
* The result is a palloc'd string, or NULL if we have reached EOL.
*/
static char *
! next_token_expand(const char *filename, FILE *file, const char **keywords)
{
char buf[MAX_TOKEN];
char *comma_str = pstrdup("");
***************
*** 231,237 **** next_token_expand(const char *filename, FILE *file)
do
{
! if (!next_token(file, buf, sizeof(buf), &initial_quote))
break;
got_something = true;
--- 234,241 ----
do
{
! if (!next_token(filename, file, buf, sizeof(buf), &initial_quote,
! keywords))
break;
got_something = true;
***************
*** 246,252 **** next_token_expand(const char *filename, FILE *file)
/* Is this referencing a file? */
if (!initial_quote && buf[0] == '@' && buf[1] != '\0')
! incbuf = tokenize_inc_file(filename, buf + 1);
else
incbuf = pstrdup(buf);
--- 250,256 ----
/* Is this referencing a file? */
if (!initial_quote && buf[0] == '@' && buf[1] != '\0')
! incbuf = tokenize_inc_file(filename, buf + 1, keywords);
else
incbuf = pstrdup(buf);
***************
*** 273,279 **** next_token_expand(const char *filename, FILE *file)
/*
* Free memory used by lines/tokens (i.e., structure built by tokenize_file)
*/
! static void
free_lines(List **lines, List **line_nums)
{
/*
--- 277,283 ----
/*
* Free memory used by lines/tokens (i.e., structure built by tokenize_file)
*/
! void
free_lines(List **lines, List **line_nums)
{
/*
***************
*** 318,324 **** free_lines(List **lines, List **line_nums)
static char *
tokenize_inc_file(const char *outer_filename,
! const char *inc_filename)
{
char *inc_fullname;
FILE *inc_file;
--- 322,328 ----
static char *
tokenize_inc_file(const char *outer_filename,
! const char *inc_filename, const char **keywords)
{
char *inc_fullname;
FILE *inc_file;
***************
*** 348,354 **** tokenize_inc_file(const char *outer_filename,
{
ereport(LOG,
(errcode_for_file_access(),
! errmsg("could not open secondary authentication file \"@%s\" as \"%s\": %m",
inc_filename, inc_fullname)));
pfree(inc_fullname);
--- 352,358 ----
{
ereport(LOG,
(errcode_for_file_access(),
! errmsg("could not open secondary configuration file \"@%s\" as \"%s\": %m",
inc_filename, inc_fullname)));
pfree(inc_fullname);
***************
*** 357,363 **** tokenize_inc_file(const char *outer_filename,
}
/* There is possible recursion here if the file contains @ */
! tokenize_file(inc_fullname, inc_file, &inc_lines, &inc_line_nums);
FreeFile(inc_file);
pfree(inc_fullname);
--- 361,368 ----
}
/* There is possible recursion here if the file contains @ */
! tokenize_file(inc_fullname, inc_file, &inc_lines, &inc_line_nums,
! keywords);
FreeFile(inc_file);
pfree(inc_fullname);
***************
*** 404,412 **** tokenize_inc_file(const char *outer_filename,
*
* filename must be the absolute path to the target file.
*/
! static void
tokenize_file(const char *filename, FILE *file,
! List **lines, List **line_nums)
{
List *current_line = NIL;
int line_number = 1;
--- 409,417 ----
*
* filename must be the absolute path to the target file.
*/
! void
tokenize_file(const char *filename, FILE *file,
! List **lines, List **line_nums, const char **keywords)
{
List *current_line = NIL;
int line_number = 1;
***************
*** 416,422 **** tokenize_file(const char *filename, FILE *file,
while (!feof(file) && !ferror(file))
{
! buf = next_token_expand(filename, file);
/* add token to list, unless we are at EOL or comment start */
if (buf)
--- 421,427 ----
while (!feof(file) && !ferror(file))
{
! buf = next_token_expand(filename, file, keywords);
/* add token to list, unless we are at EOL or comment start */
if (buf)
***************
*** 1490,1496 **** load_hba(void)
return false;
}
! tokenize_file(HbaFileName, file, &hba_lines, &hba_line_nums);
FreeFile(file);
/* Now parse all the lines */
--- 1495,1501 ----
return false;
}
! tokenize_file(HbaFileName, file, &hba_lines, &hba_line_nums, hba_keywords);
FreeFile(file);
/* Now parse all the lines */
***************
*** 1809,1815 **** load_ident(void)
}
else
{
! tokenize_file(IdentFileName, file, &ident_lines, &ident_line_nums);
FreeFile(file);
}
}
--- 1814,1821 ----
}
else
{
! tokenize_file(IdentFileName, file, &ident_lines, &ident_line_nums,
! hba_keywords);
FreeFile(file);
}
}
*** a/src/backend/port/unix_latch.c
--- b/src/backend/port/unix_latch.c
***************
*** 310,315 **** SetLatch(volatile Latch *latch)
--- 310,325 ----
}
/*
+ * Signal the given reason, in addition to SetLatch.
+ */
+ void
+ SetProcLatch(volatile Latch *latch, ProcSignalReason reason, BackendId backendId)
+ {
+ SetProcSignalReason(latch->owner_pid, reason, backendId);
+ SetLatch(latch);
+ }
+
+ /*
* Clear the latch. Calling WaitLatch after this will sleep, unless
* the latch is set again before the WaitLatch call.
*/
*** a/src/backend/port/win32_latch.c
--- b/src/backend/port/win32_latch.c
***************
*** 49,54 **** InitLatch(volatile Latch *latch)
--- 49,55 ----
latch->event = CreateEvent(NULL, TRUE, FALSE, NULL);
latch->is_shared = false;
latch->is_set = false;
+ latch->owner_pid = MyProcPid;
}
void
***************
*** 57,62 **** InitSharedLatch(volatile Latch *latch)
--- 58,64 ----
latch->is_shared = true;
latch->is_set = false;
latch->event = NULL;
+ latch->owner_pid = 0;
}
void
***************
*** 81,86 **** OwnLatch(volatile Latch *latch)
--- 83,89 ----
SpinLockRelease(&sharedHandles->mutex);
latch->event = event;
+ latch->owner_pid = MyProcPid;
}
void
***************
*** 88,93 **** DisownLatch(volatile Latch *latch)
--- 91,97 ----
{
Assert(latch->is_shared);
Assert(latch->event != NULL);
+ Assert(latch->owner_pid == MyProcPid);
/* Put the event handle back to the pool */
SpinLockAcquire(&sharedHandles->mutex);
***************
*** 101,106 **** DisownLatch(volatile Latch *latch)
--- 105,111 ----
SpinLockRelease(&sharedHandles->mutex);
latch->event = NULL;
+ latch->owner_pid = 0;
}
bool
***************
*** 119,124 **** WaitLatchOrSocket(volatile Latch *latch, SOCKET sock, long timeout)
--- 124,132 ----
int numevents;
int result = 0;
+ if (latch->owner_pid != MyProcPid)
+ elog(ERROR, "cannot wait on a latch owned by another process");
+
latchevent = latch->event;
events[0] = latchevent;
***************
*** 212,220 **** SetLatch(volatile Latch *latch)
--- 220,241 ----
}
}
+ /*
+ * Signal the given reason, in addition to SetLatch.
+ */
+ void
+ SetProcLatch(volatile Latch *latch, ProcSignalReason reason, BackendId backendId)
+ {
+ SetProcSignalReason(latch->owner_pid, reason, backendId);
+ SetLatch(latch);
+ }
+
void
ResetLatch(volatile Latch *latch)
{
+ /* Only the owner should reset the latch */
+ Assert(latch->owner_pid == MyProcPid);
+
latch->is_set = false;
}
***************
*** 231,236 **** NumSharedLatches(void)
--- 252,260 ----
/* Each walsender needs one latch */
numLatches += max_wal_senders;
+ /* Each backend needs one latch */
+ numLatches += MaxBackends;
+
return numLatches;
}
*** a/src/backend/postmaster/postmaster.c
--- b/src/backend/postmaster/postmaster.c
***************
*** 1063,1069 **** PostmasterMain(int argc, char *argv[])
autovac_init();
/*
! * Load configuration files for client authentication.
*/
if (!load_hba())
{
--- 1063,1069 ----
autovac_init();
/*
! * Load configuration files for client authentication and replication.
*/
if (!load_hba())
{
***************
*** 1075,1080 **** PostmasterMain(int argc, char *argv[])
--- 1075,1085 ----
(errmsg("could not load pg_hba.conf")));
}
load_ident();
+ if (max_wal_senders > 0 && !load_standbys())
+ {
+ ereport(FATAL,
+ (errmsg("could not load standbys.conf")));
+ }
/*
* Remember postmaster startup time
***************
*** 1713,1718 **** retry1:
--- 1718,1725 ----
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid value for boolean option \"replication\"")));
}
+ else if (strcmp(nameptr, "standby_name") == 0)
+ standby_name = pstrdup(valptr);
else
{
/* Assume it's a generic GUC option */
***************
*** 2129,2134 **** SIGHUP_handler(SIGNAL_ARGS)
--- 2136,2146 ----
load_ident();
+ /* Reload standbys configuration file too */
+ if (max_wal_senders > 0 && !load_standbys())
+ ereport(WARNING,
+ (errmsg("standbys.conf not reloaded")));
+
#ifdef EXEC_BACKEND
/* Update the starting-point file for future children */
write_nondefault_variables(PGC_SIGHUP);
*** a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
--- b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
***************
*** 47,55 **** static bool justconnected = false;
static char *recvBuf = NULL;
/* Prototypes for interface functions */
! static bool libpqrcv_connect(char *conninfo, XLogRecPtr startpoint);
static bool libpqrcv_receive(int timeout, unsigned char *type,
char **buffer, int *len);
static void libpqrcv_disconnect(void);
/* Prototypes for private functions */
--- 47,57 ----
static char *recvBuf = NULL;
/* Prototypes for interface functions */
! static bool libpqrcv_connect(char *conninfo, XLogRecPtr startpoint,
! char *standbyName);
static bool libpqrcv_receive(int timeout, unsigned char *type,
char **buffer, int *len);
+ static void libpqrcv_send(const char *buffer, int nbytes);
static void libpqrcv_disconnect(void);
/* Prototypes for private functions */
***************
*** 64,73 **** _PG_init(void)
{
/* Tell walreceiver how to reach us */
if (walrcv_connect != NULL || walrcv_receive != NULL ||
! walrcv_disconnect != NULL)
elog(ERROR, "libpqwalreceiver already loaded");
walrcv_connect = libpqrcv_connect;
walrcv_receive = libpqrcv_receive;
walrcv_disconnect = libpqrcv_disconnect;
}
--- 66,76 ----
{
/* Tell walreceiver how to reach us */
if (walrcv_connect != NULL || walrcv_receive != NULL ||
! walrcv_send != NULL || walrcv_disconnect != NULL)
elog(ERROR, "libpqwalreceiver already loaded");
walrcv_connect = libpqrcv_connect;
walrcv_receive = libpqrcv_receive;
+ walrcv_send = libpqrcv_send;
walrcv_disconnect = libpqrcv_disconnect;
}
***************
*** 75,98 **** _PG_init(void)
* Establish the connection to the primary server for XLOG streaming
*/
static bool
! libpqrcv_connect(char *conninfo, XLogRecPtr startpoint)
{
! char conninfo_repl[MAXCONNINFO + 37];
char *primary_sysid;
char standby_sysid[32];
TimeLineID primary_tli;
TimeLineID standby_tli;
PGresult *res;
char cmd[64];
/*
! * Connect using deliberately undocumented parameter: replication. The
! * database name is ignored by the server in replication mode, but specify
! * "replication" for .pgpass lookup.
*/
! snprintf(conninfo_repl, sizeof(conninfo_repl),
! "%s dbname=replication replication=true",
! conninfo);
streamConn = PQconnectdb(conninfo_repl);
if (PQstatus(streamConn) != CONNECTION_OK)
--- 78,107 ----
* Establish the connection to the primary server for XLOG streaming
*/
static bool
! libpqrcv_connect(char *conninfo, XLogRecPtr startpoint, char *standbyName)
{
! char conninfo_repl[MAXCONNINFO + MAXSTANDBYNAME + 37];
char *primary_sysid;
char standby_sysid[32];
TimeLineID primary_tli;
TimeLineID standby_tli;
+ char *primary_rplMode;
PGresult *res;
char cmd[64];
/*
! * Connect using deliberately undocumented parameter: replication
! * and standby_name. The database name is ignored by the server in
! * replication mode, but specify "replication" for .pgpass lookup.
*/
! if (standbyName && standbyName[0] != '\0')
! snprintf(conninfo_repl, sizeof(conninfo_repl),
! "%s dbname=replication replication=true standby_name='%s'",
! conninfo, standbyName);
! else
! snprintf(conninfo_repl, sizeof(conninfo_repl),
! "%s dbname=replication replication=true",
! conninfo);
streamConn = PQconnectdb(conninfo_repl);
if (PQstatus(streamConn) != CONNECTION_OK)
***************
*** 109,119 **** libpqrcv_connect(char *conninfo, XLogRecPtr startpoint)
{
PQclear(res);
ereport(ERROR,
! (errmsg("could not receive database system identifier and timeline ID from "
! "the primary server: %s",
PQerrorMessage(streamConn))));
}
! if (PQnfields(res) != 2 || PQntuples(res) != 1)
{
int ntuples = PQntuples(res);
int nfields = PQnfields(res);
--- 118,128 ----
{
PQclear(res);
ereport(ERROR,
! (errmsg("could not receive database system identifier, timeline ID and "
! "replication mode from the primary server: %s",
PQerrorMessage(streamConn))));
}
! if (PQnfields(res) != 3 || PQntuples(res) != 1)
{
int ntuples = PQntuples(res);
int nfields = PQnfields(res);
***************
*** 121,131 **** libpqrcv_connect(char *conninfo, XLogRecPtr startpoint)
PQclear(res);
ereport(ERROR,
(errmsg("invalid response from primary server"),
! errdetail("Expected 1 tuple with 2 fields, got %d tuples with %d fields.",
ntuples, nfields)));
}
primary_sysid = PQgetvalue(res, 0, 0);
primary_tli = pg_atoi(PQgetvalue(res, 0, 1), 4, 0);
/*
* Confirm that the system identifier of the primary is the same as ours.
--- 130,141 ----
PQclear(res);
ereport(ERROR,
(errmsg("invalid response from primary server"),
! errdetail("Expected 1 tuple with 3 fields, got %d tuples with %d fields.",
ntuples, nfields)));
}
primary_sysid = PQgetvalue(res, 0, 0);
primary_tli = pg_atoi(PQgetvalue(res, 0, 1), 4, 0);
+ primary_rplMode = PQgetvalue(res, 0, 2);
/*
* Confirm that the system identifier of the primary is the same as ours.
***************
*** 146,158 **** libpqrcv_connect(char *conninfo, XLogRecPtr startpoint)
* recovery target timeline.
*/
standby_tli = GetRecoveryTargetTLI();
- PQclear(res);
if (primary_tli != standby_tli)
ereport(ERROR,
(errmsg("timeline %u of the primary does not match recovery target timeline %u",
primary_tli, standby_tli)));
ThisTimeLineID = primary_tli;
/* Start streaming from the point requested by startup process */
snprintf(cmd, sizeof(cmd), "START_REPLICATION %X/%X",
startpoint.xlogid, startpoint.xrecoff);
--- 156,180 ----
* recovery target timeline.
*/
standby_tli = GetRecoveryTargetTLI();
if (primary_tli != standby_tli)
+ {
+ PQclear(res);
ereport(ERROR,
(errmsg("timeline %u of the primary does not match recovery target timeline %u",
primary_tli, standby_tli)));
+ }
ThisTimeLineID = primary_tli;
+ /*
+ * Confirm that the passed replication mode is valid.
+ */
+ rplMode = ReplicationModeNameGetValue(primary_rplMode);
+ PQclear(res);
+ if (rplMode == InvalidReplicationMode)
+ ereport(ERROR,
+ (errmsg("invalid replication mode \"%s\"",
+ primary_rplMode)));
+
/* Start streaming from the point requested by startup process */
snprintf(cmd, sizeof(cmd), "START_REPLICATION %X/%X",
startpoint.xlogid, startpoint.xrecoff);
***************
*** 398,400 **** libpqrcv_receive(int timeout, unsigned char *type, char **buffer, int *len)
--- 420,437 ----
return true;
}
+
+ /*
+ * Send a message to XLOG stream.
+ *
+ * ereports on error.
+ */
+ static void
+ libpqrcv_send(const char *buffer, int nbytes)
+ {
+ if (PQputCopyData(streamConn, buffer, nbytes) <= 0 ||
+ PQflush(streamConn))
+ ereport(ERROR,
+ (errmsg("could not send data to WAL stream: %s",
+ PQerrorMessage(streamConn))));
+ }
*** /dev/null
--- b/src/backend/replication/standbys.conf.sample
***************
*** 0 ****
--- 1,35 ----
+ # PostgreSQL Standbys Configuration File
+ # ===================================================
+ #
+ # Refer to the "Streaming Replication" section in the PostgreSQL
+ # documentation for a complete description of this file. A short
+ # synopsis follows.
+ #
+ # This file controls which replication mode each standby uses.
+ # Records are of the form:
+ #
+ # STANDBY-NAME REPLICATION-MODE
+ #
+ # (The uppercase items must be replaced by actual values.)
+ #
+ # STANDBY-NAME can be "all", standby name, or a comma-separated list
+ # thereof.
+ #
+ # REPLICATION-MODE specifies how long transaction commit waits for
+ # replication before the commit command returns a "success" to a
+ # client. The valid modes are "async", "recv", "fsync" and "replay".
+ #
+ # Standby name containing spaces, commas, quotes and other special
+ # characters must be quoted. Quoting one of the keyword "all" makes
+ # the name lose its special character, and just match standby with
+ # that name.
+ #
+ # This file is read on server startup and when the postmaster receives
+ # a SIGHUP signal. If you edit the file on a running system, you have
+ # to SIGHUP the postmaster for the changes to take effect. You can
+ # use "pg_ctl reload" to do that.
+
+ # Put your actual configuration here
+ # ----------------------------------
+
+ # STANDBY-NAME REPLICATION-MODE
*** a/src/backend/replication/walreceiver.c
--- b/src/backend/replication/walreceiver.c
***************
*** 57,62 **** bool am_walreceiver;
--- 57,63 ----
/* libpqreceiver hooks to these when loaded */
walrcv_connect_type walrcv_connect = NULL;
walrcv_receive_type walrcv_receive = NULL;
+ walrcv_send_type walrcv_send = NULL;
walrcv_disconnect_type walrcv_disconnect = NULL;
#define NAPTIME_PER_CYCLE 100 /* max sleep time between cycles (100ms) */
***************
*** 113,118 **** static void WalRcvDie(int code, Datum arg);
--- 114,120 ----
static void XLogWalRcvProcessMsg(unsigned char type, char *buf, Size len);
static void XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr);
static void XLogWalRcvFlush(void);
+ static void XLogWalRcvSendRecPtr(XLogRecPtr recptr);
/* Signal handlers */
static void WalRcvSigHupHandler(SIGNAL_ARGS);
***************
*** 158,164 **** void
--- 160,168 ----
WalReceiverMain(void)
{
char conninfo[MAXCONNINFO];
+ char standbyName[MAXSTANDBYNAME];
XLogRecPtr startpoint;
+ XLogRecPtr ackedpoint = {0, 0};
/* use volatile pointer to prevent code rearrangement */
volatile WalRcvData *walrcv = WalRcv;
***************
*** 206,211 **** WalReceiverMain(void)
--- 210,216 ----
/* Fetch information required to start streaming */
strlcpy(conninfo, (char *) walrcv->conninfo, MAXCONNINFO);
+ strlcpy(standbyName, (char *) walrcv->standbyName, MAXSTANDBYNAME);
startpoint = walrcv->receivedUpto;
SpinLockRelease(&walrcv->mutex);
***************
*** 247,253 **** WalReceiverMain(void)
/* Load the libpq-specific functions */
load_file("libpqwalreceiver", false);
if (walrcv_connect == NULL || walrcv_receive == NULL ||
! walrcv_disconnect == NULL)
elog(ERROR, "libpqwalreceiver didn't initialize correctly");
/*
--- 252,258 ----
/* Load the libpq-specific functions */
load_file("libpqwalreceiver", false);
if (walrcv_connect == NULL || walrcv_receive == NULL ||
! walrcv_send == NULL || walrcv_disconnect == NULL)
elog(ERROR, "libpqwalreceiver didn't initialize correctly");
/*
***************
*** 261,267 **** WalReceiverMain(void)
/* Establish the connection to the primary for XLOG streaming */
EnableWalRcvImmediateExit();
! walrcv_connect(conninfo, startpoint);
DisableWalRcvImmediateExit();
/* Loop until end-of-streaming or error */
--- 266,272 ----
/* Establish the connection to the primary for XLOG streaming */
EnableWalRcvImmediateExit();
! walrcv_connect(conninfo, startpoint, standbyName);
DisableWalRcvImmediateExit();
/* Loop until end-of-streaming or error */
***************
*** 311,316 **** WalReceiverMain(void)
--- 316,340 ----
*/
XLogWalRcvFlush();
}
+
+ /*
+ * If replication_mode is "replay", send the last WAL replay location
+ * to the primary, to acknowledge that replication has been completed
+ * up to that. This occurs only when WAL records were replayed since
+ * the last acknowledgement.
+ */
+ if (rplMode == REPLICATION_MODE_REPLAY &&
+ XLByteLT(ackedpoint, LogstreamResult.Flush))
+ {
+ XLogRecPtr recptr;
+
+ recptr = GetReplayRecPtr();
+ if (XLByteLT(ackedpoint, recptr))
+ {
+ XLogWalRcvSendRecPtr(recptr);
+ ackedpoint = recptr;
+ }
+ }
}
}
***************
*** 406,411 **** XLogWalRcvProcessMsg(unsigned char type, char *buf, Size len)
--- 430,448 ----
buf += sizeof(WalDataMessageHeader);
len -= sizeof(WalDataMessageHeader);
+ /*
+ * If replication_mode is "recv", send the last WAL receive
+ * location to the primary, to acknowledge that replication
+ * has been completed up to that.
+ */
+ if (rplMode == REPLICATION_MODE_RECV)
+ {
+ XLogRecPtr endptr = msghdr.dataStart;
+
+ XLByteAdvance(endptr, len);
+ XLogWalRcvSendRecPtr(endptr);
+ }
+
XLogWalRcvWrite(buf, len, msghdr.dataStart);
break;
}
***************
*** 523,528 **** XLogWalRcvFlush(void)
--- 560,573 ----
LogstreamResult.Flush = LogstreamResult.Write;
+ /*
+ * If replication_mode is "fsync", send the last WAL flush
+ * location to the primary, to acknowledge that replication
+ * has been completed up to that.
+ */
+ if (rplMode == REPLICATION_MODE_FSYNC)
+ XLogWalRcvSendRecPtr(LogstreamResult.Flush);
+
/* Update shared-memory status */
SpinLockAcquire(&walrcv->mutex);
walrcv->latestChunkStart = walrcv->receivedUpto;
***************
*** 541,543 **** XLogWalRcvFlush(void)
--- 586,609 ----
}
}
}
+
+ /* Send the lsn to the primary server */
+ static void
+ XLogWalRcvSendRecPtr(XLogRecPtr recptr)
+ {
+ static char *msgbuf = NULL;
+ WalAckMessageData msgdata;
+
+ /*
+ * Allocate buffer that will be used for each output message if first
+ * time through. We do this just once to reduce palloc overhead.
+ * The buffer must be made large enough for maximum-sized messages.
+ */
+ if (msgbuf == NULL)
+ msgbuf = palloc(1 + sizeof(WalAckMessageData));
+
+ msgbuf[0] = 'l';
+ msgdata.ackEnd = recptr;
+ memcpy(msgbuf + 1, &msgdata, sizeof(WalAckMessageData));
+ walrcv_send(msgbuf, 1 + sizeof(WalAckMessageData));
+ }
*** a/src/backend/replication/walreceiverfuncs.c
--- b/src/backend/replication/walreceiverfuncs.c
***************
*** 168,178 **** ShutdownWalRcv(void)
/*
* Request postmaster to start walreceiver.
*
! * recptr indicates the position where streaming should begin, and conninfo
! * is a libpq connection string to use.
*/
void
! RequestXLogStreaming(XLogRecPtr recptr, const char *conninfo)
{
/* use volatile pointer to prevent code rearrangement */
volatile WalRcvData *walrcv = WalRcv;
--- 168,180 ----
/*
* Request postmaster to start walreceiver.
*
! * recptr indicates the position where streaming should begin, conninfo
! * is a libpq connection string to use, and standbyName is name of this
! * standby.
*/
void
! RequestXLogStreaming(XLogRecPtr recptr, const char *conninfo,
! const char *standbyName)
{
/* use volatile pointer to prevent code rearrangement */
volatile WalRcvData *walrcv = WalRcv;
***************
*** 196,201 **** RequestXLogStreaming(XLogRecPtr recptr, const char *conninfo)
--- 198,207 ----
strlcpy((char *) walrcv->conninfo, conninfo, MAXCONNINFO);
else
walrcv->conninfo[0] = '\0';
+ if (standbyName != NULL)
+ strlcpy((char *) walrcv->standbyName, standbyName, MAXSTANDBYNAME);
+ else
+ walrcv->standbyName[0] = '\0';
walrcv->walRcvState = WALRCV_STARTING;
walrcv->startTime = now;
*** a/src/backend/replication/walsender.c
--- b/src/backend/replication/walsender.c
***************
*** 39,44 ****
--- 39,45 ----
#include "access/xlog_internal.h"
#include "catalog/pg_type.h"
+ #include "libpq/hba.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
#include "libpq/pqsignal.h"
***************
*** 48,53 ****
--- 49,55 ----
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/pmsignal.h"
+ #include "storage/proc.h"
#include "tcop/tcopprot.h"
#include "utils/guc.h"
#include "utils/memutils.h"
***************
*** 60,67 **** WalSndCtlData *WalSndCtl = NULL;
--- 62,73 ----
/* My slot in the shared memory array */
static WalSnd *MyWalSnd = NULL;
+ /* Array of WalSndWaiter in shared memory */
+ static WalSndWaiter *WalSndWaiters;
+
/* Global state */
bool am_walsender = false; /* Am I a walsender process ? */
+ char *standby_name = NULL; /* Name of connected standby */
/* User-settable parameters for walsender */
int max_wal_senders = 0; /* the maximum number of concurrent walsenders */
***************
*** 82,92 **** static uint32 sendOff = 0;
--- 88,125 ----
*/
static XLogRecPtr sentPtr = {0, 0};
+ /*
+ * How far have we completed replication already? This is also
+ * advertised in MyWalSnd->ackdPtr. This is not used in asynchronous
+ * replication case.
+ */
+ static XLogRecPtr ackdPtr = {0, 0};
+
/* Flags set by signal handlers for later service in main loop */
static volatile sig_atomic_t got_SIGHUP = false;
static volatile sig_atomic_t shutdown_requested = false;
static volatile sig_atomic_t ready_to_stop = false;
+ /* Flag set by signal handler of backends for replication */
+ static volatile sig_atomic_t replication_done = false;
+
+ /*
+ * pre-parsed content of standbys configuration file: list of
+ * StandbysLine structs
+ */
+ static List *parsed_standbys_lines = NIL;
+
+ static const char *standbys_keywords[] = {"all", NULL};
+
+ /*
+ * Path of standbys configuration file (relative to $PGDATA).
+ *
+ * XXX: We should support the GUC parameter specifying the path of
+ * standbys configuration file?
+ */
+ #define STANDBYS_FILENAME "standbys.conf"
+ static char *StandbysFileName = NULL;
+
/* Signal handlers */
static void WalSndSigHupHandler(SIGNAL_ARGS);
static void WalSndShutdownHandler(SIGNAL_ARGS);
***************
*** 101,107 **** static void WalSndHandshake(void);
static void WalSndKill(int code, Datum arg);
static void XLogRead(char *buf, XLogRecPtr recptr, Size nbytes);
static bool XLogSend(char *msgbuf, bool *caughtup);
! static void CheckClosedConnection(void);
/* Main entry point for walsender process */
--- 134,149 ----
static void WalSndKill(int code, Datum arg);
static void XLogRead(char *buf, XLogRecPtr recptr, Size nbytes);
static bool XLogSend(char *msgbuf, bool *caughtup);
! static void ProcessStreamMsgs(StringInfo inMsg);
!
! static void RegisterWalSndWaiter(BackendId backendId, XLogRecPtr record,
! Latch *latch);
! static void WakeupWalSndWaiters(XLogRecPtr record);
! static XLogRecPtr GetOldestAckdPtr(void);
!
! static bool parse_standbys_line(List *line, int line_num, StandbysLine *parsedline);
! static void free_standbys_record(StandbysLine *record);
! static void clean_standbys_list(List *lines);
/* Main entry point for walsender process */
***************
*** 218,236 **** WalSndHandshake(void)
StringInfoData buf;
char sysid[32];
char tli[11];
/*
! * Reply with a result set with one row, two columns.
! * First col is system ID, and second is timeline ID
*/
snprintf(sysid, sizeof(sysid), UINT64_FORMAT,
GetSystemIdentifier());
snprintf(tli, sizeof(tli), "%u", ThisTimeLineID);
/* Send a RowDescription message */
pq_beginmessage(&buf, 'T');
! pq_sendint(&buf, 2, 2); /* 2 fields */
/* first field */
pq_sendstring(&buf, "systemid"); /* col name */
--- 260,281 ----
StringInfoData buf;
char sysid[32];
char tli[11];
+ char mode[8];
/*
! * Reply with a result set with one row, three columns.
! * First col is system ID, second is timeline ID, and
! * third is replication mode.
*/
snprintf(sysid, sizeof(sysid), UINT64_FORMAT,
GetSystemIdentifier());
snprintf(tli, sizeof(tli), "%u", ThisTimeLineID);
+ snprintf(mode, sizeof(mode), "%s", ReplicationModeNames[rplMode]);
/* Send a RowDescription message */
pq_beginmessage(&buf, 'T');
! pq_sendint(&buf, 3, 2); /* 3 fields */
/* first field */
pq_sendstring(&buf, "systemid"); /* col name */
***************
*** 249,263 **** WalSndHandshake(void)
pq_sendint(&buf, 4, 2); /* typlen */
pq_sendint(&buf, 0, 4); /* typmod */
pq_sendint(&buf, 0, 2); /* format code */
pq_endmessage(&buf);
/* Send a DataRow message */
pq_beginmessage(&buf, 'D');
! pq_sendint(&buf, 2, 2); /* # of columns */
pq_sendint(&buf, strlen(sysid), 4); /* col1 len */
pq_sendbytes(&buf, (char *) &sysid, strlen(sysid));
pq_sendint(&buf, strlen(tli), 4); /* col2 len */
pq_sendbytes(&buf, (char *) tli, strlen(tli));
pq_endmessage(&buf);
/* Send CommandComplete and ReadyForQuery messages */
--- 294,319 ----
pq_sendint(&buf, 4, 2); /* typlen */
pq_sendint(&buf, 0, 4); /* typmod */
pq_sendint(&buf, 0, 2); /* format code */
+
+ /* third field */
+ pq_sendstring(&buf, "replication_mode"); /* col name */
+ pq_sendint(&buf, 0, 4); /* table oid */
+ pq_sendint(&buf, 0, 2); /* attnum */
+ pq_sendint(&buf, TEXTOID, 4); /* type oid */
+ pq_sendint(&buf, -1, 2); /* typlen */
+ pq_sendint(&buf, 0, 4); /* typmod */
+ pq_sendint(&buf, 0, 2); /* format code */
pq_endmessage(&buf);
/* Send a DataRow message */
pq_beginmessage(&buf, 'D');
! pq_sendint(&buf, 3, 2); /* # of columns */
pq_sendint(&buf, strlen(sysid), 4); /* col1 len */
pq_sendbytes(&buf, (char *) &sysid, strlen(sysid));
pq_sendint(&buf, strlen(tli), 4); /* col2 len */
pq_sendbytes(&buf, (char *) tli, strlen(tli));
+ pq_sendint(&buf, strlen(mode), 4); /* col3 len */
+ pq_sendbytes(&buf, (char *) &mode, strlen(mode));
pq_endmessage(&buf);
/* Send CommandComplete and ReadyForQuery messages */
***************
*** 295,304 **** WalSndHandshake(void)
pq_flush();
/*
! * Initialize position to the received one, then the
* xlog records begin to be shipped from that position
*/
! sentPtr = recptr;
/* break out of the loop */
replication_started = true;
--- 351,360 ----
pq_flush();
/*
! * Initialize positions to the received one, then the
* xlog records begin to be shipped from that position
*/
! sentPtr = ackdPtr = recptr;
/* break out of the loop */
replication_started = true;
***************
*** 332,384 **** WalSndHandshake(void)
}
/*
! * Check if the remote end has closed the connection.
*/
static void
! CheckClosedConnection(void)
{
! unsigned char firstchar;
! int r;
! r = pq_getbyte_if_available(&firstchar);
! if (r < 0)
! {
! /* unexpected error or EOF */
! ereport(COMMERROR,
! (errcode(ERRCODE_PROTOCOL_VIOLATION),
! errmsg("unexpected EOF on standby connection")));
! proc_exit(0);
! }
! if (r == 0)
{
! /* no data available without blocking */
! return;
! }
- /* Handle the very limited subset of commands expected in this phase */
- switch (firstchar)
- {
/*
* 'X' means that the standby is closing down the socket.
*/
! case 'X':
! proc_exit(0);
! default:
! ereport(FATAL,
! (errcode(ERRCODE_PROTOCOL_VIOLATION),
! errmsg("invalid standby closing message type %d",
! firstchar)));
}
}
/* Main loop of walsender process */
static int
WalSndLoop(void)
{
char *output_message;
bool caughtup = false;
/*
* Allocate buffer that will be used for each output message. We do this
* just once to reduce palloc overhead. The buffer must be made large
--- 388,512 ----
}
/*
! * Process messages received from the standby.
! *
! * ereports on error.
*/
static void
! ProcessStreamMsgs(StringInfo inMsg)
{
! bool acked = false;
! /* Loop to process successive complete messages available */
! for (;;)
{
! unsigned char firstchar;
! int r;
!
! r = pq_getbyte_if_available(&firstchar);
! if (r < 0)
! {
! /* unexpected error or EOF */
! ereport(COMMERROR,
! (errcode(ERRCODE_PROTOCOL_VIOLATION),
! errmsg("unexpected EOF on standby connection")));
! proc_exit(0);
! }
! if (r == 0)
! {
! /* no data available without blocking */
! break;
! }
!
! /* Handle the very limited subset of commands expected in this phase */
! switch (firstchar)
! {
! case 'd': /* CopyData message */
! {
! unsigned char rpltype;
!
! /*
! * Read the message contents. This is expected to be done without
! * blocking because we've been able to get message type code.
! */
! if (pq_getmessage(inMsg, 0))
! proc_exit(0); /* suitable message already logged */
!
! /* Read the replication message type from CopyData message */
! rpltype = pq_getmsgbyte(inMsg);
! switch (rpltype)
! {
! case 'l':
! {
! WalAckMessageData *msgdata;
!
! msgdata = (WalAckMessageData *) pq_getmsgbytes(inMsg, sizeof(WalAckMessageData));
!
! /*
! * Update local status.
! *
! * The ackd ptr received from standby should not
! * go backwards.
! */
! if (XLByteLE(ackdPtr, msgdata->ackEnd))
! ackdPtr = msgdata->ackEnd;
! else
! ereport(FATAL,
! (errmsg("replication completion location went back from "
! "%X/%X to %X/%X",
! ackdPtr.xlogid, ackdPtr.xrecoff,
! msgdata->ackEnd.xlogid, msgdata->ackEnd.xrecoff)));
!
! acked = true; /* also need to update shared position */
! break;
! }
! default:
! ereport(FATAL,
! (errcode(ERRCODE_PROTOCOL_VIOLATION),
! errmsg("invalid replication message type %d",
! rpltype)));
! }
! break;
! }
/*
* 'X' means that the standby is closing down the socket.
*/
! case 'X':
! proc_exit(0);
! default:
! ereport(FATAL,
! (errcode(ERRCODE_PROTOCOL_VIOLATION),
! errmsg("invalid standby closing message type %d",
! firstchar)));
! }
}
+
+ if (acked)
+ {
+ /* use volatile pointer to prevent code rearrangement */
+ volatile WalSnd *walsnd = MyWalSnd;
+
+ SpinLockAcquire(&walsnd->mutex);
+ walsnd->ackdPtr = ackdPtr;
+ SpinLockRelease(&walsnd->mutex);
+ }
+
+ /* Wake up the backends that this walsender had been blocking */
+ WakeupWalSndWaiters(GetOldestAckdPtr());
}
/* Main loop of walsender process */
static int
WalSndLoop(void)
{
+ StringInfoData input_message;
char *output_message;
bool caughtup = false;
+ initStringInfo(&input_message);
+
/*
* Allocate buffer that will be used for each output message. We do this
* just once to reduce palloc overhead. The buffer must be made large
***************
*** 455,462 **** WalSndLoop(void)
WalSndDelay * 1000L);
}
! /* Check if the connection was closed */
! CheckClosedConnection();
}
else
{
--- 583,590 ----
WalSndDelay * 1000L);
}
! /* Process messages received from the standby */
! ProcessStreamMsgs(&input_message);
}
else
{
***************
*** 515,520 **** InitWalSnd(void)
--- 643,650 ----
*/
walsnd->pid = MyProcPid;
MemSet(&walsnd->sentPtr, 0, sizeof(XLogRecPtr));
+ MemSet(&walsnd->ackdPtr, 0, sizeof(XLogRecPtr));
+ walsnd->rplMode = rplMode;
SpinLockRelease(&walsnd->mutex);
/* don't need the lock anymore */
OwnLatch((Latch *) &walsnd->latch);
***************
*** 540,545 **** WalSndKill(int code, Datum arg)
--- 670,679 ----
{
Assert(MyWalSnd != NULL);
+ /* Wake up the backends that this walsender had been blocking */
+ MyWalSnd->rplMode = InvalidReplicationMode;
+ WakeupWalSndWaiters(GetOldestAckdPtr());
+
/*
* Mark WalSnd struct no longer in use. Assume that no lock is required
* for this.
***************
*** 904,909 **** WalSndShmemSize(void)
--- 1038,1050 ----
size = offsetof(WalSndCtlData, walsnds);
size = add_size(size, mul_size(max_wal_senders, sizeof(WalSnd)));
+ /*
+ * If replication is enabled, we have a data structure called
+ * WalSndWaiters, created in shared memory.
+ */
+ if (max_wal_senders > 0)
+ size = add_size(size, mul_size(MaxBackends, sizeof(WalSndWaiter)));
+
return size;
}
***************
*** 913,926 **** WalSndShmemInit(void)
{
bool found;
int i;
WalSndCtl = (WalSndCtlData *)
! ShmemInitStruct("Wal Sender Ctl", WalSndShmemSize(), &found);
if (!found)
{
/* First time through, so initialize */
! MemSet(WalSndCtl, 0, WalSndShmemSize());
for (i = 0; i < max_wal_senders; i++)
{
--- 1054,1069 ----
{
bool found;
int i;
+ Size size = add_size(offsetof(WalSndCtlData, walsnds),
+ mul_size(max_wal_senders, sizeof(WalSnd)));
WalSndCtl = (WalSndCtlData *)
! ShmemInitStruct("Wal Sender Ctl", size, &found);
if (!found)
{
/* First time through, so initialize */
! MemSet(WalSndCtl, 0, size);
for (i = 0; i < max_wal_senders; i++)
{
***************
*** 930,935 **** WalSndShmemInit(void)
--- 1073,1088 ----
InitSharedLatch(&walsnd->latch);
}
}
+
+ /* Create or attach to the WalSndWaiters array too, if needed */
+ if (max_wal_senders > 0)
+ {
+ WalSndWaiters = (WalSndWaiter *)
+ ShmemInitStruct("WalSndWaiters",
+ mul_size(MaxBackends, sizeof(WalSndWaiter)),
+ &found);
+ WalSndCtl->maxWaiters = MaxBackends;
+ }
}
/* Wake up all walsenders */
***************
*** 943,977 **** WalSndWakeup(void)
}
/*
! * This isn't currently used for anything. Monitoring tools might be
! * interested in the future, and we'll need something like this in the
! * future for synchronous replication.
*/
! #ifdef NOT_USED
/*
! * Returns the oldest Send position among walsenders. Or InvalidXLogRecPtr
! * if none.
*/
! XLogRecPtr
! GetOldestWALSendPointer(void)
{
XLogRecPtr oldest = {0, 0};
! int i;
! bool found = false;
for (i = 0; i < max_wal_senders; i++)
{
/* use volatile pointer to prevent code rearrangement */
volatile WalSnd *walsnd = &WalSndCtl->walsnds[i];
! XLogRecPtr recptr;
! if (walsnd->pid == 0)
continue;
SpinLockAcquire(&walsnd->mutex);
! recptr = walsnd->sentPtr;
SpinLockRelease(&walsnd->mutex);
if (recptr.xlogid == 0 && recptr.xrecoff == 0)
continue;
--- 1096,1259 ----
}
/*
! * Ensure that replication has been completed up to the given position.
*/
! void
! WaitXLogSend(XLogRecPtr record)
! {
! int i;
!
! Assert(max_wal_senders > 0);
!
! /*
! * Register myself into the wait list and sleep until replication has
! * been completed up to the given position and the walsender signals me.
! *
! * If replication has been completed up to the latest position before
! * the registration, walsender might be unable to send the signal
! * immediately. We must wake up the walsender after the registration.
! */
! ResetLatch(&MyProc->latch);
! RegisterWalSndWaiter(MyBackendId, record, &MyProc->latch);
! WalSndWakeup();
!
! for (;;)
! {
! WaitLatch(&MyProc->latch, 1000000L);
! if (replication_done)
! {
! replication_done = false;
! return;
! }
! }
! }
!
! /*
! * Register the given backend into the wait list.
! */
! static void
! RegisterWalSndWaiter(BackendId backendId, XLogRecPtr record, Latch *latch)
! {
! /* use volatile pointer to prevent code rearrangement */
! volatile WalSndCtlData *walsndctl = WalSndCtl;
! int i;
! int count = 0;
!
! LWLockAcquire(WalSndWaiterLock, LW_EXCLUSIVE);
!
! /* Out of slots. This should not happen. */
! if (walsndctl->numWaiters + 1 > walsndctl->maxWaiters)
! elog(PANIC, "out of replication waiters slots");
!
! /*
! * The given position is expected to be relatively new in the
! * wait list. Since the entries in the list are sorted in an
! * increasing order of XLogRecPtr, we can shorten the time it
! * takes to find an insert slot by scanning the list backwards.
! */
! for (i = walsndctl->numWaiters; i > 0; i--)
! {
! if (XLByteLE(WalSndWaiters[i - 1].record, record))
! break;
! count++;
! }
!
! /* Shuffle the list if needed */
! if (count > 0)
! memmove(&WalSndWaiters[i + 1], &WalSndWaiters[i],
! count * sizeof(WalSndWaiter));
!
! WalSndWaiters[i].backendId = backendId;
! WalSndWaiters[i].record = record;
! WalSndWaiters[i].latch = latch;
! walsndctl->numWaiters++;
!
! LWLockRelease(WalSndWaiterLock);
! }
!
/*
! * Wake up the backends waiting until replication has been completed
! * up to the position older than or equal to the given one.
! *
! * Wake up all waiters if InvalidXLogRecPtr is given.
*/
! static void
! WakeupWalSndWaiters(XLogRecPtr record)
! {
! /* use volatile pointer to prevent code rearrangement */
! volatile WalSndCtlData *walsndctl = WalSndCtl;
! int i;
! int count = 0;
! bool all_wakeup = (record.xlogid == 0 && record.xrecoff == 0);
!
! LWLockAcquire(WalSndWaiterLock, LW_EXCLUSIVE);
!
! for (i = 0; i < walsndctl->numWaiters; i++)
! {
! /* use volatile pointer to prevent code rearrangement */
! volatile WalSndWaiter *waiter = &WalSndWaiters[i];
!
! if (all_wakeup || XLByteLE(waiter->record, record))
! {
! SetProcLatch(waiter->latch, PROCSIG_REPLICATION_INTERRUPT,
! waiter->backendId);
! count++;
! }
! else
! {
! /*
! * If the backend waiting for the Ack position newer than
! * the given one is found, we don't need to search the wait
! * list any more. This is because the waiters in the list
! * are guaranteed to be sorted in an increasing order of
! * XLogRecPtr.
! */
! break;
! }
! }
!
! /* If there are still some waiters, left-justify them in the list */
! walsndctl->numWaiters -= count;
! if (walsndctl->numWaiters > 0 && count > 0)
! memmove(&WalSndWaiters, &WalSndWaiters[i],
! walsndctl->numWaiters * sizeof(WalSndWaiter));
!
! LWLockRelease(WalSndWaiterLock);
! }
!
! /*
! * Returns the oldest Ack position in synchronous walsenders. Or
! * InvalidXLogRecPtr if none.
! */
! static XLogRecPtr
! GetOldestAckdPtr(void)
{
XLogRecPtr oldest = {0, 0};
! int i;
! bool found = false;
for (i = 0; i < max_wal_senders; i++)
{
/* use volatile pointer to prevent code rearrangement */
volatile WalSnd *walsnd = &WalSndCtl->walsnds[i];
! XLogRecPtr recptr;
! /*
! * Ignore the Ack position that asynchronous walsender has
! * since it has never received any Ack.
! */
! if (walsnd->pid == 0 ||
! walsnd->rplMode <= REPLICATION_MODE_ASYNC)
continue;
SpinLockAcquire(&walsnd->mutex);
! recptr = walsnd->ackdPtr;
SpinLockRelease(&walsnd->mutex);
+ /*
+ * Ignore the Ack position that the walsender which has not
+ * received any Ack yet has.
+ */
if (recptr.xlogid == 0 && recptr.xrecoff == 0)
continue;
***************
*** 982,985 **** GetOldestWALSendPointer(void)
return oldest;
}
! #endif
--- 1264,1476 ----
return oldest;
}
! /*
! * This is called when PROCSIG_REPLICATION_INTERRUPT is received.
! */
! void
! HandleReplicationInterrupt(void)
! {
! replication_done = true;
! }
!
!
! /* ----------
! * Routines to handle standbys configuration file
! * ----------
! */
!
! /*
! * Scan the (pre-parsed) standbys configuration file line by line,
! * looking for a match to the standby name passed from the standby.
! */
! bool
! check_standbys(void)
! {
! ListCell *line;
! StandbysLine *standbys;
!
! foreach(line, parsed_standbys_lines)
! {
! char *tok;
!
! standbys = (StandbysLine *) lfirst(line);
!
! /* Check standby name */
! for (tok = strtok(standbys->standbyName, MULTI_VALUE_SEP);
! tok != NULL;
! tok = strtok(NULL, MULTI_VALUE_SEP))
! {
! if (strcmp(tok, "all\n") == 0 ||
! (standby_name != NULL &&
! strcmp(tok, standby_name) == 0))
! {
! rplMode = standbys->rplMode;
! return true;
! }
! }
! }
! return false;
! }
!
! /*
! * Parse one line in the standbys configuration file and store
! * the result in a StandbysLine structure.
! */
! static bool
! parse_standbys_line(List *line, int line_num, StandbysLine *parsedline)
! {
! char *token;
! ListCell *line_item;
!
! line_item = list_head(line);
!
! parsedline->linenumber = line_num;
!
! /* Get the standby name. */
! parsedline->standbyName = pstrdup(lfirst(line_item));
!
! /* Get the mode. */
! line_item = lnext(line_item);
! if (!line_item)
! {
! ereport(LOG,
! (errcode(ERRCODE_CONFIG_FILE_ERROR),
! errmsg("end-of-line before mode specification"),
! errcontext("line %d of configuration file \"%s\"",
! line_num, StandbysFileName)));
! return false;
! }
! token = lfirst(line_item);
!
! parsedline->rplMode = ReplicationModeNameGetValue(token);
! if (parsedline->rplMode == InvalidReplicationMode)
! {
! ereport(LOG,
! (errcode(ERRCODE_CONFIG_FILE_ERROR),
! errmsg("invalid replication mode \"%s\"",
! token),
! errcontext("line %d of configuration file \"%s\"",
! line_num, StandbysFileName)));
! return false;
! }
!
! /* Ignore remaining tokens */
!
! return true;
! }
!
! /*
! * Free an StandbysLine structure
! */
! static void
! free_standbys_record(StandbysLine *record)
! {
! if (record->standbyName)
! pfree(record->standbyName);
! pfree(record);
! }
!
! /*
! * Free all records on the parsed Standbys list
! */
! static void
! clean_standbys_list(List *lines)
! {
! ListCell *line;
!
! foreach(line, lines)
! {
! StandbysLine *parsed = (StandbysLine *) lfirst(line);
!
! if (parsed)
! free_standbys_record(parsed);
! }
! list_free(lines);
! }
!
! /*
! * Read the config file and create a List of StandbysLine records for the contents.
! *
! * The configuration is read into a temporary list, and if any parse error occurs
! * the old list is kept in place and false is returned. Only if the whole file
! * parses Ok is the list replaced, and the function returns true.
! */
! bool
! load_standbys(void)
! {
! FILE *file;
! List *standbys_lines = NIL;
! List *standbys_line_nums = NIL;
! ListCell *line,
! *line_num;
! List *new_parsed_lines = NIL;
! bool ok = true;
!
! /* Ignore standbys.conf if replication is not enabled */
! if (max_wal_senders <= 0)
! return true;
!
! /* If first time through, convert relative path to absolute */
! if (StandbysFileName == NULL)
! StandbysFileName = make_absolute_path(STANDBYS_FILENAME);
!
! file = AllocateFile(StandbysFileName, "r");
! if (file == NULL)
! {
! ereport(LOG,
! (errcode_for_file_access(),
! errmsg("could not open configuration file \"%s\": %m",
! StandbysFileName)));
!
! /*
! * Caller will take care of making this a FATAL error in case this is
! * the initial startup. If it happens on reload, we just keep the old
! * version around.
! */
! return false;
! }
!
! tokenize_file(StandbysFileName, file, &standbys_lines, &standbys_line_nums,
! standbys_keywords);
! FreeFile(file);
!
! /* Now parse all the lines */
! forboth(line, standbys_lines, line_num, standbys_line_nums)
! {
! StandbysLine *newline;
!
! newline = palloc0(sizeof(StandbysLine));
!
! if (!parse_standbys_line(lfirst(line), lfirst_int(line_num), newline))
! {
! /* Parse error in the file, so indicate there's a problem */
! free_standbys_record(newline);
! ok = false;
!
! /*
! * Keep parsing the rest of the file so we can report errors on
! * more than the first row. Error has already been reported in the
! * parsing function, so no need to log it here.
! */
! continue;
! }
!
! new_parsed_lines = lappend(new_parsed_lines, newline);
! }
!
! /* Free the temporary lists */
! free_lines(&standbys_lines, &standbys_line_nums);
!
! if (!ok)
! {
! /* Parsing failed at one or more rows, so bail out */
! clean_standbys_list(new_parsed_lines);
! return false;
! }
!
! /* Loaded new file successfully, replace the one we use */
! clean_standbys_list(parsed_standbys_lines);
! parsed_standbys_lines = new_parsed_lines;
!
! return true;
! }
*** a/src/backend/storage/ipc/procsignal.c
--- b/src/backend/storage/ipc/procsignal.c
***************
*** 20,25 ****
--- 20,26 ----
#include "bootstrap/bootstrap.h"
#include "commands/async.h"
#include "miscadmin.h"
+ #include "replication/walsender.h"
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/procsignal.h"
***************
*** 172,177 **** CleanupProcSignalState(int status, Datum arg)
--- 173,192 ----
int
SendProcSignal(pid_t pid, ProcSignalReason reason, BackendId backendId)
{
+ if (SetProcSignalReason(pid, reason, backendId))
+ return kill(pid, SIGUSR1); /* Send signal */
+
+ errno = ESRCH;
+ return -1;
+ }
+
+ /*
+ * SetProcSignalReason
+ * Set the reason flag
+ */
+ bool
+ SetProcSignalReason(pid_t pid, ProcSignalReason reason, BackendId backendId)
+ {
volatile ProcSignalSlot *slot;
if (backendId != InvalidBackendId)
***************
*** 190,197 **** SendProcSignal(pid_t pid, ProcSignalReason reason, BackendId backendId)
{
/* Atomically set the proper flag */
slot->pss_signalFlags[reason] = true;
! /* Send signal */
! return kill(pid, SIGUSR1);
}
}
else
--- 205,211 ----
{
/* Atomically set the proper flag */
slot->pss_signalFlags[reason] = true;
! return true;
}
}
else
***************
*** 214,227 **** SendProcSignal(pid_t pid, ProcSignalReason reason, BackendId backendId)
/* Atomically set the proper flag */
slot->pss_signalFlags[reason] = true;
! /* Send signal */
! return kill(pid, SIGUSR1);
}
}
}
!
! errno = ESRCH;
! return -1;
}
/*
--- 228,238 ----
/* Atomically set the proper flag */
slot->pss_signalFlags[reason] = true;
! return true;
}
}
}
! return false;
}
/*
***************
*** 279,284 **** procsignal_sigusr1_handler(SIGNAL_ARGS)
--- 290,298 ----
if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN))
RecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
+ if (CheckProcSignal(PROCSIG_REPLICATION_INTERRUPT))
+ HandleReplicationInterrupt();
+
latch_sigusr1_handler();
errno = save_errno;
*** a/src/backend/storage/lmgr/proc.c
--- b/src/backend/storage/lmgr/proc.c
***************
*** 196,201 **** InitProcGlobal(void)
--- 196,202 ----
PGSemaphoreCreate(&(procs[i].sem));
procs[i].links.next = (SHM_QUEUE *) ProcGlobal->freeProcs;
ProcGlobal->freeProcs = &procs[i];
+ InitSharedLatch(&procs[i].latch);
}
/*
***************
*** 214,219 **** InitProcGlobal(void)
--- 215,221 ----
PGSemaphoreCreate(&(procs[i].sem));
procs[i].links.next = (SHM_QUEUE *) ProcGlobal->autovacFreeProcs;
ProcGlobal->autovacFreeProcs = &procs[i];
+ InitSharedLatch(&procs[i].latch);
}
/*
***************
*** 325,330 **** InitProcess(void)
--- 327,333 ----
for (i = 0; i < NUM_LOCK_PARTITIONS; i++)
SHMQueueInit(&(MyProc->myProcLocks[i]));
MyProc->recoveryConflictPending = false;
+ OwnLatch(&MyProc->latch);
/*
* We might be reusing a semaphore that belonged to a failed process. So
***************
*** 688,693 **** ProcKill(int code, Datum arg)
--- 691,697 ----
}
/* PGPROC struct isn't mine anymore */
+ DisownLatch(&MyProc->latch);
MyProc = NULL;
/* Update shared estimate of spins_per_delay */
*** a/src/backend/utils/init/postinit.c
--- b/src/backend/utils/init/postinit.c
***************
*** 664,669 **** InitPostgres(const char *in_dbname, Oid dboid, const char *username,
--- 664,690 ----
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to start walsender")));
+ /*
+ * In EXEC_BACKEND case, we didn't inherit the contents of standbys.conf
+ * etcetera from the postmaster, and have to load them ourselves. Note we
+ * are loading them into the startup transaction's memory context, not
+ * PostmasterContext, but that shouldn't matter.
+ *
+ * FIXME: [fork/exec] Ugh. Is there a way around this overhead?
+ */
+ #ifdef EXEC_BACKEND
+ if (!load_standbys())
+ {
+ ereport(FATAL,
+ (errmsg("could not load standbys.conf")));
+ }
+ #endif
+
+ if (!check_standbys())
+ ereport(FATAL,
+ (errmsg("no standbys.conf entry for standby name \"%s\"",
+ standby_name)));
+
/* process any options passed in the startup packet */
if (MyProcPort != NULL)
process_startup_options(MyProcPort, am_superuser);
*** a/src/include/access/xlog.h
--- b/src/include/access/xlog.h
***************
*** 189,194 **** typedef enum
--- 189,229 ----
extern XLogRecPtr XactLastRecEnd;
+ /*
+ * Replication mode. This is used to identify how long transaction
+ * commit should wait for replication.
+ *
+ * REPLICATION_MODE_ASYNC doesn't make transaction commit wait for
+ * replication, i.e., asynchronous replication.
+ *
+ * REPLICATION_MODE_RECV makes transaction commit wait for XLOG
+ * records to be received on the standby.
+ *
+ * REPLICATION_MODE_FSYNC makes transaction commit wait for XLOG
+ * records to be received and fsync'd on the standby.
+ *
+ * REPLICATION_MODE_REPLAY makes transaction commit wait for XLOG
+ * records to be received, fsync'd and replayed on the standby.
+ */
+ typedef enum ReplicationMode
+ {
+ InvalidReplicationMode = -1,
+ REPLICATION_MODE_ASYNC = 0,
+ REPLICATION_MODE_RECV,
+ REPLICATION_MODE_FSYNC,
+ REPLICATION_MODE_REPLAY
+
+ /*
+ * NOTE: if you add a new mode, change MAXREPLICATIONMODE below
+ * and update the ReplicationModeNames array in xlog.c
+ */
+ } ReplicationMode;
+
+ #define MAXREPLICATIONMODE REPLICATION_MODE_REPLAY
+
+ extern const char *ReplicationModeNames[];
+ extern ReplicationMode rplMode;
+
/* these variables are GUC parameters related to XLOG */
extern int CheckPointSegments;
extern int wal_keep_segments;
***************
*** 298,307 **** extern void XLogPutNextOid(Oid nextOid);
--- 333,345 ----
extern XLogRecPtr GetRedoRecPtr(void);
extern XLogRecPtr GetInsertRecPtr(void);
extern XLogRecPtr GetFlushRecPtr(void);
+ extern XLogRecPtr GetReplayRecPtr(void);
extern void GetNextXidAndEpoch(TransactionId *xid, uint32 *epoch);
extern TimeLineID GetRecoveryTargetTLI(void);
extern void HandleStartupProcInterrupts(void);
extern void StartupProcessMain(void);
+ extern ReplicationMode ReplicationModeNameGetValue(char *name);
+
#endif /* XLOG_H */
*** a/src/include/libpq/hba.h
--- b/src/include/libpq/hba.h
***************
*** 15,20 ****
--- 15,24 ----
#include "libpq/pqcomm.h"
+ /* This is used to separate values in multi-valued column strings */
+ #define MULTI_VALUE_SEP "\001"
+
+
typedef enum UserAuth
{
uaReject,
***************
*** 89,93 **** extern int check_usermap(const char *usermap_name,
--- 93,100 ----
const char *pg_role, const char *auth_user,
bool case_sensitive);
extern bool pg_isblank(const char c);
+ extern void tokenize_file(const char *filename, FILE *file,
+ List **lines, List **line_nums, const char **keywords);
+ extern void free_lines(List **lines, List **line_nums);
#endif /* HBA_H */
*** a/src/include/replication/walprotocol.h
--- b/src/include/replication/walprotocol.h
***************
*** 50,53 **** typedef struct
--- 50,63 ----
*/
#define MAX_SEND_SIZE (XLOG_BLCKSZ * 16)
+ /*
+ * Body for a WAL acknowledgment message (message type 'l'). This is wrapped
+ * within a CopyData message at the FE/BE protocol level.
+ */
+ typedef struct
+ {
+ /* End of WAL replicated to the standby */
+ XLogRecPtr ackEnd;
+ } WalAckMessageData;
+
#endif /* _WALPROTOCOL_H */
*** a/src/include/replication/walreceiver.h
--- b/src/include/replication/walreceiver.h
***************
*** 26,31 **** extern bool am_walreceiver;
--- 26,38 ----
#define MAXCONNINFO 1024
/*
+ * MAXSTANDBYNAME: maximum size of standby name.
+ *
+ * XXX: Should this move to pg_config_manual.h?
+ */
+ #define MAXSTANDBYNAME 64
+
+ /*
* Values for WalRcv->walRcvState.
*/
typedef enum
***************
*** 71,89 **** typedef struct
*/
char conninfo[MAXCONNINFO];
slock_t mutex; /* locks shared variables shown above */
} WalRcvData;
extern WalRcvData *WalRcv;
/* libpqwalreceiver hooks */
! typedef bool (*walrcv_connect_type) (char *conninfo, XLogRecPtr startpoint);
extern PGDLLIMPORT walrcv_connect_type walrcv_connect;
typedef bool (*walrcv_receive_type) (int timeout, unsigned char *type,
char **buffer, int *len);
extern PGDLLIMPORT walrcv_receive_type walrcv_receive;
typedef void (*walrcv_disconnect_type) (void);
extern PGDLLIMPORT walrcv_disconnect_type walrcv_disconnect;
--- 78,106 ----
*/
char conninfo[MAXCONNINFO];
+ /*
+ * standby name; is used for the master to determine replication mode
+ * from standbys configuration file.
+ */
+ char standbyName[MAXSTANDBYNAME];
+
slock_t mutex; /* locks shared variables shown above */
} WalRcvData;
extern WalRcvData *WalRcv;
/* libpqwalreceiver hooks */
! typedef bool (*walrcv_connect_type) (char *conninfo, XLogRecPtr startpoint,
! char *standbyName);
extern PGDLLIMPORT walrcv_connect_type walrcv_connect;
typedef bool (*walrcv_receive_type) (int timeout, unsigned char *type,
char **buffer, int *len);
extern PGDLLIMPORT walrcv_receive_type walrcv_receive;
+ typedef void (*walrcv_send_type) (const char *buffer, int nbytes);
+ extern PGDLLIMPORT walrcv_send_type walrcv_send;
+
typedef void (*walrcv_disconnect_type) (void);
extern PGDLLIMPORT walrcv_disconnect_type walrcv_disconnect;
***************
*** 95,101 **** extern Size WalRcvShmemSize(void);
extern void WalRcvShmemInit(void);
extern void ShutdownWalRcv(void);
extern bool WalRcvInProgress(void);
! extern void RequestXLogStreaming(XLogRecPtr recptr, const char *conninfo);
extern XLogRecPtr GetWalRcvWriteRecPtr(XLogRecPtr *latestChunkStart);
#endif /* _WALRECEIVER_H */
--- 112,119 ----
extern void WalRcvShmemInit(void);
extern void ShutdownWalRcv(void);
extern bool WalRcvInProgress(void);
! extern void RequestXLogStreaming(XLogRecPtr recptr, const char *conninfo,
! const char *standbyName);
extern XLogRecPtr GetWalRcvWriteRecPtr(XLogRecPtr *latestChunkStart);
#endif /* _WALRECEIVER_H */
*** a/src/include/replication/walsender.h
--- b/src/include/replication/walsender.h
***************
*** 23,28 **** typedef struct WalSnd
--- 23,31 ----
{
pid_t pid; /* this walsender's process id, or 0 */
XLogRecPtr sentPtr; /* WAL has been sent up to this point */
+ XLogRecPtr ackdPtr; /* WAL has been replicated up to this point */
+
+ ReplicationMode rplMode; /* replication mode */
slock_t mutex; /* locks shared variables shown above */
***************
*** 36,57 **** typedef struct WalSnd
--- 39,91 ----
/* There is one WalSndCtl struct for the whole database cluster */
typedef struct
{
+ /* Protected by WalSndWaiterLock */
+ int numWaiters; /* current # of WalSndWaiters */
+ int maxWaiters; /* allocated size of WalSndWaiters */
+
WalSnd walsnds[1]; /* VARIABLE LENGTH ARRAY */
} WalSndCtlData;
extern WalSndCtlData *WalSndCtl;
+ /*
+ * Each waiter has a WalSndWaiter struct in shared memory.
+ */
+ typedef struct WalSndWaiter
+ {
+ BackendId backendId; /* this waiter's backend ID */
+ XLogRecPtr record; /* this waiter wants for replication to be
+ * acked up to this point */
+ Latch *latch; /* pointer to the latch used to wake up this
+ * waiter */
+ } WalSndWaiter;
+
/* global state */
extern bool am_walsender;
+ extern char *standby_name;
/* user-settable parameters */
extern int WalSndDelay;
extern int max_wal_senders;
+ /* struct definition for standbys configuration file */
+ typedef struct
+ {
+ int linenumber;
+ char *standbyName;
+ ReplicationMode rplMode;
+ } StandbysLine;
+
extern int WalSenderMain(void);
extern void WalSndSignals(void);
extern Size WalSndShmemSize(void);
extern void WalSndShmemInit(void);
extern void WalSndWakeup(void);
+ extern void WaitXLogSend(XLogRecPtr record);
+
+ extern void HandleReplicationInterrupt(void);
+
+ extern bool check_standbys(void);
+ extern bool load_standbys(void);
#endif /* _WALSENDER_H */
*** a/src/include/storage/latch.h
--- b/src/include/storage/latch.h
***************
*** 16,21 ****
--- 16,23 ----
#include <signal.h>
+ #include "storage/procsignal.h"
+
/*
* Latch structure should be treated as opaque and only accessed through
* the public functions. It is defined here to allow embedding Latches as
***************
*** 25,33 **** typedef struct
{
sig_atomic_t is_set;
bool is_shared;
- #ifndef WIN32
int owner_pid;
! #else
HANDLE event;
#endif
} Latch;
--- 27,34 ----
{
sig_atomic_t is_set;
bool is_shared;
int owner_pid;
! #ifdef WIN32
HANDLE event;
#endif
} Latch;
***************
*** 43,48 **** extern bool WaitLatch(volatile Latch *latch, long timeout);
--- 44,51 ----
extern int WaitLatchOrSocket(volatile Latch *latch, pgsocket sock,
long timeout);
extern void SetLatch(volatile Latch *latch);
+ extern void SetProcLatch(volatile Latch *latch,
+ ProcSignalReason reason, BackendId backendId);
extern void ResetLatch(volatile Latch *latch);
#define TestLatch(latch) (((volatile Latch *) latch)->is_set)
*** a/src/include/storage/lwlock.h
--- b/src/include/storage/lwlock.h
***************
*** 70,75 **** typedef enum LWLockId
--- 70,76 ----
RelationMappingLock,
AsyncCtlLock,
AsyncQueueLock,
+ WalSndWaiterLock,
/* Individual lock IDs end here */
FirstBufMappingLock,
FirstLockMgrLock = FirstBufMappingLock + NUM_BUFFER_PARTITIONS,
*** a/src/include/storage/proc.h
--- b/src/include/storage/proc.h
***************
*** 14,19 ****
--- 14,20 ----
#ifndef _PROC_H_
#define _PROC_H_
+ #include "storage/latch.h"
#include "storage/lock.h"
#include "storage/pg_sema.h"
#include "utils/timestamp.h"
***************
*** 116,121 **** struct PGPROC
--- 117,128 ----
* lock object by this backend */
/*
+ * Latch used by walsenders to wake up this backend when replication
+ * has been done.
+ */
+ Latch latch;
+
+ /*
* All PROCLOCK objects for locks held or awaited by this backend are
* linked into one of these lists, according to the partition number of
* their lock.
*** a/src/include/storage/procsignal.h
--- b/src/include/storage/procsignal.h
***************
*** 40,45 **** typedef enum
--- 40,47 ----
PROCSIG_RECOVERY_CONFLICT_BUFFERPIN,
PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK,
+ PROCSIG_REPLICATION_INTERRUPT, /* replication interrupt */
+
NUM_PROCSIGNALS /* Must be last! */
} ProcSignalReason;
***************
*** 52,57 **** extern void ProcSignalShmemInit(void);
--- 54,61 ----
extern void ProcSignalInit(int pss_idx);
extern int SendProcSignal(pid_t pid, ProcSignalReason reason,
BackendId backendId);
+ extern bool SetProcSignalReason(pid_t pid, ProcSignalReason reason,
+ BackendId backendId);
extern void procsignal_sigusr1_handler(SIGNAL_ARGS);
*** a/src/interfaces/libpq/fe-connect.c
--- b/src/interfaces/libpq/fe-connect.c
***************
*** 254,259 **** static const PQconninfoOption PQconninfoOptions[] = {
--- 254,262 ----
{"replication", NULL, NULL, NULL,
"Replication", "D", 5},
+ {"standby_name", NULL, NULL, NULL,
+ "Standby-Name", "D", 64},
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
***************
*** 613,618 **** fillPGconn(PGconn *conn, PQconninfoOption *connOptions)
--- 616,623 ----
#endif
tmp = conninfo_getval(connOptions, "replication");
conn->replication = tmp ? strdup(tmp) : NULL;
+ tmp = conninfo_getval(connOptions, "standby_name");
+ conn->standbyName = tmp ? strdup(tmp) : NULL;
}
/*
***************
*** 2622,2627 **** freePGconn(PGconn *conn)
--- 2627,2634 ----
free(conn->dbName);
if (conn->replication)
free(conn->replication);
+ if (conn->standbyName)
+ free(conn->standbyName);
if (conn->pguser)
free(conn->pguser);
if (conn->pgpass)
*** a/src/interfaces/libpq/fe-exec.c
--- b/src/interfaces/libpq/fe-exec.c
***************
*** 2002,2007 **** PQnotifies(PGconn *conn)
--- 2002,2010 ----
/*
* PQputCopyData - send some data to the backend during COPY IN
*
+ * This function can be called by walreceiver even during COPY OUT
+ * to send a message to the master.
+ *
* Returns 1 if successful, 0 if data could not be sent (only possible
* in nonblock mode), or -1 if an error occurs.
*/
***************
*** 2010,2016 **** PQputCopyData(PGconn *conn, const char *buffer, int nbytes)
{
if (!conn)
return -1;
! if (conn->asyncStatus != PGASYNC_COPY_IN)
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("no COPY in progress\n"));
--- 2013,2020 ----
{
if (!conn)
return -1;
! if (conn->asyncStatus != PGASYNC_COPY_IN &&
! conn->asyncStatus != PGASYNC_COPY_OUT)
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("no COPY in progress\n"));
*** a/src/interfaces/libpq/fe-protocol3.c
--- b/src/interfaces/libpq/fe-protocol3.c
***************
*** 1911,1916 **** build_startup_packet(const PGconn *conn, char *packet,
--- 1911,1918 ----
ADD_STARTUP_OPTION("database", conn->dbName);
if (conn->replication && conn->replication[0])
ADD_STARTUP_OPTION("replication", conn->replication);
+ if (conn->standbyName && conn->standbyName[0])
+ ADD_STARTUP_OPTION("standby_name", conn->standbyName);
if (conn->pgoptions && conn->pgoptions[0])
ADD_STARTUP_OPTION("options", conn->pgoptions);
if (conn->send_appname)
*** a/src/interfaces/libpq/libpq-int.h
--- b/src/interfaces/libpq/libpq-int.h
***************
*** 297,302 **** struct pg_conn
--- 297,303 ----
char *fbappname; /* fallback application name */
char *dbName; /* database name */
char *replication; /* connect as the replication standby? */
+ char *standbyName; /* standby name */
char *pguser; /* Postgres username and password, if any */
char *pgpass;
char *keepalives; /* use TCP keepalives? */