* sync replication standby names defined.
*
* Since this routine gets called every commit time, it's important to
- * exit quickly if sync replication is not requested. So we check
- * WalSndCtl->sync_standbys_defined flag without the lock and exit
- * immediately if it's false. If it's true, we need to check it again
- * later while holding the lock, to check the flag and operate the sync
- * rep queue atomically. This is necessary to avoid the race condition
- * described in SyncRepUpdateSyncStandbysDefined(). On the other hand, if
- * it's false, the lock is not necessary because we don't touch the queue.
+ * exit quickly if sync replication is not requested.
+ *
+ * We check WalSndCtl->sync_standbys_status flag without the lock and exit
+ * immediately if SYNC_STANDBY_INIT is set (the checkpointer has
+ * initialized this data) but SYNC_STANDBY_DEFINED is missing (no sync
+ * replication requested).
+ *
+ * If SYNC_STANDBY_DEFINED is set, we need to check the status again later
+ * while holding the lock, to check the flag and operate the sync rep
+ * queue atomically. This is necessary to avoid the race condition
+ * described in SyncRepUpdateSyncStandbysDefined(). On the other hand, if
+ * SYNC_STANDBY_DEFINED is not set, the lock is not necessary because we
+ * don't touch the queue.
*/
if (!SyncRepRequested() ||
- !((volatile WalSndCtlData *) WalSndCtl)->sync_standbys_defined)
+ ((((volatile WalSndCtlData *) WalSndCtl)->sync_standbys_status) &
+ (SYNC_STANDBY_INIT | SYNC_STANDBY_DEFINED)) == SYNC_STANDBY_INIT)
return;
/* Cap the level for anything other than commit to remote flush only. */
Assert(MyProc->syncRepState == SYNC_REP_NOT_WAITING);
/*
- * We don't wait for sync rep if WalSndCtl->sync_standbys_defined is not
- * set. See SyncRepUpdateSyncStandbysDefined.
+ * We don't wait for sync rep if SYNC_STANDBY_DEFINED is not set. See
+ * SyncRepUpdateSyncStandbysDefined().
*
* Also check that the standby hasn't already replied. Unlikely race
* condition but we'll be fetching that cache line anyway so it's likely
* to be a low cost check.
+ *
+ * If the sync standby data has not been initialized yet
+ * (SYNC_STANDBY_INIT is not set), fall back to a check based on the LSN,
+ * then do a direct GUC check.
*/
- if (!WalSndCtl->sync_standbys_defined ||
- lsn <= WalSndCtl->lsn[mode])
+ if (WalSndCtl->sync_standbys_status & SYNC_STANDBY_INIT)
+ {
+ if ((WalSndCtl->sync_standbys_status & SYNC_STANDBY_DEFINED) == 0 ||
+ lsn <= WalSndCtl->lsn[mode])
+ {
+ LWLockRelease(SyncRepLock);
+ return;
+ }
+ }
+ else if (lsn <= WalSndCtl->lsn[mode])
+ {
+ /*
+ * The LSN is older than what we need to wait for. The sync standby
+ * data has not been initialized yet, but we are OK to not wait
+ * because we know that there is no point in doing so based on the
+ * LSN.
+ */
+ LWLockRelease(SyncRepLock);
+ return;
+ }
+ else if (!SyncStandbysDefined())
{
+ /*
+ * If we are here, the sync standby data has not been initialized yet,
+ * and the LSN is newer than what need to wait for, so we have fallen
+ * back to the best thing we could do in this case: a check on
+ * SyncStandbysDefined() to see if the GUC is set or not.
+ *
+ * When the GUC has a value, we wait until the checkpointer updates
+ * the status data because we cannot be sure yet if we should wait or
+ * not. Here, the GUC has *no* value, we are sure that there is no
+ * point to wait; this matters for example when initializing a
+ * cluster, where we should never wait, and no sync standbys is the
+ * default behavior.
+ */
LWLockRelease(SyncRepLock);
return;
}
/*
* The checkpointer calls this as needed to update the shared
- * sync_standbys_defined flag, so that backends don't remain permanently wedged
+ * sync_standbys_status flag, so that backends don't remain permanently wedged
* if synchronous_standby_names is unset. It's safe to check the current value
* without the lock, because it's only ever updated by one process. But we
* must take the lock to change it.
{
bool sync_standbys_defined = SyncStandbysDefined();
- if (sync_standbys_defined != WalSndCtl->sync_standbys_defined)
+ if (sync_standbys_defined !=
+ ((WalSndCtl->sync_standbys_status & SYNC_STANDBY_DEFINED) != 0))
{
LWLockAcquire(SyncRepLock, LW_EXCLUSIVE);
* backend that hasn't yet reloaded its config might go to sleep on
* the queue (and never wake up). This prevents that.
*/
- WalSndCtl->sync_standbys_defined = sync_standbys_defined;
+ WalSndCtl->sync_standbys_status = SYNC_STANDBY_INIT |
+ (sync_standbys_defined ? SYNC_STANDBY_DEFINED : 0);
+
+ LWLockRelease(SyncRepLock);
+ }
+ else if ((WalSndCtl->sync_standbys_status & SYNC_STANDBY_INIT) == 0)
+ {
+ LWLockAcquire(SyncRepLock, LW_EXCLUSIVE);
+
+ /*
+ * Note that there is no need to wake up the queues here. We would
+ * reach this path only if SyncStandbysDefined() returns false, or it
+ * would mean that some backends are waiting with the GUC set. See
+ * SyncRepWaitForLSN().
+ */
+ Assert(!SyncStandbysDefined());
+
+ /*
+ * Even if there is no sync standby defined, let the readers of this
+ * information know that the sync standby data has been initialized.
+ * This can just be done once, hence the previous check on
+ * SYNC_STANDBY_INIT to avoid useless work.
+ */
+ WalSndCtl->sync_standbys_status |= SYNC_STANDBY_INIT;
LWLockRelease(SyncRepLock);
}