mirror of https://github.com/postgres/postgres
Revert 0f5ca02f53
0f5ca02f53
introduces 3 new keywords. It appears to be too much for relatively
small feature. Given now we past feature freeze, it's already late for
discussion of the new syntax. So, revert.
Discussion: https://postgr.es/m/28209.1586294824%40sss.pgh.pa.us
pull/51/head
parent
02a2e8b442
commit
1aac32df89
@ -1,295 +0,0 @@ |
|||||||
/*-------------------------------------------------------------------------
|
|
||||||
* |
|
||||||
* wait.c |
|
||||||
* Implements WAIT FOR clause for BEGIN and START TRANSACTION commands. |
|
||||||
* This clause allows waiting for given LSN to be replayed on standby. |
|
||||||
* |
|
||||||
* Copyright (c) 2020, PostgreSQL Global Development Group |
|
||||||
* |
|
||||||
* IDENTIFICATION |
|
||||||
* src/backend/commands/wait.c |
|
||||||
* |
|
||||||
*------------------------------------------------------------------------- |
|
||||||
*/ |
|
||||||
#include "postgres.h" |
|
||||||
|
|
||||||
#include <math.h> |
|
||||||
|
|
||||||
#include "access/xlog.h" |
|
||||||
#include "access/xlogdefs.h" |
|
||||||
#include "commands/wait.h" |
|
||||||
#include "funcapi.h" |
|
||||||
#include "miscadmin.h" |
|
||||||
#include "pgstat.h" |
|
||||||
#include "storage/backendid.h" |
|
||||||
#include "storage/pmsignal.h" |
|
||||||
#include "storage/proc.h" |
|
||||||
#include "storage/shmem.h" |
|
||||||
#include "storage/sinvaladt.h" |
|
||||||
#include "storage/spin.h" |
|
||||||
#include "utils/builtins.h" |
|
||||||
#include "utils/pg_lsn.h" |
|
||||||
#include "utils/timestamp.h" |
|
||||||
|
|
||||||
/*
|
|
||||||
* Shared memory structure representing information about LSNs, which backends |
|
||||||
* are waiting for replay. |
|
||||||
*/ |
|
||||||
typedef struct |
|
||||||
{ |
|
||||||
slock_t mutex; /* mutex protecting the fields below */ |
|
||||||
int max_backend_id; /* max backend_id present in lsns[] */ |
|
||||||
pg_atomic_uint64 min_lsn; /* minimal waited LSN */ |
|
||||||
/* per-backend array of waited LSNs */ |
|
||||||
XLogRecPtr lsns[FLEXIBLE_ARRAY_MEMBER]; |
|
||||||
} WaitLSNState; |
|
||||||
|
|
||||||
static WaitLSNState * state; |
|
||||||
|
|
||||||
/*
|
|
||||||
* Add the wait event of the current backend to shared memory array |
|
||||||
*/ |
|
||||||
static void |
|
||||||
WaitLSNAdd(XLogRecPtr lsn_to_wait) |
|
||||||
{ |
|
||||||
SpinLockAcquire(&state->mutex); |
|
||||||
if (state->max_backend_id < MyBackendId) |
|
||||||
state->max_backend_id = MyBackendId; |
|
||||||
|
|
||||||
state->lsns[MyBackendId] = lsn_to_wait; |
|
||||||
|
|
||||||
if (lsn_to_wait < state->min_lsn.value) |
|
||||||
state->min_lsn.value = lsn_to_wait; |
|
||||||
SpinLockRelease(&state->mutex); |
|
||||||
} |
|
||||||
|
|
||||||
/*
|
|
||||||
* Delete wait event of the current backend from the shared memory array. |
|
||||||
*/ |
|
||||||
void |
|
||||||
WaitLSNDelete(void) |
|
||||||
{ |
|
||||||
int i; |
|
||||||
XLogRecPtr deleted_lsn; |
|
||||||
|
|
||||||
SpinLockAcquire(&state->mutex); |
|
||||||
|
|
||||||
deleted_lsn = state->lsns[MyBackendId]; |
|
||||||
state->lsns[MyBackendId] = InvalidXLogRecPtr; |
|
||||||
|
|
||||||
/* If we are deleting the minimal LSN, then choose the next min_lsn */ |
|
||||||
if (!XLogRecPtrIsInvalid(deleted_lsn) && |
|
||||||
deleted_lsn == state->min_lsn.value) |
|
||||||
{ |
|
||||||
state->min_lsn.value = InvalidXLogRecPtr; |
|
||||||
for (i = 2; i <= state->max_backend_id; i++) |
|
||||||
{ |
|
||||||
if (!XLogRecPtrIsInvalid(state->lsns[i]) && |
|
||||||
(state->lsns[i] < state->min_lsn.value || |
|
||||||
XLogRecPtrIsInvalid(state->min_lsn.value))) |
|
||||||
{ |
|
||||||
state->min_lsn.value = state->lsns[i]; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/* If deleting from the end of the array, shorten the array's used part */ |
|
||||||
if (state->max_backend_id == MyBackendId) |
|
||||||
{ |
|
||||||
for (i = (MyBackendId); i >= 2; i--) |
|
||||||
if (!XLogRecPtrIsInvalid(state->lsns[i])) |
|
||||||
{ |
|
||||||
state->max_backend_id = i; |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
SpinLockRelease(&state->mutex); |
|
||||||
} |
|
||||||
|
|
||||||
/*
|
|
||||||
* Report amount of shared memory space needed for WaitLSNState |
|
||||||
*/ |
|
||||||
Size |
|
||||||
WaitLSNShmemSize(void) |
|
||||||
{ |
|
||||||
Size size; |
|
||||||
|
|
||||||
size = offsetof(WaitLSNState, lsns); |
|
||||||
size = add_size(size, mul_size(MaxBackends + 1, sizeof(XLogRecPtr))); |
|
||||||
return size; |
|
||||||
} |
|
||||||
|
|
||||||
/*
|
|
||||||
* Initialize an shared memory structure for waiting for LSN |
|
||||||
*/ |
|
||||||
void |
|
||||||
WaitLSNShmemInit(void) |
|
||||||
{ |
|
||||||
bool found; |
|
||||||
uint32 i; |
|
||||||
|
|
||||||
state = (WaitLSNState *) ShmemInitStruct("pg_wait_lsn", |
|
||||||
WaitLSNShmemSize(), |
|
||||||
&found); |
|
||||||
if (!found) |
|
||||||
{ |
|
||||||
SpinLockInit(&state->mutex); |
|
||||||
|
|
||||||
for (i = 0; i < (MaxBackends + 1); i++) |
|
||||||
state->lsns[i] = InvalidXLogRecPtr; |
|
||||||
|
|
||||||
state->max_backend_id = 0; |
|
||||||
pg_atomic_init_u64(&state->min_lsn, InvalidXLogRecPtr); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/*
|
|
||||||
* Set latches in shared memory to signal that new LSN has been replayed |
|
||||||
*/ |
|
||||||
void |
|
||||||
WaitLSNSetLatch(XLogRecPtr cur_lsn) |
|
||||||
{ |
|
||||||
uint32 i; |
|
||||||
int max_backend_id; |
|
||||||
PGPROC *backend; |
|
||||||
|
|
||||||
SpinLockAcquire(&state->mutex); |
|
||||||
max_backend_id = state->max_backend_id; |
|
||||||
|
|
||||||
for (i = 2; i <= max_backend_id; i++) |
|
||||||
{ |
|
||||||
backend = BackendIdGetProc(i); |
|
||||||
|
|
||||||
if (backend && state->lsns[i] != 0 && |
|
||||||
state->lsns[i] <= cur_lsn) |
|
||||||
{ |
|
||||||
SetLatch(&backend->procLatch); |
|
||||||
} |
|
||||||
} |
|
||||||
SpinLockRelease(&state->mutex); |
|
||||||
} |
|
||||||
|
|
||||||
/*
|
|
||||||
* Get minimal LSN that some backend is waiting for |
|
||||||
*/ |
|
||||||
XLogRecPtr |
|
||||||
WaitLSNGetMin(void) |
|
||||||
{ |
|
||||||
return state->min_lsn.value; |
|
||||||
} |
|
||||||
|
|
||||||
/*
|
|
||||||
* On WAIT use a latch to wait till LSN is replayed, postmaster dies or timeout |
|
||||||
* happens. Timeout is specified in milliseconds. Returns true if LSN was |
|
||||||
* reached and false otherwise. |
|
||||||
*/ |
|
||||||
bool |
|
||||||
WaitLSNUtility(XLogRecPtr target_lsn, const int timeout_ms) |
|
||||||
{ |
|
||||||
XLogRecPtr cur_lsn; |
|
||||||
int latch_events; |
|
||||||
float8 endtime; |
|
||||||
bool res = false; |
|
||||||
bool wait_forever = (timeout_ms <= 0); |
|
||||||
|
|
||||||
endtime = GetNowFloat() + timeout_ms / 1000.0; |
|
||||||
|
|
||||||
latch_events = WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH; |
|
||||||
|
|
||||||
/* Check if we already reached the needed LSN */ |
|
||||||
cur_lsn = GetXLogReplayRecPtr(NULL); |
|
||||||
if (cur_lsn >= target_lsn) |
|
||||||
return true; |
|
||||||
|
|
||||||
WaitLSNAdd(target_lsn); |
|
||||||
ResetLatch(MyLatch); |
|
||||||
|
|
||||||
/* Recheck if LSN was reached while WaitLSNAdd() and ResetLatch() */ |
|
||||||
cur_lsn = GetXLogReplayRecPtr(NULL); |
|
||||||
if (cur_lsn >= target_lsn) |
|
||||||
return true; |
|
||||||
|
|
||||||
for (;;) |
|
||||||
{ |
|
||||||
int rc; |
|
||||||
float8 time_left = 0; |
|
||||||
long time_left_ms = 0; |
|
||||||
|
|
||||||
time_left = endtime - GetNowFloat(); |
|
||||||
|
|
||||||
/* Use 1 second as the default timeout to check for interrupts */ |
|
||||||
if (wait_forever || time_left < 0 || time_left > 1.0) |
|
||||||
time_left_ms = 1000; |
|
||||||
else |
|
||||||
time_left_ms = (long) ceil(time_left * 1000.0); |
|
||||||
|
|
||||||
/* If interrupt, LockErrorCleanup() will do WaitLSNDelete() for us */ |
|
||||||
CHECK_FOR_INTERRUPTS(); |
|
||||||
|
|
||||||
/* If postmaster dies, finish immediately */ |
|
||||||
if (!PostmasterIsAlive()) |
|
||||||
break; |
|
||||||
|
|
||||||
rc = WaitLatch(MyLatch, latch_events, time_left_ms, |
|
||||||
WAIT_EVENT_CLIENT_READ); |
|
||||||
|
|
||||||
ResetLatch(MyLatch); |
|
||||||
|
|
||||||
if (rc & WL_LATCH_SET) |
|
||||||
cur_lsn = GetXLogReplayRecPtr(NULL); |
|
||||||
|
|
||||||
if (rc & WL_TIMEOUT) |
|
||||||
{ |
|
||||||
time_left = endtime - GetNowFloat(); |
|
||||||
/* If the time specified by user has passed, stop waiting */ |
|
||||||
if (!wait_forever && time_left <= 0.0) |
|
||||||
break; |
|
||||||
cur_lsn = GetXLogReplayRecPtr(NULL); |
|
||||||
} |
|
||||||
|
|
||||||
/* If LSN has been replayed */ |
|
||||||
if (target_lsn <= cur_lsn) |
|
||||||
break; |
|
||||||
} |
|
||||||
|
|
||||||
WaitLSNDelete(); |
|
||||||
|
|
||||||
if (cur_lsn < target_lsn) |
|
||||||
ereport(WARNING, |
|
||||||
(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION), |
|
||||||
errmsg("didn't start transaction because LSN was not reached"), |
|
||||||
errhint("Try to increase wait timeout."))); |
|
||||||
else |
|
||||||
res = true; |
|
||||||
|
|
||||||
return res; |
|
||||||
} |
|
||||||
|
|
||||||
/*
|
|
||||||
* Implementation of WAIT FOR clause for BEGIN and START TRANSACTION commands |
|
||||||
*/ |
|
||||||
int |
|
||||||
WaitLSNMain(WaitClause *stmt, DestReceiver *dest) |
|
||||||
{ |
|
||||||
TupleDesc tupdesc; |
|
||||||
TupOutputState *tstate; |
|
||||||
XLogRecPtr target_lsn; |
|
||||||
bool res = false; |
|
||||||
|
|
||||||
target_lsn = DatumGetLSN(DirectFunctionCall1(pg_lsn_in, |
|
||||||
CStringGetDatum(stmt->lsn))); |
|
||||||
res = WaitLSNUtility(target_lsn, stmt->timeout); |
|
||||||
|
|
||||||
/* Need a tuple descriptor representing a single TEXT column */ |
|
||||||
tupdesc = CreateTemplateTupleDesc(1); |
|
||||||
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "LSN reached", TEXTOID, -1, 0); |
|
||||||
|
|
||||||
/* Prepare for projection of tuples */ |
|
||||||
tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsMinimalTuple); |
|
||||||
|
|
||||||
/* Send the result */ |
|
||||||
do_text_output_oneline(tstate, res ? "t" : "f"); |
|
||||||
end_tup_output(tstate); |
|
||||||
return res; |
|
||||||
} |
|
@ -1,26 +0,0 @@ |
|||||||
/*-------------------------------------------------------------------------
|
|
||||||
* |
|
||||||
* wait.h |
|
||||||
* prototypes for commands/wait.c |
|
||||||
* |
|
||||||
* Copyright (c) 2020, PostgreSQL Global Development Group |
|
||||||
* |
|
||||||
* src/include/commands/wait.h |
|
||||||
* |
|
||||||
*------------------------------------------------------------------------- |
|
||||||
*/ |
|
||||||
#ifndef WAIT_H |
|
||||||
#define WAIT_H |
|
||||||
|
|
||||||
#include "tcop/dest.h" |
|
||||||
#include "nodes/parsenodes.h" |
|
||||||
|
|
||||||
extern bool WaitLSNUtility(XLogRecPtr lsn, const int timeout_ms); |
|
||||||
extern Size WaitLSNShmemSize(void); |
|
||||||
extern void WaitLSNShmemInit(void); |
|
||||||
extern void WaitLSNSetLatch(XLogRecPtr cur_lsn); |
|
||||||
extern XLogRecPtr WaitLSNGetMin(void); |
|
||||||
extern int WaitLSNMain(WaitClause *stmt, DestReceiver *dest); |
|
||||||
extern void WaitLSNDelete(void); |
|
||||||
|
|
||||||
#endif /* WAIT_H */ |
|
@ -1,85 +0,0 @@ |
|||||||
# Checks for BEGIN WAIT FOR LSN |
|
||||||
use strict; |
|
||||||
use warnings; |
|
||||||
|
|
||||||
use PostgresNode; |
|
||||||
use TestLib; |
|
||||||
use Test::More tests => 8; |
|
||||||
|
|
||||||
# Initialize master node |
|
||||||
my $node_master = get_new_node('master'); |
|
||||||
$node_master->init(allows_streaming => 1); |
|
||||||
$node_master->start; |
|
||||||
|
|
||||||
# And some content and take a backup |
|
||||||
$node_master->safe_psql('postgres', |
|
||||||
"CREATE TABLE wait_test AS SELECT generate_series(1,10) AS a"); |
|
||||||
my $backup_name = 'my_backup'; |
|
||||||
$node_master->backup($backup_name); |
|
||||||
|
|
||||||
# Using the backup, create a streaming standby with a 1 second delay |
|
||||||
my $node_standby = get_new_node('standby'); |
|
||||||
my $delay = 1; |
|
||||||
$node_standby->init_from_backup($node_master, $backup_name, |
|
||||||
has_streaming => 1); |
|
||||||
$node_standby->append_conf('postgresql.conf', qq[ |
|
||||||
recovery_min_apply_delay = '${delay}s' |
|
||||||
]); |
|
||||||
$node_standby->start; |
|
||||||
|
|
||||||
|
|
||||||
# Check that timeouts make us wait for the specified time (1s here) |
|
||||||
my $current_time = $node_standby->safe_psql('postgres', "SELECT now()"); |
|
||||||
my $two_seconds = 2000; # in milliseconds |
|
||||||
my $start_time = time(); |
|
||||||
$node_standby->safe_psql('postgres', |
|
||||||
"BEGIN WAIT FOR LSN '0/FFFFFFFF' TIMEOUT $two_seconds"); |
|
||||||
my $time_waited = (time() - $start_time) * 1000; # convert to milliseconds |
|
||||||
ok($time_waited >= $two_seconds, "WAIT FOR TIMEOUT waits for enough time"); |
|
||||||
|
|
||||||
|
|
||||||
# Check that timeouts let us stop waiting right away, before reaching target LSN |
|
||||||
$node_master->safe_psql('postgres', |
|
||||||
"INSERT INTO wait_test VALUES (generate_series(11, 20))"); |
|
||||||
my $lsn1 = $node_master->safe_psql('postgres', "SELECT pg_current_wal_lsn()"); |
|
||||||
my ($ret, $out, $err) = $node_standby->psql('postgres', |
|
||||||
"BEGIN WAIT FOR LSN '$lsn1' TIMEOUT 1"); |
|
||||||
|
|
||||||
ok($ret == 0, "zero return value when failed to WAIT FOR LSN on standby"); |
|
||||||
ok($err =~ /WARNING: didn't start transaction because LSN was not reached/, |
|
||||||
"correct error message when failed to WAIT FOR LSN on standby"); |
|
||||||
ok($out eq "f", "if given too little wait time, WAIT doesn't reach target LSN"); |
|
||||||
|
|
||||||
|
|
||||||
# Check that WAIT FOR works fine and reaches target LSN if given no timeout |
|
||||||
|
|
||||||
# Add data on master, memorize master's last LSN |
|
||||||
$node_master->safe_psql('postgres', |
|
||||||
"INSERT INTO wait_test VALUES (generate_series(21, 30))"); |
|
||||||
my $lsn2 = $node_master->safe_psql('postgres', "SELECT pg_current_wal_lsn()"); |
|
||||||
|
|
||||||
# Wait for it to appear on replica, memorize replica's last LSN |
|
||||||
$node_standby->safe_psql('postgres', |
|
||||||
"BEGIN WAIT FOR LSN '$lsn2'"); |
|
||||||
my $reached_lsn = $node_standby->safe_psql('postgres', |
|
||||||
"SELECT pg_last_wal_replay_lsn()"); |
|
||||||
|
|
||||||
# Make sure that master's and replica's LSNs are the same after WAIT |
|
||||||
my $compare_lsns = $node_standby->safe_psql('postgres', |
|
||||||
"SELECT pg_lsn_cmp('$reached_lsn'::pg_lsn, '$lsn2'::pg_lsn)"); |
|
||||||
ok($compare_lsns eq 0, |
|
||||||
"standby reached the same LSN as master before starting transaction"); |
|
||||||
|
|
||||||
|
|
||||||
# Make sure that it's not allowed to use WAIT FOR on master |
|
||||||
($ret, $out, $err) = $node_master->psql('postgres', |
|
||||||
"BEGIN WAIT FOR LSN '0/FFFFFFFF'"); |
|
||||||
|
|
||||||
ok($ret != 0, "non-zero return value when trying to WAIT FOR LSN on master"); |
|
||||||
ok($err =~ /ERROR: WAIT FOR can only be used on standby/, |
|
||||||
"correct error message when trying to WAIT FOR LSN on master"); |
|
||||||
ok($out eq '', "empty output when trying to WAIT FOR LSN on master"); |
|
||||||
|
|
||||||
|
|
||||||
$node_standby->stop; |
|
||||||
$node_master->stop; |
|
Loading…
Reference in new issue