From 8d85ed55f46749eff6f493edf1e214f4804726d5 Mon Sep 17 00:00:00 2001 From: Jim Jones Date: Sun, 29 Mar 2026 19:18:49 +0200 Subject: [PATCH v1] Add libpq connection parameter max_wal_replay_size Introduce a new libpq connection parameter, max_wal_replay_size, that allows clients to skip standby servers whose WAL replay backlog exceeds a given threshold. The value is a size specifier accepted by pg_size_bytes(), e.g. "100MB" or "1GB". When this parameter is set and a candidate host is a standby, libpq issues a single query during connection establishment to measure the amount of WAL received but not yet replayed (pg_last_wal_receive_lsn() - pg_last_wal_replay_lsn()). If the backlog exceeds the threshold, the connection to that host is closed and the next host in the list is tried. A standby with no active WAL receiver is also rejected, as its backlog cannot be determined. The check is skipped entirely when connecting to a primary, or when target_session_attrs is set to "primary" or "read-write". It does apply when target_session_attrs is "any", "read-only", "standby", or "prefer-standby". The new state CONNECTION_CHECK_WAL_REPLAY_SIZE is added to the PQconnectPoll() state machine, following the same async pattern as the existing CONNECTION_CHECK_STANDBY state. --- doc/src/sgml/libpq.sgml | 39 ++++ src/interfaces/libpq/fe-connect.c | 199 +++++++++++++++- src/interfaces/libpq/libpq-fe.h | 1 + src/interfaces/libpq/libpq-int.h | 2 + src/test/perl/PostgreSQL/Test/Utils.pm | 1 + src/test/recovery/meson.build | 1 + .../recovery/t/053_max_wal_replay_size.pl | 218 ++++++++++++++++++ src/test/regress/pg_regress.c | 1 + 8 files changed, 459 insertions(+), 3 deletions(-) create mode 100644 src/test/recovery/t/053_max_wal_replay_size.pl diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index 6db823808fc..b91cad42c31 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -2424,6 +2424,45 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname + + max_wal_replay_size + + + Specifies the maximum acceptable WAL replay backlog when connecting + to a standby server. The backlog is measured as the difference + between the last WAL byte received by the standby and the last WAL + byte it has replayed — in other words, how much received WAL is + waiting to be applied. Note that this measures only the apply lag + on the standby itself; it does not account for any delay in WAL + reaching the standby from the primary. The value must be a size + specifier understood by pg_size_bytes, such as + 100MB or 1GB. If not set, no + check is performed. + + + + When this parameter is set and the selected host is a standby (i.e., + in recovery), libpq will query the + server's WAL replay backlog and reject the connection if it exceeds + the specified threshold. A standby that has no active WAL receiver + process is one whose backlog cannot be determined and is therefore + also rejected. In either case, libpq will + try the next host in the list, if any. + + + + This parameter is ignored when connecting to a primary or when + target_session_attrs is set to + primary or read-write. When + target_session_attrs is set to + any, the check still applies, and a standby that + exceeds the threshold will be skipped in favour of the next candidate + host (which may be a primary). + + + + + load_balance_hosts diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index db9b4c8edbf..f6d0ed238ba 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -385,6 +385,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = { "Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */ offsetof(struct pg_conn, target_session_attrs)}, + {"max_wal_replay_size", "PGMAXWALREPLAYSIZE", NULL, NULL, + "Max-WAL-replay-size", "", 64, + offsetof(struct pg_conn, max_wal_replay_size)}, + {"load_balance_hosts", "PGLOADBALANCEHOSTS", DefaultLoadBalanceHosts, NULL, "Load-Balance-Hosts", "", 8, /* sizeof("disable") = 8 */ @@ -512,11 +516,9 @@ static bool sslVerifyProtocolVersion(const char *version); static bool sslVerifyProtocolRange(const char *min, const char *max); static bool pqParseProtocolVersion(const char *value, ProtocolVersion *result, PGconn *conn, const char *context); - /* global variable because fe-auth.c needs to access it */ pgthreadlock_t pg_g_threadlock = default_threadlock; - /* * pqDropConnection * @@ -686,6 +688,7 @@ pqDropServerData(PGconn *conn) conn->std_strings = false; conn->default_transaction_read_only = PG_BOOL_UNKNOWN; conn->in_hot_standby = PG_BOOL_UNKNOWN; + conn->wal_replay_size_checked = false; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; @@ -2119,6 +2122,43 @@ pqConnectOptions2(PGconn *conn) } } + /* + * Validate the max_wal_replay_size option. + * + * The value is passed directly into a SQL query as a string literal + * (inside single quotes). This allowlist blocks every character that + * could escape the quotes or otherwise alter the query — in particular + * single quotes, backslashes, and non-ASCII bytes. Signs ('+'/'-') are + * also excluded: pg_size_bytes() does not accept them, so permitting + * them would only produce a confusing server-side error. + * + * This is a security boundary, not a full semantic validator. Values + * such as "10XY" pass this check but will be rejected by pg_size_bytes() + * on the server with a clear "invalid size" error. There is no + * pg_size_bytes()-equivalent available in libpq, so complete client-side + * validation would require duplicating backend logic. + */ + if (conn->max_wal_replay_size) + { + const char *p; + + for (p = conn->max_wal_replay_size; *p; p++) + { + if (!isascii((unsigned char)*p) || + (!isdigit((unsigned char)*p) && + !isalpha((unsigned char)*p) && + !isspace((unsigned char)*p) && + *p != '.')) + { + conn->status = CONNECTION_BAD; + libpq_append_conn_error(conn, "invalid %s value: \"%s\"", + "max_wal_replay_size", + conn->max_wal_replay_size); + return false; + } + } + } + if (conn->min_protocol_version) { if (!pqParseProtocolVersion(conn->min_protocol_version, &conn->min_pversion, conn, "min_protocol_version")) @@ -2947,6 +2987,7 @@ PQconnectPoll(PGconn *conn) case CONNECTION_CHECK_WRITABLE: case CONNECTION_CONSUME: case CONNECTION_CHECK_STANDBY: + case CONNECTION_CHECK_WAL_REPLAY_SIZE: { /* Load waiting data */ int n = pqReadData(conn); @@ -4426,6 +4467,58 @@ keep_going: /* We will come back to here until there is case CONNECTION_CHECK_TARGET: { + + /* + * Servers before 9.0 have no hot standby mode at all, so treat + * them as primaries unconditionally. This mirrors the same check + * in the SERVER_TYPE_PRIMARY/STANDBY branch below, but must be + * done here first so that the WAL replay size check guard (which + * tests in_hot_standby) sees the correct value even when + * target_session_attrs=any. + */ + if (conn->sversion < 90000) + conn->in_hot_standby = PG_BOOL_NO; + + /* + * If the user specified a max WAL replay size, and we haven't + * yet checked it for this host, and the server is not known to + * be a primary, send an async query to evaluate the replay size. + * The check is skipped when target_session_attrs requires a + * primary or read-write session, and when the server has already + * been identified as a primary via startup parameters. + */ + if (conn->max_wal_replay_size && + !conn->wal_replay_size_checked && + conn->target_server_type != SERVER_TYPE_PRIMARY && + conn->target_server_type != SERVER_TYPE_READ_WRITE && + conn->in_hot_standby != PG_BOOL_NO) + { + char qbuf[512]; + + /* + * Build a single query that determines recovery status and + * evaluates the replay size in one round-trip. We include + * pg_is_in_recovery() so that primaries (where in_hot_standby + * may still be unknown) are handled correctly without a + * separate query. + */ + snprintf(qbuf, sizeof(qbuf), + "SELECT" + " pg_catalog.pg_is_in_recovery()," + " pg_wal_lsn_diff(pg_last_wal_receive_lsn()," + " pg_last_wal_replay_lsn())" + " > pg_catalog.pg_size_bytes('%s')," + " pg_catalog.pg_size_pretty(" + " pg_wal_lsn_diff(pg_last_wal_receive_lsn()," + " pg_last_wal_replay_lsn()))", + conn->max_wal_replay_size); + conn->status = CONNECTION_OK; + if (!PQsendQueryContinue(conn, qbuf)) + goto error_return; + conn->status = CONNECTION_CHECK_WAL_REPLAY_SIZE; + return PGRES_POLLING_READING; + } + /* * If a read-write, read-only, primary, or standby connection * is required, see if we have one. @@ -4714,6 +4807,105 @@ keep_going: /* We will come back to here until there is goto keep_going; } + case CONNECTION_CHECK_WAL_REPLAY_SIZE: + { + /* + * Waiting for result of the WAL replay size check query. We + * must transiently set status = CONNECTION_OK in order to use + * the result-consuming subroutines. + * + * All functions in the query (pg_is_in_recovery, + * pg_last_wal_receive_lsn, pg_last_wal_replay_lsn, + * pg_wal_lsn_diff, pg_size_bytes, pg_size_pretty) are + * granted to PUBLIC, so unprivileged users obtain the same + * results as superusers. This is intentional: the check + * must work for any connecting role. + * + * The query returns three columns: + * 0: pg_is_in_recovery() -- bool + * 1: size_exceeds_threshold -- bool, or NULL if no + * WAL receiver is active + * 2: size_pretty -- text, or NULL + */ + conn->status = CONNECTION_OK; + if (!PQconsumeInput(conn)) + goto error_return; + + if (PQisBusy(conn)) + { + conn->status = CONNECTION_CHECK_WAL_REPLAY_SIZE; + return PGRES_POLLING_READING; + } + + res = PQgetResult(conn); + if (res && PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1) + { + /* col 0: not in recovery means this is a primary */ + if (strcmp(PQgetvalue(res, 0, 0), "f") == 0) + { + conn->in_hot_standby = PG_BOOL_NO; + PQclear(res); + conn->wal_replay_size_checked = true; + conn->status = CONNECTION_CONSUME; + goto keep_going; + } + + /* + * col 1: NULL means pg_last_wal_receive_lsn() returned + * NULL, i.e., no WAL receiver is active on this standby. + */ + else if (PQgetisnull(res, 0, 1)) + { + libpq_append_conn_error(conn, + "could not determine WAL replay backlog on standby (no WAL receiver active)"); + PQclear(res); + conn->status = CONNECTION_OK; + sendTerminateConn(conn); + conn->try_next_host = true; + goto keep_going; + } + + /* col 1: size exceeds the threshold */ + else if (strcmp(PQgetvalue(res, 0, 1), "t") == 0) + { + char *size_pretty = PQgetvalue(res, 0, 2); + + libpq_append_conn_error(conn, + "WAL replay size on standby is too large: %s (max_wal_replay_size=%s)", + size_pretty, + conn->max_wal_replay_size); + PQclear(res); + conn->status = CONNECTION_OK; + sendTerminateConn(conn); + conn->try_next_host = true; + goto keep_going; + } + + /* Replay size is within the acceptable threshold */ + conn->in_hot_standby = PG_BOOL_YES; + PQclear(res); + conn->wal_replay_size_checked = true; + conn->status = CONNECTION_CONSUME; + goto keep_going; + } + + /* + * Something went wrong with the WAL replay size check query. + * If the server returned an error (e.g. invalid size), its + * message is already in conn->errorMessage; don't pile on a + * generic wrapper on top of it. + */ + if (res == NULL || + PQresultStatus(res) != PGRES_FATAL_ERROR) + libpq_append_conn_error(conn, "could not evaluate WAL replay size"); + PQclear(res); + conn->status = CONNECTION_OK; + sendTerminateConn(conn); + conn->try_next_host = true; + goto keep_going; + } + default: libpq_append_conn_error(conn, "invalid connection state %d, probably indicative of memory corruption", @@ -5033,13 +5225,14 @@ pqMakeEmptyPGconn(void) conn->std_strings = false; /* unless server says differently */ conn->default_transaction_read_only = PG_BOOL_UNKNOWN; conn->in_hot_standby = PG_BOOL_UNKNOWN; + conn->wal_replay_size_checked = false; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->verbosity = PQERRORS_DEFAULT; conn->show_context = PQSHOW_CONTEXT_ERRORS; conn->sock = PGINVALID_SOCKET; conn->altsock = PGINVALID_SOCKET; conn->Pfdebug = NULL; - + conn->max_wal_replay_size = NULL; /* * We try to send at least 8K at a time, which is the usual size of pipe * buffers on Unix systems. That way, when we are sending a large amount diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h index f06e7a972c3..c0b8b699a4c 100644 --- a/src/interfaces/libpq/libpq-fe.h +++ b/src/interfaces/libpq/libpq-fe.h @@ -110,6 +110,7 @@ typedef enum CONNECTION_CHECK_TARGET, /* Internal state: checking target server * properties. */ CONNECTION_CHECK_STANDBY, /* Checking if server is in standby mode. */ + CONNECTION_CHECK_WAL_REPLAY_SIZE, /* Checking WAL replay size on standby. */ CONNECTION_ALLOCATED, /* Waiting for connection attempt to be * started. */ CONNECTION_AUTHENTICATING, /* Authentication is in progress with some diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index bd7eb59f5f8..c16fb376621 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -425,6 +425,8 @@ struct pg_conn char *ssl_min_protocol_version; /* minimum TLS protocol version */ char *ssl_max_protocol_version; /* maximum TLS protocol version */ char *target_session_attrs; /* desired session properties */ + char *max_wal_replay_size; /* maximum WAL replay size allowed */ + bool wal_replay_size_checked; /* WAL replay size check done for current host */ char *require_auth; /* name of the expected auth method */ char *load_balance_hosts; /* load balance over hosts */ char *scram_client_key; /* base64-encoded SCRAM client key */ diff --git a/src/test/perl/PostgreSQL/Test/Utils.pm b/src/test/perl/PostgreSQL/Test/Utils.pm index ff843eecc6e..dbb1e31f014 100644 --- a/src/test/perl/PostgreSQL/Test/Utils.pm +++ b/src/test/perl/PostgreSQL/Test/Utils.pm @@ -142,6 +142,7 @@ BEGIN PGSSLROOTCERT PGSSLSNI PGTARGETSESSIONATTRS + PGMAXWALREPLAYSIZE PGUSER PGPORT PGHOST diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build index 36d789720a3..e824ffadc12 100644 --- a/src/test/recovery/meson.build +++ b/src/test/recovery/meson.build @@ -61,6 +61,7 @@ tests += { 't/050_redo_segment_missing.pl', 't/051_effective_wal_level.pl', 't/052_checkpoint_segment_missing.pl', + 't/053_max_wal_replay_size.pl', ], }, } diff --git a/src/test/recovery/t/053_max_wal_replay_size.pl b/src/test/recovery/t/053_max_wal_replay_size.pl new file mode 100644 index 00000000000..4929abddf46 --- /dev/null +++ b/src/test/recovery/t/053_max_wal_replay_size.pl @@ -0,0 +1,218 @@ +# Copyright (c) 2025, PostgreSQL Global Development Group + +# Tests for the max_wal_replay_size parameter with target_session_attrs + +use strict; +use warnings; +use PostgreSQL::Test::Utils; +use PostgreSQL::Test::Cluster; +use Test::More; + +# Initialize the primary node +my $primary = PostgreSQL::Test::Cluster->new('primary'); +$primary->init(allows_streaming => 1); +$primary->append_conf('postgresql.conf', "listen_addresses = 'localhost'"); +$primary->start; + +# Create and start the first standby (will be paused later to induce lag) +my $standby1_backup = $primary->backup('my_backup1'); +my $standby_node1 = PostgreSQL::Test::Cluster->new('standby1'); +$standby_node1->init_from_backup($primary, 'my_backup1', has_streaming => 1); +$standby_node1->append_conf('postgresql.conf', "listen_addresses = 'localhost'"); +$standby_node1->start; + +# Create and start the second standby (keeps streaming normally) +my $standby2_backup = $primary->backup('my_backup2'); +my $standby_node2 = PostgreSQL::Test::Cluster->new('standby2'); +$standby_node2->init_from_backup($primary, 'my_backup2', has_streaming => 1); +$standby_node2->append_conf('postgresql.conf', "listen_addresses = 'localhost'"); +$standby_node2->start; + +# Create and start a third standby that is in recovery but has no WAL receiver +# (no primary_conninfo, no restore_command). pg_is_in_recovery() returns true +# but pg_last_wal_receive_lsn() returns NULL, exercising the code path that +# rejects connections with "no WAL receiver active". +my $standby_node3 = PostgreSQL::Test::Cluster->new('standby3'); +$standby_node3->init_from_backup($primary, 'my_backup2'); +$standby_node3->append_conf('postgresql.conf', "listen_addresses = 'localhost'"); +$standby_node3->set_standby_mode(); +$standby_node3->start; + +# Pause WAL replay on standby1 to simulate lag +$standby_node1->safe_psql('postgres', 'SELECT pg_wal_replay_pause();'); + +# Generate WAL activity on primary to cause replay lag on standby1 +$primary->safe_psql('postgres', 'CREATE TABLE t AS SELECT generate_series(1,100000) i'); +$primary->safe_psql('postgres', 'SELECT pg_switch_wal();'); + +# Wait until standby1 has accumulated a meaningful WAL replay backlog (> 2 MB). +# Polling avoids a fixed-duration sleep that may be too short on slow systems. +$standby_node1->poll_query_until( + 'postgres', + "SELECT pg_wal_lsn_diff(pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn()) > 2 * 1024 * 1024") + or die "standby1 did not accumulate replay lag in time"; + +# Collect connection ports +my ($stdout, $stderr); +my $port_primary = $primary->port; +my $port_standby1 = $standby_node1->port; +my $port_standby2 = $standby_node2->port; +my $port_standby3 = $standby_node3->port; + +# 1. Connects to standby1 when lag is below high threshold (1GB) +$standby_node1->psql( + 'postgres', + 'SELECT inet_server_port()', + connstr => "dbname=postgres host=localhost,localhost,localhost port=$port_primary,$port_standby1,$port_standby2 target_session_attrs=standby max_wal_replay_size=1GB", + stdout => \$stdout, + stderr => \$stderr, +); +is($stdout, $port_standby1, "Connects to standby1 with high size threshold (1GB)"); + +# 2. Skips standby1 due to replay size (1MB threshold), connects to standby2 +$standby_node1->psql( + 'postgres', + 'SELECT inet_server_port()', + connstr => "dbname=postgres host=localhost,localhost,localhost port=$port_primary,$port_standby1,$port_standby2 target_session_attrs=standby max_wal_replay_size=1MB", + stdout => \$stdout, + stderr => \$stderr, +); +is($stdout, $port_standby2, "Skips standby1 due to replay size, connects to standby2"); + +# 3. Skips standby1 due to replay size, connects to primary with prefer-standby +$standby_node1->psql( + 'postgres', + 'SELECT inet_server_port()', + connstr => "dbname=postgres host=localhost,localhost port=$port_primary,$port_standby1 target_session_attrs=prefer-standby max_wal_replay_size=1MB", + stdout => \$stdout, + stderr => \$stderr, +); +is($stdout, $port_primary, "Connects to primary (prefer-standby) due to standby1 replay size"); + +# 4. Connects to primary with target_session_attrs=any (standby1 exceeds threshold) +$standby_node2->psql( + 'postgres', + 'SELECT inet_server_port()', + connstr => "dbname=postgres host=localhost,localhost port=$port_standby1,$port_primary target_session_attrs=any max_wal_replay_size=1MB", + stdout => \$stdout, + stderr => \$stderr, +); +is($stdout, $port_primary, "Connects to primary (any) due to standby1 replay size"); + +# 5. All connections fail: standby1 exceeds replay size, and only standbys are allowed +$standby_node1->psql( + 'postgres', + 'SELECT inet_server_port()', + connstr => "dbname=postgres host=localhost,localhost port=$port_primary,$port_standby1 target_session_attrs=standby max_wal_replay_size=1MB", + stdout => \$stdout, + stderr => \$stderr, +); +like($stderr, qr/WAL replay size on standby is too large/, + "Connection rejected: replay size exceeds 1MB"); + +# 6. Invalid value for max_wal_replay_size (non-numeric) +$standby_node1->psql( + 'postgres', + 'SELECT inet_server_port()', + connstr => "dbname=postgres host=localhost port=$port_standby2 max_wal_replay_size=foo", + stdout => \$stdout, + stderr => \$stderr, +); +like($stderr, qr/invalid size/i, "Rejects non-numeric max_wal_replay_size value"); + +# 7. Negative max_wal_replay_size value +$standby_node1->psql( + 'postgres', + 'SELECT inet_server_port()', + connstr => "dbname=postgres host=localhost port=$port_standby2 max_wal_replay_size=-1GB", + stdout => \$stdout, + stderr => \$stderr, +); +like($stderr, qr/invalid max_wal_replay_size value/, + "Rejects negative max_wal_replay_size value"); + +# 8. Replay lag equals threshold exactly (should allow connection) +my $lag_bytes; +$standby_node1->psql( + 'postgres', + 'SELECT pg_wal_lsn_diff(pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn())', + connstr => "dbname=postgres host=localhost port=$port_standby1", + stdout => \$lag_bytes, +); +chomp($lag_bytes); +$standby_node1->psql( + 'postgres', + 'SELECT 1', + connstr => "dbname=postgres host=localhost port=$port_standby1 max_wal_replay_size=$lag_bytes", + stdout => \$stdout, +); +is($stdout, '1', "Connects when replay lag equals threshold exactly ($lag_bytes bytes)"); + +# 9. Replay lag exceeds threshold by 1 byte (should reject) +my $lag_low; +$standby_node1->psql( + 'postgres', + 'SELECT pg_wal_lsn_diff(pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn()) - 1;', + connstr => "dbname=postgres host=localhost port=$port_standby1", + stdout => \$lag_low, +); +chomp($lag_low); +$standby_node1->psql( + 'postgres', + 'SELECT 1', + connstr => "dbname=postgres host=localhost port=$port_standby1 max_wal_replay_size=$lag_low", + stdout => \$stdout, + stderr => \$stderr, +); +like($stderr, qr/WAL replay size on standby is too large/, + "Connection rejected: replay size exceeds max_wal_replay_size ($lag_low bytes)"); + +# 10. Connects to standby2 after it is freshly restarted +$standby_node2->restart; +$standby_node2->psql( + 'postgres', + 'SELECT inet_server_port()', + connstr => "dbname=postgres host=localhost,localhost,localhost port=$port_primary,$port_standby1,$port_standby2 target_session_attrs=standby max_wal_replay_size=1MB", + stdout => \$stdout, + stderr => \$stderr, +); +is($stdout, $port_standby2, "Connects to standby2 after restart"); + +# 11. Standby with no active WAL receiver is rejected +# standby3 is in recovery (standby.signal present) but has no primary_conninfo +# and no restore_command, so pg_last_wal_receive_lsn() returns NULL. +$standby_node3->psql( + 'postgres', + 'SELECT inet_server_port()', + connstr => "dbname=postgres host=localhost port=$port_standby3 max_wal_replay_size=1MB", + stdout => \$stdout, + stderr => \$stderr, +); +like($stderr, qr/could not determine WAL replay backlog/, + "Rejects standby with no active WAL receiver"); + +# 12. target_session_attrs=read-only: check still applies. +# standby1 exceeds the 1MB threshold, so libpq skips it and connects to +# standby2 (which is current). +$standby_node2->psql( + 'postgres', + 'SELECT inet_server_port()', + connstr => "dbname=postgres host=localhost,localhost,localhost port=$port_primary,$port_standby1,$port_standby2 target_session_attrs=read-only max_wal_replay_size=1MB", + stdout => \$stdout, + stderr => \$stderr, +); +is($stdout, $port_standby2, "read-only: skips standby1 due to replay size, connects to standby2"); + +# 13. target_session_attrs=read-write: check is bypassed entirely. +# The impossibly small threshold of 1 byte must not prevent connecting to +# the primary (which is the only read-write host). +$primary->psql( + 'postgres', + 'SELECT inet_server_port()', + connstr => "dbname=postgres host=localhost,localhost port=$port_primary,$port_standby2 target_session_attrs=read-write max_wal_replay_size=1", + stdout => \$stdout, + stderr => \$stderr, +); +is($stdout, $port_primary, "read-write: max_wal_replay_size check bypassed, connects to primary"); + +done_testing(); diff --git a/src/test/regress/pg_regress.c b/src/test/regress/pg_regress.c index b8b6a911987..5274603cc7c 100644 --- a/src/test/regress/pg_regress.c +++ b/src/test/regress/pg_regress.c @@ -840,6 +840,7 @@ initialize_environment(void) unsetenv("PGSSLROOTCERT"); unsetenv("PGSSLSNI"); unsetenv("PGTARGETSESSIONATTRS"); + unsetenv("PGMAXWALREPLAYSIZE"); unsetenv("PGUSER"); /* PGPORT, see below */ /* PGHOST, see below */ -- 2.43.0