From 4c2862e4c063b5cba99495d967938b109df7dc5c Mon Sep 17 00:00:00 2001 From: Andrew Dunstan Date: Wed, 3 Jun 2026 09:50:17 -0400 Subject: [PATCH v13 3/3] Convert TAP tests to PostgreSQL::Test::Session Convert tests that drove a long-lived background psql process (or repeatedly spawned psql) to use the libpq Session object instead. This covers concurrent-session and asynchronous-query tests across contrib, src/bin and src/test: amcheck, bloom, pg_visibility, test_decoding, pg_amcheck, pg_upgrade, authentication, oauth_validator, test_aio, test_checksums, test_misc, test_slru, xid_wraparound, postmaster, recovery and subscription. Where a test must load libpq from a different installation than the one it connects to (pg_upgrade's cross-version workload), it passes that node so Session locates the library correctly on all platforms. --- contrib/amcheck/t/001_verify_heapam.pl | 36 +- contrib/amcheck/t/002_cic.pl | 7 +- contrib/amcheck/t/003_cic_2pc.pl | 64 ++- contrib/bloom/t/001_wal.pl | 44 +- .../t/001_concurrent_transaction.pl | 9 +- contrib/test_decoding/t/001_repl_stats.pl | 11 +- src/bin/pg_amcheck/t/004_verify_heapam.pl | 97 +++-- .../pg_upgrade/t/007_multixact_conversion.pl | 37 +- src/test/authentication/t/001_password.pl | 35 +- src/test/authentication/t/007_pre_auth.pl | 34 +- .../modules/oauth_validator/t/001_server.pl | 29 +- src/test/modules/test_aio/t/001_aio.pl | 165 ++++--- .../modules/test_aio/t/004_read_stream.pl | 75 ++-- .../modules/test_checksums/t/002_restarts.pl | 7 +- .../test_checksums/t/003_standby_restarts.pl | 13 +- .../modules/test_checksums/t/004_offline.pl | 7 +- src/test/modules/test_misc/t/005_timeouts.pl | 45 +- .../modules/test_misc/t/007_catcache_inval.pl | 24 +- .../t/010_index_concurrently_upsert.pl | 406 ++++++------------ .../modules/test_misc/t/011_lock_stats.pl | 96 ++--- .../test_misc/t/013_temp_obj_multisession.pl | 26 +- src/test/modules/test_slru/t/001_multixact.pl | 17 +- .../xid_wraparound/t/001_emergency_vacuum.pl | 16 +- .../modules/xid_wraparound/t/002_limits.pl | 21 +- .../xid_wraparound/t/004_notify_freeze.pl | 27 +- .../postmaster/t/002_connection_limits.pl | 25 +- src/test/recovery/t/009_twophase.pl | 16 +- src/test/recovery/t/013_crash_restart.pl | 4 +- src/test/recovery/t/022_crash_temp_files.pl | 4 +- src/test/recovery/t/031_recovery_conflict.pl | 39 +- src/test/recovery/t/037_invalid_database.pl | 45 +- .../t/040_standby_failover_slots_sync.pl | 26 +- .../recovery/t/041_checkpoint_at_promote.pl | 11 +- src/test/recovery/t/042_low_level_backup.pl | 12 +- .../recovery/t/046_checkpoint_logical_slot.pl | 66 ++- .../t/047_checkpoint_physical_slot.pl | 18 +- .../recovery/t/048_vacuum_horizon_floor.pl | 40 +- src/test/recovery/t/049_wait_for_lsn.pl | 156 ++++--- .../recovery/t/050_redo_segment_missing.pl | 14 +- .../recovery/t/051_effective_wal_level.pl | 23 +- src/test/subscription/t/015_stream.pl | 35 +- src/test/subscription/t/035_conflicts.pl | 23 +- .../t/038_walsnd_shutdown_timeout.pl | 15 +- 43 files changed, 875 insertions(+), 1045 deletions(-) diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl index e3fee19ae5d..9ea72179fcd 100644 --- a/contrib/amcheck/t/001_verify_heapam.pl +++ b/contrib/amcheck/t/001_verify_heapam.pl @@ -5,6 +5,7 @@ use strict; use warnings FATAL => 'all'; use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Session; use PostgreSQL::Test::Utils; use Test::More; @@ -18,7 +19,9 @@ $node = PostgreSQL::Test::Cluster->new('test'); $node->init(no_data_checksums => 1); $node->append_conf('postgresql.conf', 'autovacuum=off'); $node->start; -$node->safe_psql('postgres', q(CREATE EXTENSION amcheck)); +my $session = PostgreSQL::Test::Session->new(node => $node); + +$session->do(q(CREATE EXTENSION amcheck)); # # Check a table with data loaded but no corruption, freezing, etc. @@ -49,7 +52,7 @@ detects_heap_corruption( # Check a corrupt table with all-frozen data # fresh_test_table('test'); -$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test)); +$session->do(q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test)); detects_no_corruption("verify_heapam('test')", "all-frozen not corrupted table"); corrupt_first_page('test'); @@ -81,7 +84,7 @@ sub relation_filepath my ($relname) = @_; my $pgdata = $node->data_dir; - my $rel = $node->safe_psql('postgres', + my $rel = $session->query_oneval( qq(SELECT pg_relation_filepath('$relname'))); die "path not found for relation $relname" unless defined $rel; return "$pgdata/$rel"; @@ -92,8 +95,8 @@ sub fresh_test_table { my ($relname) = @_; - return $node->safe_psql( - 'postgres', qq( + return $session->do( + qq( DROP TABLE IF EXISTS $relname CASCADE; CREATE TABLE $relname (a integer, b text); ALTER TABLE $relname SET (autovacuum_enabled=false); @@ -117,8 +120,8 @@ sub fresh_test_sequence { my ($seqname) = @_; - return $node->safe_psql( - 'postgres', qq( + return $session->do( + qq( DROP SEQUENCE IF EXISTS $seqname CASCADE; CREATE SEQUENCE $seqname INCREMENT BY 13 @@ -134,8 +137,8 @@ sub advance_test_sequence { my ($seqname) = @_; - return $node->safe_psql( - 'postgres', qq( + return $session->query_oneval( + qq( SELECT nextval('$seqname'); )); } @@ -145,10 +148,7 @@ sub set_test_sequence { my ($seqname) = @_; - return $node->safe_psql( - 'postgres', qq( - SELECT setval('$seqname', 102); - )); + return $session->query_oneval(qq(SELECT setval('$seqname', 102))); } # Call SQL functions to reset the sequence @@ -156,8 +156,8 @@ sub reset_test_sequence { my ($seqname) = @_; - return $node->safe_psql( - 'postgres', qq( + return $session->do( + qq( ALTER SEQUENCE $seqname RESTART WITH 51 )); } @@ -169,6 +169,7 @@ sub corrupt_first_page my ($relname) = @_; my $relpath = relation_filepath($relname); + $session->close; $node->stop; my $fh; @@ -191,6 +192,7 @@ sub corrupt_first_page or BAIL_OUT("close failed: $!"); $node->start; + $session->reconnect; } sub detects_heap_corruption @@ -216,7 +218,7 @@ sub detects_corruption my ($function, $testname, @re) = @_; - my $result = $node->safe_psql('postgres', qq(SELECT * FROM $function)); + my $result = $session->query_tuples(qq(SELECT * FROM $function)); like($result, $_, $testname) for (@re); } @@ -226,7 +228,7 @@ sub detects_no_corruption my ($function, $testname) = @_; - my $result = $node->safe_psql('postgres', qq(SELECT * FROM $function)); + my $result = $session->query_tuples(qq(SELECT * FROM $function)); is($result, '', $testname); } diff --git a/contrib/amcheck/t/002_cic.pl b/contrib/amcheck/t/002_cic.pl index 629d00c1d05..f2c7bc8653f 100644 --- a/contrib/amcheck/t/002_cic.pl +++ b/contrib/amcheck/t/002_cic.pl @@ -6,6 +6,7 @@ use strict; use warnings FATAL => 'all'; use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Session; use PostgreSQL::Test::Utils; use Test::More; @@ -72,8 +73,8 @@ $node->safe_psql('postgres', q(INSERT INTO quebec SELECT i FROM generate_series(1, 2) s(i);)); # start background transaction -my $in_progress_h = $node->background_psql('postgres'); -$in_progress_h->query_safe(q(BEGIN; SELECT pg_current_xact_id();)); +my $in_progress_h = PostgreSQL::Test::Session->new(node => $node); +$in_progress_h->do(q(BEGIN; SELECT pg_current_xact_id();)); # delete one row from table, while background transaction is in progress $node->safe_psql('postgres', q(DELETE FROM quebec WHERE i = 1;)); @@ -86,7 +87,7 @@ my $result = $node->psql('postgres', q(SELECT bt_index_parent_check('oscar', heapallindexed => true))); is($result, '0', 'bt_index_parent_check for CIC after removed row'); -$in_progress_h->quit; +$in_progress_h->close; $node->stop; done_testing(); diff --git a/contrib/amcheck/t/003_cic_2pc.pl b/contrib/amcheck/t/003_cic_2pc.pl index f28eeac17ef..d3a1643bf33 100644 --- a/contrib/amcheck/t/003_cic_2pc.pl +++ b/contrib/amcheck/t/003_cic_2pc.pl @@ -7,6 +7,7 @@ use warnings FATAL => 'all'; use PostgreSQL::Test::Cluster; use PostgreSQL::Test::Utils; +use PostgreSQL::Test::Session; use Test::More; @@ -36,29 +37,42 @@ $node->safe_psql('postgres', q(CREATE TABLE tbl(i int, j jsonb))); # statements. # -my $main_h = $node->background_psql('postgres'); +my $main_h = PostgreSQL::Test::Session->new(node=>$node); -$main_h->query_safe( +$main_h->do_async( q( BEGIN; INSERT INTO tbl VALUES(0, '[[14,2,3]]'); )); -my $cic_h = $node->background_psql('postgres'); +my $cic_h = PostgreSQL::Test::Session->new(node=>$node); -$cic_h->query_until( - qr/start/, q( -\echo start -CREATE INDEX CONCURRENTLY idx ON tbl(i); -CREATE INDEX CONCURRENTLY ginidx ON tbl USING gin(j); +$cic_h->setnonblocking(1); + +$cic_h->enterPipelineMode(); + +$cic_h->do_pipeline( + q( +CREATE INDEX CONCURRENTLY idx ON tbl(i) +)); + +$cic_h->pipelineSync(); + +$cic_h->do_pipeline( + q( +CREATE INDEX CONCURRENTLY ginidx ON tbl USING gin(j) )); -$main_h->query_safe( +$cic_h->pipelineSync(); + +$main_h->wait_for_completion; +$main_h->do_async( q( PREPARE TRANSACTION 'a'; )); -$main_h->query_safe( +$main_h->wait_for_completion; +$main_h->do_async( q( BEGIN; INSERT INTO tbl VALUES(0, '[[14,2,3]]'); @@ -66,7 +80,8 @@ INSERT INTO tbl VALUES(0, '[[14,2,3]]'); $node->safe_psql('postgres', q(COMMIT PREPARED 'a';)); -$main_h->query_safe( +$main_h->wait_for_completion; +$main_h->do_async( q( PREPARE TRANSACTION 'b'; BEGIN; @@ -75,14 +90,17 @@ INSERT INTO tbl VALUES(0, '"mary had a little lamb"'); $node->safe_psql('postgres', q(COMMIT PREPARED 'b';)); -$main_h->query_safe( - q( -PREPARE TRANSACTION 'c'; -COMMIT PREPARED 'c'; -)); +$main_h->wait_for_completion; +$main_h->do( + q(PREPARE TRANSACTION 'c';), + q(COMMIT PREPARED 'c';)); -$main_h->quit; -$cic_h->quit; +$main_h->close; + +# called twice out of an abundance of caution about pipeline mode +$cic_h->wait_for_completion; +$cic_h->wait_for_completion; +$cic_h->close; $result = $node->psql('postgres', q(SELECT bt_index_check('idx',true))); is($result, '0', 'bt_index_check after overlapping 2PC'); @@ -106,10 +124,9 @@ PREPARE TRANSACTION 'persists_forever'; )); $node->restart; -my $reindex_h = $node->background_psql('postgres'); -$reindex_h->query_until( - qr/start/, q( -\echo start +my $reindex_h = PostgreSQL::Test::Session->new(node => $node); +$reindex_h->do_async( + q( DROP INDEX CONCURRENTLY idx; CREATE INDEX CONCURRENTLY idx ON tbl(i); DROP INDEX CONCURRENTLY ginidx; @@ -117,7 +134,8 @@ CREATE INDEX CONCURRENTLY ginidx ON tbl USING gin(j); )); $node->safe_psql('postgres', "COMMIT PREPARED 'spans_restart'"); -$reindex_h->quit; +$reindex_h->wait_for_completion; +$reindex_h->close; $result = $node->psql('postgres', q(SELECT bt_index_check('idx',true))); is($result, '0', 'bt_index_check after 2PC and restart'); $result = $node->psql('postgres', q(SELECT gin_index_check('ginidx'))); diff --git a/contrib/bloom/t/001_wal.pl b/contrib/bloom/t/001_wal.pl index 683b1876055..f86c49d6357 100644 --- a/contrib/bloom/t/001_wal.pl +++ b/contrib/bloom/t/001_wal.pl @@ -5,11 +5,14 @@ use strict; use warnings FATAL => 'all'; use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Session; use PostgreSQL::Test::Utils; use Test::More; my $node_primary; my $node_standby; +my $session_primary; +my $session_standby; # Run few queries on both primary and standby and check their results match. sub test_index_replay @@ -21,20 +24,18 @@ sub test_index_replay # Wait for standby to catch up $node_primary->wait_for_catchup($node_standby); - my $queries = qq(SET enable_seqscan=off; -SET enable_bitmapscan=on; -SET enable_indexscan=on; -SELECT * FROM tst WHERE i = 0; -SELECT * FROM tst WHERE i = 3; -SELECT * FROM tst WHERE t = 'b'; -SELECT * FROM tst WHERE t = 'f'; -SELECT * FROM tst WHERE i = 3 AND t = 'c'; -SELECT * FROM tst WHERE i = 7 AND t = 'e'; -); + my @queries = ( + "SELECT * FROM tst WHERE i = 0", + "SELECT * FROM tst WHERE i = 3", + "SELECT * FROM tst WHERE t = 'b'", + "SELECT * FROM tst WHERE t = 'f'", + "SELECT * FROM tst WHERE i = 3 AND t = 'c'", + "SELECT * FROM tst WHERE i = 7 AND t = 'e'", + ); # Run test queries and compare their result - my $primary_result = $node_primary->safe_psql("postgres", $queries); - my $standby_result = $node_standby->safe_psql("postgres", $queries); + my $primary_result = $session_primary->query_tuples(@queries); + my $standby_result = $session_standby->query_tuples(@queries); is($primary_result, $standby_result, "$test_name: query result matches"); return; @@ -55,13 +56,24 @@ $node_standby->init_from_backup($node_primary, $backup_name, has_streaming => 1); $node_standby->start; +# Create and initialize the sessions +$session_primary = PostgreSQL::Test::Session->new(node => $node_primary); +$session_standby = PostgreSQL::Test::Session->new(node => $node_standby); +my $initset = q[ + SET enable_seqscan=off; + SET enable_bitmapscan=on; + SET enable_indexscan=on; +]; +$session_primary->do($initset); +$session_standby->do($initset); + # Create some bloom index on primary -$node_primary->safe_psql("postgres", "CREATE EXTENSION bloom;"); -$node_primary->safe_psql("postgres", "CREATE TABLE tst (i int4, t text);"); -$node_primary->safe_psql("postgres", +$session_primary->do("CREATE EXTENSION bloom;"); +$session_primary->do("CREATE TABLE tst (i int4, t text);"); +$session_primary->do( "INSERT INTO tst SELECT i%10, substr(encode(sha256(i::text::bytea), 'hex'), 1, 1) FROM generate_series(1,10000) i;" ); -$node_primary->safe_psql("postgres", +$session_primary->do( "CREATE INDEX bloomidx ON tst USING bloom (i, t) WITH (col1 = 3);"); # Test that queries give same result diff --git a/contrib/pg_visibility/t/001_concurrent_transaction.pl b/contrib/pg_visibility/t/001_concurrent_transaction.pl index 3aa556892a6..cf6e3b45182 100644 --- a/contrib/pg_visibility/t/001_concurrent_transaction.pl +++ b/contrib/pg_visibility/t/001_concurrent_transaction.pl @@ -6,6 +6,7 @@ use strict; use warnings FATAL => 'all'; use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Session; use PostgreSQL::Test::Utils; use Test::More; @@ -24,10 +25,10 @@ $standby->start; # Setup another database $node->safe_psql("postgres", "CREATE DATABASE other_database;\n"); -my $bsession = $node->background_psql('other_database'); +my $bsession = PostgreSQL::Test::Session->new(node => $node, dbname => 'other_database'); # Run a concurrent transaction -$bsession->query_safe( +$bsession->do( qq[ BEGIN; SELECT txid_current(); @@ -55,8 +56,8 @@ $result = $standby->safe_psql("postgres", ok($result eq "", "pg_check_visible() detects no errors"); # Shutdown -$bsession->query_safe("COMMIT;"); -$bsession->quit; +$bsession->do("COMMIT;"); +$bsession->close; $node->stop; $standby->stop; diff --git a/contrib/test_decoding/t/001_repl_stats.pl b/contrib/test_decoding/t/001_repl_stats.pl index 6814c792e2b..79b9182014f 100644 --- a/contrib/test_decoding/t/001_repl_stats.pl +++ b/contrib/test_decoding/t/001_repl_stats.pl @@ -7,6 +7,7 @@ use strict; use warnings FATAL => 'all'; use File::Path qw(rmtree); use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Session; use PostgreSQL::Test::Utils; use Test::More; @@ -129,11 +130,11 @@ $node->safe_psql('postgres', ); # Look at slot data, with a persistent connection. -my $bpgsql = $node->background_psql('postgres', on_error_stop => 1); +my $bgsession = PostgreSQL::Test::Session->new(node=>$node); # Launch query and look at slot data, incrementing the refcount of the # stats entry. -$bpgsql->query_safe( +$bgsession->do_async( "SELECT pg_logical_slot_peek_binary_changes('$slot_name_restart', NULL, NULL)" ); @@ -150,7 +151,8 @@ $node->safe_psql('postgres', # Look again at the slot data. The local stats reference should be refreshed # to the reinitialized entry. -$bpgsql->query_safe( +$bgsession->wait_for_completion; +$bgsession->do_async( "SELECT pg_logical_slot_peek_binary_changes('$slot_name_restart', NULL, NULL)" ); # Drop again the slot, the entry is not dropped yet as the previous session @@ -176,6 +178,7 @@ command_like( my $stats_file = "$datadir/pg_stat/pgstat.stat"; ok(-f "$stats_file", "stats file must exist after shutdown"); -$bpgsql->quit; +$bgsession->wait_for_completion; +$bgsession->close; done_testing(); diff --git a/src/bin/pg_amcheck/t/004_verify_heapam.pl b/src/bin/pg_amcheck/t/004_verify_heapam.pl index 95f1f34c90d..9afacbcaaea 100644 --- a/src/bin/pg_amcheck/t/004_verify_heapam.pl +++ b/src/bin/pg_amcheck/t/004_verify_heapam.pl @@ -5,6 +5,7 @@ use strict; use warnings FATAL => 'all'; use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Session; use PostgreSQL::Test::Utils; use Test::More; @@ -190,16 +191,17 @@ $node->append_conf('postgresql.conf', 'max_prepared_transactions=10'); $node->start; my $port = $node->port; my $pgdata = $node->data_dir; -$node->safe_psql('postgres', "CREATE EXTENSION amcheck"); -$node->safe_psql('postgres', "CREATE EXTENSION pageinspect"); +my $session = PostgreSQL::Test::Session->new(node => $node); +$session->do("CREATE EXTENSION amcheck"); +$session->do("CREATE EXTENSION pageinspect"); # Get a non-zero datfrozenxid -$node->safe_psql('postgres', qq(VACUUM FREEZE)); +$session->do(qq(VACUUM FREEZE)); # Create the test table with precisely the schema that our corruption function # expects. -$node->safe_psql( - 'postgres', qq( +$session->do( + qq( CREATE TABLE public.test (a BIGINT, b TEXT, c TEXT); ALTER TABLE public.test SET (autovacuum_enabled=false); ALTER TABLE public.test ALTER COLUMN c SET STORAGE EXTERNAL; @@ -209,14 +211,15 @@ $node->safe_psql( # We want (0 < datfrozenxid < test.relfrozenxid). To achieve this, we freeze # an otherwise unused table, public.junk, prior to inserting data and freezing # public.test -$node->safe_psql( - 'postgres', qq( +$session->do( + qq( CREATE TABLE public.junk AS SELECT 'junk'::TEXT AS junk_column; ALTER TABLE public.junk SET (autovacuum_enabled=false); - VACUUM FREEZE public.junk - )); + ), + 'VACUUM FREEZE public.junk' +); -my $rel = $node->safe_psql('postgres', +my $rel = $session->query_oneval( qq(SELECT pg_relation_filepath('public.test'))); my $relpath = "$pgdata/$rel"; @@ -229,23 +232,24 @@ my $ROWCOUNT_BASIC = 16; # First insert data needed for tests unrelated to update chain validation. # Then freeze the page. These tuples are at offset numbers 1 to 16. -$node->safe_psql( - 'postgres', qq( +$session->do( + qq( INSERT INTO public.test (a, b, c) SELECT x'DEADF9F9DEADF9F9'::bigint, 'abcdefg', repeat('w', 10000) FROM generate_series(1, $ROWCOUNT_BASIC); - VACUUM FREEZE public.test;) + ), + 'VACUUM FREEZE public.test' ); # Create some simple HOT update chains for line pointer validation. After # the page is HOT pruned, we'll have two redirects line pointers each pointing # to a tuple. We'll then change the second redirect to point to the same # tuple as the first one and verify that we can detect corruption. -$node->safe_psql( - 'postgres', qq( +$session->do( + qq( INSERT INTO public.test (a, b, c) VALUES ( x'DEADF9F9DEADF9F9'::bigint, 'abcdefg', generate_series(1,2)); -- offset numbers 17 and 18 @@ -254,8 +258,8 @@ $node->safe_psql( )); # Create some more HOT update chains. -$node->safe_psql( - 'postgres', qq( +$session->do( + qq( INSERT INTO public.test (a, b, c) VALUES ( x'DEADF9F9DEADF9F9'::bigint, 'abcdefg', generate_series(3,6)); -- offset numbers 21 through 24 @@ -264,25 +268,30 @@ $node->safe_psql( )); # Negative test case of HOT-pruning with aborted tuple. -$node->safe_psql( - 'postgres', qq( +$session->do( + qq( BEGIN; UPDATE public.test SET c = 'a' WHERE c = '5'; -- offset number 27 ABORT; - VACUUM FREEZE public.test; - )); + ), + 'VACUUM FREEZE public.test;', + ); # Next update on any tuple will be stored at the same place of tuple inserted # by aborted transaction. This should not cause the table to appear corrupt. -$node->safe_psql( - 'postgres', qq( +$session->do( + qq( + BEGIN; UPDATE public.test SET c = 'a' WHERE c = '6'; -- offset number 27 again - VACUUM FREEZE public.test; - )); + COMMIT; + ), + 'VACUUM FREEZE public.test;', + ); # Data for HOT chain validation, so not calling VACUUM FREEZE. -$node->safe_psql( - 'postgres', qq( +$session->do( + qq( + BEGIN; INSERT INTO public.test (a, b, c) VALUES ( x'DEADF9F9DEADF9F9'::bigint, 'abcdefg', generate_series(7,15)); -- offset numbers 28 to 36 @@ -293,11 +302,12 @@ $node->safe_psql( UPDATE public.test SET c = 'a' WHERE c = '13'; -- offset number 41 UPDATE public.test SET c = 'a' WHERE c = '14'; -- offset number 42 UPDATE public.test SET c = 'a' WHERE c = '15'; -- offset number 43 + COMMIT; )); # Need one aborted transaction to test corruption in HOT chains. -$node->safe_psql( - 'postgres', qq( +$session->do( + qq( BEGIN; UPDATE public.test SET c = 'a' WHERE c = '9'; -- offset number 44 ABORT; @@ -306,19 +316,19 @@ $node->safe_psql( # Need one in-progress transaction to test few corruption in HOT chains. # We are creating PREPARE TRANSACTION here as these will not be aborted # even if we stop the node. -$node->safe_psql( - 'postgres', qq( +$session->do( + qq( BEGIN; PREPARE TRANSACTION 'in_progress_tx'; )); -my $in_progress_xid = $node->safe_psql( - 'postgres', qq( +my $in_progress_xid = $session->query_oneval( + qq( SELECT transaction FROM pg_prepared_xacts; )); -my $relfrozenxid = $node->safe_psql('postgres', +my $relfrozenxid = $session->query_oneval( q(select relfrozenxid from pg_class where relname = 'test')); -my $datfrozenxid = $node->safe_psql('postgres', +my $datfrozenxid = $session->query_oneval( q(select datfrozenxid from pg_database where datname = 'postgres')); # Sanity check that our 'test' table has a relfrozenxid newer than the @@ -326,6 +336,7 @@ my $datfrozenxid = $node->safe_psql('postgres', # first normal xid. We rely on these invariants in some of our tests. if ($datfrozenxid <= 3 || $datfrozenxid >= $relfrozenxid) { + $session->close; $node->clean_node; plan skip_all => "Xid thresholds not as expected: got datfrozenxid = $datfrozenxid, relfrozenxid = $relfrozenxid"; @@ -334,17 +345,21 @@ if ($datfrozenxid <= 3 || $datfrozenxid >= $relfrozenxid) # Find where each of the tuples is located on the page. If a particular # line pointer is a redirect rather than a tuple, we record the offset as -1. -my @lp_off = split '\n', $node->safe_psql( - 'postgres', qq( +my $lp_off_res = $session->query( + qq( SELECT CASE WHEN lp_flags = 2 THEN -1 ELSE lp_off END FROM heap_page_items(get_raw_page('test', 'main', 0)) ) -); + ); +my @lp_off; +push(@lp_off, $_->[0]) foreach @{$lp_off_res->{rows}}; + scalar @lp_off == $ROWCOUNT or BAIL_OUT("row offset counts mismatch"); # Sanity check that our 'test' table on disk layout matches expectations. If # this is not so, we will have to skip the test until somebody updates the test # to work on this platform. +$session->close; $node->stop; my $file; open($file, '+<', $relpath) @@ -751,17 +766,19 @@ for (my $tupidx = 0; $tupidx < $ROWCOUNT; $tupidx++) close($file) or BAIL_OUT("close failed: $!"); $node->start; +$session->reconnect; # Run pg_amcheck against the corrupt table with epoch=0, comparing actual # corruption messages against the expected messages $node->command_checks_all( [ 'pg_amcheck', '--no-dependent-indexes', '--port' => $port, 'postgres' ], 2, [@expected], [], 'Expected corruption message output'); -$node->safe_psql( - 'postgres', qq( +$session->do( + qq( COMMIT PREPARED 'in_progress_tx'; )); +$session->close; $node->teardown_node; $node->clean_node; diff --git a/src/bin/pg_upgrade/t/007_multixact_conversion.pl b/src/bin/pg_upgrade/t/007_multixact_conversion.pl index 867a0623153..9a4c8ed9839 100644 --- a/src/bin/pg_upgrade/t/007_multixact_conversion.pl +++ b/src/bin/pg_upgrade/t/007_multixact_conversion.pl @@ -15,6 +15,7 @@ use warnings FATAL => 'all'; use Math::BigInt; use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Session; use PostgreSQL::Test::Utils; use Test::More; @@ -38,10 +39,8 @@ my $tempdir = PostgreSQL::Test::Utils::tempdir; # versions. # # The first argument is the cluster to connect to, the second argument -# is a cluster using the new version. We need the 'psql' binary from -# the new version, the new cluster is otherwise unused. (We need to -# use the new 'psql' because some of the more advanced background psql -# perl module features depend on a fairly recent psql version.) +# is a cluster using the new version. We need the libpq library from +# the new version (for Session), the new cluster is otherwise unused. sub mxact_workload { my $node = shift; # Cluster to connect to @@ -79,18 +78,20 @@ sub mxact_workload # in each connection. for (0 .. $nclients) { - # Use the psql binary from the new installation. The - # BackgroundPsql functionality doesn't work with older psql - # versions. - my $conn = $binnode->background_psql( - '', - connstr => $node->connstr('postgres'), - timeout => $connection_timeout_secs); + # Connect to the (old) cluster, but load libpq from the new + # installation: the Session FFI wrapper binds libpq functions that + # may not be present in the old installation's libpq. Passing the + # node (rather than an explicit libdir) lets Session locate the + # library correctly on all platforms, including Windows where it + # lives in bindir. + my $conn = PostgreSQL::Test::Session->new( + node => $binnode, + connstr => $node->connstr('postgres')); - $conn->query_safe("SET log_statement=none", verbose => $verbose) + $conn->do("SET log_statement=none") unless $verbose; - $conn->query_safe("SET enable_seqscan=off", verbose => $verbose); - $conn->query_safe("BEGIN", verbose => $verbose); + $conn->do("SET enable_seqscan=off"); + $conn->do("BEGIN"); push(@connections, $conn); } @@ -108,9 +109,9 @@ sub mxact_workload my $conn = $connections[ $i % $nclients ]; my $sql = ($i % $abort_every == 0) ? "ABORT" : "COMMIT"; - $conn->query_safe($sql, verbose => $verbose); + $conn->do($sql); - $conn->query_safe("BEGIN", verbose => $verbose); + $conn->do("BEGIN"); if ($i % $update_every == 0) { $sql = qq[ @@ -126,12 +127,12 @@ sub mxact_workload ) as x ]; } - $conn->query_safe($sql, verbose => $verbose); + $conn->do($sql); } for my $conn (@connections) { - $conn->quit(); + $conn->close; } $node->stop; diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl index 69ed4919b16..ac2aa3ce23e 100644 --- a/src/test/authentication/t/001_password.pl +++ b/src/test/authentication/t/001_password.pl @@ -13,6 +13,7 @@ use strict; use warnings FATAL => 'all'; use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Session; use PostgreSQL::Test::Utils; use Test::More; if (!$use_unix_sockets) @@ -185,36 +186,18 @@ my $res = $node->safe_psql( WHERE rolname = 'scram_role_iter'"); is($res, 'SCRAM-SHA-256$1024:', 'scram_iterations in server side ROLE'); -# If we don't have IO::Pty, forget it, because IPC::Run depends on that -# to support pty connections. Also skip if IPC::Run isn't at least 0.98 -# as earlier version cause the session to time out. -SKIP: -{ - skip "IO::Pty and IPC::Run >= 0.98 required", 1 - unless eval { require IO::Pty; IPC::Run->VERSION('0.98'); }; +# set password using PQchangePassword +my $session = PostgreSQL::Test::Session->new (node => $node); - # Alter the password on the created role using \password in psql to ensure - # that clientside password changes use the scram_iterations value when - # calculating SCRAM secrets. - my $session = $node->interactive_psql('postgres'); - - $session->set_query_timer_restart(); - $session->query("SET password_encryption='scram-sha-256';"); - $session->query("SET scram_iterations=42;"); - $session->query_until(qr/Enter new password/, - "\\password scram_role_iter\n"); - $session->query_until(qr/Enter it again/, "pass\n"); - $session->query_until(qr/postgres=# /, "pass\n"); - $session->quit; - - $res = $node->safe_psql( - 'postgres', +$session->do("SET password_encryption='scram-sha-256';", + "SET scram_iterations=42;"); +$res = $session->set_password("scram_role_iter","pass"); +is($res->{status}, 1, "set password ok"); +$res = $session->query_oneval( "SELECT substr(rolpassword,1,17) FROM pg_authid WHERE rolname = 'scram_role_iter'"); - is($res, 'SCRAM-SHA-256$42:', - 'scram_iterations in psql \password command'); -} +is($res, 'SCRAM-SHA-256$42:', 'scram_iterations correct'); # Create a database to test regular expression. $node->safe_psql('postgres', "CREATE database regex_testdb;"); diff --git a/src/test/authentication/t/007_pre_auth.pl b/src/test/authentication/t/007_pre_auth.pl index 04063f4721d..17de92bc41a 100644 --- a/src/test/authentication/t/007_pre_auth.pl +++ b/src/test/authentication/t/007_pre_auth.pl @@ -7,6 +7,7 @@ use strict; use warnings FATAL => 'all'; use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Session; use PostgreSQL::Test::Utils; use Time::HiRes qw(usleep); use Test::More; @@ -36,24 +37,27 @@ if (!$node->check_extension('injection_points')) $node->safe_psql('postgres', 'CREATE EXTENSION injection_points'); # Connect to the server and inject a waitpoint. -my $psql = $node->background_psql('postgres'); -$psql->query_safe("SELECT injection_points_attach('init-pre-auth', 'wait')"); +my $session = PostgreSQL::Test::Session->new(node => $node); +$session->do("SELECT injection_points_attach('init-pre-auth', 'wait')"); # From this point on, all new connections will hang during startup, just before -# authentication. Use the $psql connection handle for server interaction. -my $conn = $node->background_psql('postgres', wait => 0); +# authentication. Use the $session connection handle for server interaction. +my $conn = PostgreSQL::Test::Session->new(node => $node, wait => 0); # Wait for the connection to show up in pg_stat_activity, with the wait_event -# of the injection point. +# of the injection point. We need to poll the async connection to drive it forward. my $pid; while (1) { - $pid = $psql->query( + # Drive the async connection forward - it won't progress without polling + $conn->poll_connect(); + + $pid = $session->query_oneval( qq{SELECT pid FROM pg_stat_activity WHERE backend_type = 'client backend' AND state = 'starting' - AND wait_event = 'init-pre-auth';}); - last if $pid ne ""; + AND wait_event = 'init-pre-auth';}, 1); + last if defined $pid && $pid ne ""; usleep(100_000); } @@ -62,24 +66,24 @@ note "backend $pid is authenticating"; ok(1, 'authenticating connections are recorded in pg_stat_activity'); # Detach the waitpoint and wait for the connection to complete. -$psql->query_safe("SELECT injection_points_wakeup('init-pre-auth');"); +$session->do("SELECT injection_points_wakeup('init-pre-auth')"); $conn->wait_connect(); # Make sure the pgstat entry is updated eventually. while (1) { my $state = - $psql->query("SELECT state FROM pg_stat_activity WHERE pid = $pid;"); - last if $state eq "idle"; + $session->query_oneval("SELECT state FROM pg_stat_activity WHERE pid = $pid"); + last if defined $state && $state eq "idle"; - note "state for backend $pid is '$state'; waiting for 'idle'..."; + note "state for backend $pid is '" . ($state // 'undef') . "'; waiting for 'idle'..."; usleep(100_000); } ok(1, 'authenticated connections reach idle state in pg_stat_activity'); -$psql->query_safe("SELECT injection_points_detach('init-pre-auth');"); -$psql->quit(); -$conn->quit(); +$session->do("SELECT injection_points_detach('init-pre-auth')"); +$session->close(); +$conn->close(); done_testing(); diff --git a/src/test/modules/oauth_validator/t/001_server.pl b/src/test/modules/oauth_validator/t/001_server.pl index 1619fbffd45..3d4eb1bfe30 100644 --- a/src/test/modules/oauth_validator/t/001_server.pl +++ b/src/test/modules/oauth_validator/t/001_server.pl @@ -12,6 +12,7 @@ use warnings FATAL => 'all'; use JSON::PP qw(encode_json); use MIME::Base64 qw(encode_base64); use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Session; use PostgreSQL::Test::Utils; use Test::More; @@ -57,7 +58,7 @@ $node->safe_psql('postgres', 'CREATE USER testalt;'); $node->safe_psql('postgres', 'CREATE USER testparam;'); # Save a background connection for later configuration changes. -my $bgconn = $node->background_psql('postgres'); +my $bgconn = PostgreSQL::Test::Session->new(node => $node); my $webserver = OAuth::Server->new(); $webserver->run(); @@ -124,11 +125,11 @@ $log_start = $node->wait_for_log(qr/reloading configuration files/, $log_start); # Check pg_hba_file_rules() support. -my $contents = $bgconn->query_safe( +my $contents = $bgconn->query( qq(SELECT rule_number, auth_method, options FROM pg_hba_file_rules ORDER BY rule_number;)); -is( $contents, +is( $contents->{psqlout}, qq{1|oauth|\{issuer=$issuer,"scope=openid postgres",validator=validator\} 2|oauth|\{issuer=$issuer/.well-known/oauth-authorization-server/alternate,"scope=openid postgres alt",validator=validator\} 3|oauth|\{issuer=$issuer/param,"scope=openid postgres",validator=validator\}}, @@ -551,7 +552,7 @@ $common_connstr = "dbname=postgres oauth_issuer=$issuer/.well-known/openid-configuration oauth_scope='' oauth_client_id=f02c6361-0635"; # Misbehaving validators must fail shut. -$bgconn->query_safe("ALTER SYSTEM SET oauth_validator.authn_id TO ''"); +$bgconn->do("ALTER SYSTEM SET oauth_validator.authn_id TO ''"); $node->reload; $log_start = $node->wait_for_log(qr/reloading configuration files/, $log_start); @@ -562,15 +563,15 @@ $node->connect_fails( expected_stderr => qr/OAuth bearer authentication failed/, log_like => [ qr/connection authenticated: identity=""/, - qr/FATAL:\s+OAuth bearer authentication failed/, + qr/FATAL: ( [A-Z0-9]+:)? OAuth bearer authentication failed/, qr/DETAIL:\s+Validator provided no identity/, ]); # Even if a validator authenticates the user, if the token isn't considered # valid, the connection fails. -$bgconn->query_safe( +$bgconn->do( "ALTER SYSTEM SET oauth_validator.authn_id TO 'test\@example.org'"); -$bgconn->query_safe( +$bgconn->do( "ALTER SYSTEM SET oauth_validator.authorize_tokens TO false"); $node->reload; $log_start = @@ -582,7 +583,7 @@ $node->connect_fails( expected_stderr => qr/OAuth bearer authentication failed/, log_like => [ qr/connection authenticated: identity="test\@example\.org"/, - qr/FATAL:\s+OAuth bearer authentication failed/, + qr/FATAL: ( [A-Z0-9]+:)? OAuth bearer authentication failed/, qr/DETAIL:\s+Validator failed to authorize the provided token/, ]); @@ -664,8 +665,8 @@ local all testparam oauth issuer="$issuer" scope="" delegate_ident_mapping=1 }); # To start, have the validator use the role names as authn IDs. -$bgconn->query_safe("ALTER SYSTEM RESET oauth_validator.authn_id"); -$bgconn->query_safe("ALTER SYSTEM RESET oauth_validator.authorize_tokens"); +$bgconn->do("ALTER SYSTEM RESET oauth_validator.authn_id"); +$bgconn->do("ALTER SYSTEM RESET oauth_validator.authorize_tokens"); $node->reload; $log_start = @@ -682,7 +683,7 @@ $node->connect_fails( expected_stderr => qr/OAuth bearer authentication failed/); # Have the validator identify the end user as user@example.com. -$bgconn->query_safe( +$bgconn->do( "ALTER SYSTEM SET oauth_validator.authn_id TO 'user\@example.com'"); $node->reload; $log_start = @@ -706,7 +707,7 @@ $node->connect_ok( expected_stderr => qr@Visit https://example\.com/ and enter the code: postgresuser@); -$bgconn->query_safe("ALTER SYSTEM RESET oauth_validator.authn_id"); +$bgconn->do("ALTER SYSTEM RESET oauth_validator.authn_id"); $node->reload; $log_start = $node->wait_for_log(qr/reloading configuration files/, $log_start); @@ -829,7 +830,7 @@ $user = "testalt"; $node->connect_fails( "user=$user dbname=postgres oauth_issuer=$issuer/.well-known/oauth-authorization-server/alternate oauth_client_id=f02c6361-0636", "fail_validator is used for $user", - expected_stderr => qr/FATAL:\s+fail_validator: sentinel error/); + expected_stderr => qr/FATAL: ( [A-Z0-9]+:)? fail_validator: sentinel error/); # # Test ABI compatibility magic marker @@ -849,7 +850,7 @@ $node->connect_fails( "user=test dbname=postgres oauth_issuer=$issuer/.well-known/oauth-authorization-server/alternate oauth_client_id=f02c6361-0636", "magic_validator is used for $user", expected_stderr => - qr/FATAL:\s+OAuth validator module "magic_validator": magic number mismatch/ + qr/FATAL: ( [A-Z0-9]+:)? OAuth validator module "magic_validator": magic number mismatch/ ); $node->stop; diff --git a/src/test/modules/test_aio/t/001_aio.pl b/src/test/modules/test_aio/t/001_aio.pl index 63cadd64c15..bf946801601 100644 --- a/src/test/modules/test_aio/t/001_aio.pl +++ b/src/test/modules/test_aio/t/001_aio.pl @@ -4,6 +4,7 @@ use strict; use warnings FATAL => 'all'; use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Session; use PostgreSQL::Test::Utils; use Test::More; @@ -62,19 +63,19 @@ sub psql_like { local $Test::Builder::Level = $Test::Builder::Level + 1; my $io_method = shift; - my $psql = shift; + my $session = shift; my $name = shift; my $sql = shift; my $expected_stdout = shift; my $expected_stderr = shift; - my ($cmdret, $output); - ($output, $cmdret) = $psql->query($sql); + my $res = $session->query($sql); + my $output = $res->{psqlout}; like($output, $expected_stdout, "$io_method: $name: expected stdout"); - like($psql->{stderr}, $expected_stderr, + like($session->get_stderr(), $expected_stderr, "$io_method: $name: expected stderr"); - $psql->{stderr} = ''; + $session->clear_stderr(); return $output; } @@ -87,17 +88,15 @@ sub query_wait_block local $Test::Builder::Level = $Test::Builder::Level + 1; my $io_method = shift; my $node = shift; - my $psql = shift; + my $session = shift; my $name = shift; my $sql = shift; my $waitfor = shift; my $wait_current_session = shift; - my $pid = $psql->query_safe('SELECT pg_backend_pid()'); + my $pid = $session->backend_pid(); - $psql->{stdin} .= qq($sql;\n); - $psql->{run}->pump_nb(); - note "issued sql: $sql;\n"; + $session->do_async($sql); ok(1, "$io_method: $name: issued sql"); my $waitquery; @@ -163,7 +162,7 @@ sub test_handle my $io_method = shift; my $node = shift; - my $psql = $node->background_psql('postgres', on_error_stop => 0); + my $psql = PostgreSQL::Test::Session->new(node => $node); # leak warning: implicit xact psql_like( @@ -269,7 +268,7 @@ sub test_batchmode my $io_method = shift; my $node = shift; - my $psql = $node->background_psql('postgres', on_error_stop => 0); + my $psql = PostgreSQL::Test::Session->new(node => $node); # In a build with RELCACHE_FORCE_RELEASE and CATCACHE_FORCE_RELEASE, just # using SELECT batch_start() causes spurious test failures, because the @@ -329,7 +328,7 @@ sub test_io_error my $node = shift; my ($ret, $output); - my $psql = $node->background_psql('postgres', on_error_stop => 0); + my $psql = PostgreSQL::Test::Session->new(node => $node); $psql->query_safe( qq( @@ -381,8 +380,8 @@ sub test_startwait_io my $node = shift; my ($ret, $output); - my $psql_a = $node->background_psql('postgres', on_error_stop => 0); - my $psql_b = $node->background_psql('postgres', on_error_stop => 0); + my $psql_a = PostgreSQL::Test::Session->new(node => $node); + my $psql_b = PostgreSQL::Test::Session->new(node => $node); ### Verify behavior for normal tables @@ -441,7 +440,7 @@ sub test_startwait_io # Because the IO was terminated, but not marked as valid, second session should get the right to start io - pump_until($psql_b->{run}, $psql_b->{timeout}, \$psql_b->{stdout}, qr/t/); + $psql_b->wait_for_async_pattern(qr/t/); ok(1, "$io_method: blocking start buffer io, can start io"); # terminate the IO again @@ -479,7 +478,7 @@ sub test_startwait_io qr/^$/); # Because the IO was terminated, and marked as valid, second session should complete but not need io - pump_until($psql_b->{run}, $psql_b->{timeout}, \$psql_b->{stdout}, qr/f/); + $psql_b->wait_for_async_pattern(qr/f/); ok(1, "$io_method: blocking start buffer io, no need to start io"); # buffer is valid now, make it invalid again @@ -558,8 +557,8 @@ sub test_complete_foreign my $node = shift; my ($ret, $output); - my $psql_a = $node->background_psql('postgres', on_error_stop => 0); - my $psql_b = $node->background_psql('postgres', on_error_stop => 0); + my $psql_a = PostgreSQL::Test::Session->new(node => $node); + my $psql_b = PostgreSQL::Test::Session->new(node => $node); # Issue IO without waiting for completion, then sleep $psql_a->query_safe( @@ -628,7 +627,7 @@ sub test_close_fd my $node = shift; my ($ret, $output); - my $psql = $node->background_psql('postgres', on_error_stop => 0); + my $psql = PostgreSQL::Test::Session->new(node => $node); psql_like( $io_method, @@ -678,7 +677,7 @@ sub test_inject my $node = shift; my ($ret, $output); - my $psql = $node->background_psql('postgres', on_error_stop => 0); + my $psql = PostgreSQL::Test::Session->new(node => $node); # injected what we'd expect $psql->query_safe(qq(SELECT inj_io_short_read_attach(8192);)); @@ -812,7 +811,7 @@ sub test_inject_worker my $node = shift; my ($ret, $output); - my $psql = $node->background_psql('postgres', on_error_stop => 0); + my $psql = PostgreSQL::Test::Session->new(node => $node); # trigger a failure to reopen, should error out, but should recover $psql->query_safe( @@ -849,7 +848,7 @@ sub test_invalidate my $io_method = shift; my $node = shift; - my $psql = $node->background_psql('postgres', on_error_stop => 0); + my $psql = PostgreSQL::Test::Session->new(node => $node); foreach my $persistency (qw(normal unlogged temporary)) { @@ -907,8 +906,8 @@ sub test_zero my $io_method = shift; my $node = shift; - my $psql_a = $node->background_psql('postgres', on_error_stop => 0); - my $psql_b = $node->background_psql('postgres', on_error_stop => 0); + my $psql_a = PostgreSQL::Test::Session->new(node => $node); + my $psql_b = PostgreSQL::Test::Session->new(node => $node); foreach my $persistency (qw(normal temporary)) { @@ -933,7 +932,7 @@ SELECT modify_rel_block('tbl_zero', 0, corrupt_header=>true); qq( SELECT read_rel_block_ll('tbl_zero', 0, zero_on_error=>false)), qr/^$/, - qr/^psql::\d+: ERROR: invalid page in block 0 of relation "base\/.*\/.*$/ + qr/^(?:psql::\d+: )?ERROR: invalid page in block 0 of relation "base\/.*\/.*$/ ); # Check that page validity errors are zeroed @@ -944,7 +943,7 @@ SELECT read_rel_block_ll('tbl_zero', 0, zero_on_error=>false)), qq( SELECT read_rel_block_ll('tbl_zero', 0, zero_on_error=>true)), qr/^$/, - qr/^psql::\d+: WARNING: invalid page in block 0 of relation "base\/.*\/.*"; zeroing out page$/ + qr/^(?:psql::\d+: )?WARNING: invalid page in block 0 of relation "base\/.*\/.*"; zeroing out page$/ ); # And that once the corruption is fixed, we can read again @@ -952,7 +951,7 @@ SELECT read_rel_block_ll('tbl_zero', 0, zero_on_error=>true)), qq( SELECT modify_rel_block('tbl_zero', 0, zero=>true); )); - $psql_a->{stderr} = ''; + $psql_a->clear_stderr(); psql_like( $io_method, @@ -975,7 +974,7 @@ SELECT modify_rel_block('tbl_zero', 3, corrupt_header=>true); "$persistency: test zeroing of invalid block 3", qq(SELECT read_rel_block_ll('tbl_zero', 3, zero_on_error=>true);), qr/^$/, - qr/^psql::\d+: WARNING: invalid page in block 3 of relation "base\/.*\/.*"; zeroing out page$/ + qr/^(?:psql::\d+: )?WARNING: invalid page in block 3 of relation "base\/.*\/.*"; zeroing out page$/ ); @@ -992,7 +991,7 @@ SELECT modify_rel_block('tbl_zero', 3, corrupt_header=>true); "$persistency: test reading of invalid block 2,3 in larger read", qq(SELECT read_rel_block_ll('tbl_zero', 1, nblocks=>4, zero_on_error=>false)), qr/^$/, - qr/^psql::\d+: ERROR: 2 invalid pages among blocks 1..4 of relation "base\/.*\/.*\nDETAIL: Block 2 held the first invalid page\.\nHINT:[^\n]+$/ + qr/^(?:psql::\d+: )?ERROR: 2 invalid pages among blocks 1..4 of relation "base\/.*\/.*\nDETAIL: Block 2 held the first invalid page\.\nHINT:[^\n]+$/ ); # Then test zeroing via ZERO_ON_ERROR flag @@ -1002,7 +1001,7 @@ SELECT modify_rel_block('tbl_zero', 3, corrupt_header=>true); "$persistency: test zeroing of invalid block 2,3 in larger read, ZERO_ON_ERROR", qq(SELECT read_rel_block_ll('tbl_zero', 1, nblocks=>4, zero_on_error=>true)), qr/^$/, - qr/^psql::\d+: WARNING: zeroing out 2 invalid pages among blocks 1..4 of relation "base\/.*\/.*\nDETAIL: Block 2 held the first zeroed page\.\nHINT:[^\n]+$/ + qr/^(?:psql::\d+: )?WARNING: zeroing out 2 invalid pages among blocks 1..4 of relation "base\/.*\/.*\nDETAIL: Block 2 held the first zeroed page\.\nHINT:[^\n]+$/ ); # Then test zeroing via zero_damaged_pages @@ -1017,7 +1016,7 @@ SELECT read_rel_block_ll('tbl_zero', 1, nblocks=>4, zero_on_error=>false) COMMIT; ), qr/^$/, - qr/^psql::\d+: WARNING: zeroing out 2 invalid pages among blocks 1..4 of relation "base\/.*\/.*\nDETAIL: Block 2 held the first zeroed page\.\nHINT:[^\n]+$/ + qr/^(?:psql::\d+: )?WARNING: zeroing out 2 invalid pages among blocks 1..4 of relation "base\/.*\/.*\nDETAIL: Block 2 held the first zeroed page\.\nHINT:[^\n]+$/ ); $psql_a->query_safe(qq(COMMIT)); @@ -1030,7 +1029,7 @@ SELECT invalidate_rel_block('tbl_zero', g.i) FROM generate_series(0, 15) g(i); SELECT modify_rel_block('tbl_zero', 3, zero=>true); )); - $psql_a->{stderr} = ''; + $psql_a->clear_stderr(); psql_like( $io_method, @@ -1039,7 +1038,7 @@ SELECT modify_rel_block('tbl_zero', 3, zero=>true); qq( SELECT count(*) FROM tbl_zero), qr/^$/, - qr/^psql::\d+: ERROR: invalid page in block 2 of relation "base\/.*\/.*$/ + qr/^(?:psql::\d+: )?ERROR: invalid page in block 2 of relation "base\/.*\/.*$/ ); # Verify that bufmgr.c IO zeroes out pages with page validity errors @@ -1054,7 +1053,7 @@ SELECT count(*) FROM tbl_zero; COMMIT; ), qr/^\d+$/, - qr/^psql::\d+: WARNING: invalid page in block 2 of relation "base\/.*\/.*$/ + qr/^(?:psql::\d+: )?WARNING: invalid page in block 2 of relation "base\/.*\/.*$/ ); # Check that warnings/errors about page validity in an IO started by @@ -1093,7 +1092,7 @@ DROP TABLE tbl_zero; )); } - $psql_a->{stderr} = ''; + $psql_a->clear_stderr(); $psql_a->quit(); $psql_b->quit(); @@ -1105,19 +1104,17 @@ sub test_checksum my $io_method = shift; my $node = shift; - my $psql_a = $node->background_psql('postgres', on_error_stop => 0); + my $psql_a = PostgreSQL::Test::Session->new(node => $node); - $psql_a->query_safe( - qq( -CREATE TABLE tbl_normal(id int) WITH (AUTOVACUUM_ENABLED = false); -INSERT INTO tbl_normal SELECT generate_series(1, 5000); -SELECT modify_rel_block('tbl_normal', 3, corrupt_checksum=>true); - -CREATE TEMPORARY TABLE tbl_temp(id int) WITH (AUTOVACUUM_ENABLED = false); -INSERT INTO tbl_temp SELECT generate_series(1, 5000); -SELECT modify_rel_block('tbl_temp', 3, corrupt_checksum=>true); -SELECT modify_rel_block('tbl_temp', 4, corrupt_checksum=>true); -)); + # Split multi-statement query into separate calls to match psql behavior + # where errors in one statement don't prevent subsequent statements + $psql_a->query_safe(qq(CREATE TABLE tbl_normal(id int) WITH (AUTOVACUUM_ENABLED = false))); + $psql_a->query_safe(qq(INSERT INTO tbl_normal SELECT generate_series(1, 5000))); + $psql_a->query_safe(qq(SELECT modify_rel_block('tbl_normal', 3, corrupt_checksum=>true))); + $psql_a->query_safe(qq(CREATE TEMPORARY TABLE tbl_temp(id int) WITH (AUTOVACUUM_ENABLED = false))); + $psql_a->query_safe(qq(INSERT INTO tbl_temp SELECT generate_series(1, 5000))); + $psql_a->query_safe(qq(SELECT modify_rel_block('tbl_temp', 3, corrupt_checksum=>true))); + $psql_a->query_safe(qq(SELECT modify_rel_block('tbl_temp', 4, corrupt_checksum=>true))); # To be able to test checksum failures on shared rels we need a shared rel # with invalid pages - which is a bit scary. pg_shseclabel seems like a @@ -1140,7 +1137,7 @@ SELECT modify_rel_block('pg_shseclabel', 3, corrupt_checksum=>true); qq( SELECT read_rel_block_ll('tbl_normal', 3, nblocks=>1, zero_on_error=>false);), qr/^$/, - qr/^psql::\d+: ERROR: invalid page in block 3 of relation "base\/\d+\/\d+"$/ + qr/^(?:psql::\d+: )?ERROR: invalid page in block 3 of relation "base\/\d+\/\d+"$/ ); my ($cs_count_after, $cs_ts_after) = @@ -1162,7 +1159,7 @@ SELECT read_rel_block_ll('tbl_normal', 3, nblocks=>1, zero_on_error=>false);), qq( SELECT read_rel_block_ll('tbl_temp', 4, nblocks=>2, zero_on_error=>false);), qr/^$/, - qr/^psql::\d+: ERROR: invalid page in block 4 of relation "base\/\d+\/t\d+_\d+"$/ + qr/^(?:psql::\d+: )?ERROR: invalid page in block 4 of relation "base\/\d+\/t\d+_\d+"$/ ); ($cs_count_after, $cs_ts_after) = checksum_failures($psql_a, 'postgres'); @@ -1183,7 +1180,7 @@ SELECT read_rel_block_ll('tbl_temp', 4, nblocks=>2, zero_on_error=>false);), qq( SELECT read_rel_block_ll('pg_shseclabel', 2, nblocks=>2, zero_on_error=>false);), qr/^$/, - qr/^psql::\d+: ERROR: 2 invalid pages among blocks 2..3 of relation "global\/\d+"\nDETAIL: Block 2 held the first invalid page\.\nHINT:[^\n]+$/ + qr/^(?:psql::\d+: )?ERROR: 2 invalid pages among blocks 2..3 of relation "global\/\d+"\nDETAIL: Block 2 held the first invalid page\.\nHINT:[^\n]+$/ ); ($cs_count_after, $cs_ts_after) = checksum_failures($psql_a); @@ -1201,7 +1198,7 @@ SELECT read_rel_block_ll('pg_shseclabel', 2, nblocks=>2, zero_on_error=>false);) SELECT modify_rel_block('pg_shseclabel', 1, zero=>true); DROP TABLE tbl_normal; )); - $psql_a->{stderr} = ''; + $psql_a->clear_stderr(); $psql_a->quit(); } @@ -1214,7 +1211,7 @@ sub test_checksum_createdb my $io_method = shift; my $node = shift; - my $psql = $node->background_psql('postgres', on_error_stop => 0); + my $psql = PostgreSQL::Test::Session->new(node => $node); $node->safe_psql('postgres', 'CREATE DATABASE regression_createdb_source'); @@ -1248,7 +1245,7 @@ STRATEGY wal_log; "create database w/ wal strategy, invalid source", $createdb_sql, qr/^$/, - qr/psql::\d+: ERROR: invalid page in block 1 of relation "base\/\d+\/\d+"$/ + qr/^(?:psql::\d+: )?ERROR: invalid page in block 1 of relation "base\/\d+\/\d+"$/ ); my ($cs_count_after, $cs_ts_after) = checksum_failures($psql, 'regression_createdb_source'); @@ -1277,7 +1274,7 @@ sub test_ignore_checksum my $io_method = shift; my $node = shift; - my $psql = $node->background_psql('postgres', on_error_stop => 0); + my $psql = PostgreSQL::Test::Session->new(node => $node); # Test setup $psql->query_safe( @@ -1342,7 +1339,7 @@ SELECT modify_rel_block('tbl_cs_fail', 4, corrupt_header=>true); qq( SELECT read_rel_block_ll('tbl_cs_fail', 3, nblocks=>1, zero_on_error=>false);), qr/^$/, - qr/^psql::\d+: WARNING: ignoring checksum failure in block 3/ + qr/^(?:psql::\d+: )?WARNING: ignoring checksum failure in block 3/ ); # Check that the log contains a LOG message about the failure @@ -1357,7 +1354,7 @@ SELECT read_rel_block_ll('tbl_cs_fail', 3, nblocks=>1, zero_on_error=>false);), qq( SELECT read_rel_block_ll('tbl_cs_fail', 2, nblocks=>3, zero_on_error=>false);), qr/^$/, - qr/^psql::\d+: ERROR: invalid page in block 4 of relation "base\/\d+\/\d+"$/ + qr/^(?:psql::\d+: )?ERROR: invalid page in block 4 of relation "base\/\d+\/\d+"$/ ); # Test multi-block read with different problems in different blocks @@ -1369,7 +1366,7 @@ SELECT modify_rel_block('tbl_cs_fail', 3, corrupt_checksum=>true, corrupt_header SELECT modify_rel_block('tbl_cs_fail', 4, corrupt_header=>true); SELECT modify_rel_block('tbl_cs_fail', 5, corrupt_header=>true); )); - $psql->{stderr} = ''; + $psql->clear_stderr(); $log_location = -s $node->logfile; psql_like( @@ -1379,7 +1376,7 @@ SELECT modify_rel_block('tbl_cs_fail', 5, corrupt_header=>true); qq( SELECT read_rel_block_ll('tbl_cs_fail', 1, nblocks=>5, zero_on_error=>true);), qr/^$/, - qr/^psql::\d+: WARNING: zeroing 3 page\(s\) and ignoring 2 checksum failure\(s\) among blocks 1..5 of relation "/ + qr/^(?:psql::\d+: )?WARNING: zeroing 3 page\(s\) and ignoring 2 checksum failure\(s\) among blocks 1..5 of relation "/ ); @@ -1412,7 +1409,7 @@ SELECT read_rel_block_ll('tbl_cs_fail', 1, nblocks=>5, zero_on_error=>true);), qq( SELECT modify_rel_block('tbl_cs_fail', 3, corrupt_checksum=>true, corrupt_header=>true); )); - $psql->{stderr} = ''; + $psql->clear_stderr(); psql_like( $io_method, @@ -1421,7 +1418,7 @@ SELECT modify_rel_block('tbl_cs_fail', 3, corrupt_checksum=>true, corrupt_header qq( SELECT read_rel_block_ll('tbl_cs_fail', 3, nblocks=>1, zero_on_error=>false);), qr/^$/, - qr/^psql::\d+: ERROR: invalid page in block 3 of relation "/); + qr/^(?:psql::\d+: )?ERROR: invalid page in block 3 of relation "/); psql_like( $io_method, @@ -1430,7 +1427,7 @@ SELECT read_rel_block_ll('tbl_cs_fail', 3, nblocks=>1, zero_on_error=>false);), qq( SELECT read_rel_block_ll('tbl_cs_fail', 3, nblocks=>1, zero_on_error=>true);), qr/^$/, - qr/^psql::\d+: WARNING: invalid page in block 3 of relation "base\/.*"; zeroing out page/ + qr/^(?:psql::\d+: )?WARNING: invalid page in block 3 of relation "base\/.*"; zeroing out page/ ); @@ -1446,8 +1443,8 @@ sub test_read_buffers my ($ret, $output); my $table; - my $psql_a = $node->background_psql('postgres', on_error_stop => 0); - my $psql_b = $node->background_psql('postgres', on_error_stop => 0); + my $psql_a = PostgreSQL::Test::Session->new(node => $node); + my $psql_b = PostgreSQL::Test::Session->new(node => $node); $psql_a->query_safe( qq( @@ -1631,7 +1628,7 @@ INSERT INTO tmp_ok SELECT generate_series(1, 5000); $psql_a->query_safe(qq|SELECT evict_rel('$table')|); my $buf_id = - $psql_b->query_safe(qq|SELECT buffer_create_toy('$table', 3)|); + $psql_b->query_oneval(qq|SELECT buffer_create_toy('$table', 3)|); $psql_b->query_safe( qq|SELECT buffer_call_start_io($buf_id, for_input=>true, wait=>true)| ); @@ -1648,16 +1645,16 @@ INSERT INTO tmp_ok SELECT generate_series(1, 5000); qq|SELECT buffer_call_terminate_io($buf_id, for_input=>true, succeed=>false, io_error=>false, release_aio=>false)| ); # Because no IO wref was assigned, block 3 should not report foreign IO - pump_until($psql_a->{run}, $psql_a->{timeout}, \$psql_a->{stdout}, - qr/0\|1\|t\|f\|2\n2\|3\|t\|f\|3/); - ok(1, + like( + $psql_a->wait_for_async_pattern(qr/0\|1\|t\|f\|2\n2\|3\|t\|f\|3/), + qr/0\|1\|t\|f\|2\n2\|3\|t\|f\|3/, "$io_method: $persistency: IO was split due to concurrent failed IO" ); # Same as before, except the concurrent IO succeeds this time $psql_a->query_safe(qq|SELECT evict_rel('$table')|); $buf_id = - $psql_b->query_safe(qq|SELECT buffer_create_toy('$table', 3)|); + $psql_b->query_oneval(qq|SELECT buffer_create_toy('$table', 3)|); $psql_b->query_safe( qq|SELECT buffer_call_start_io($buf_id, for_input=>true, wait=>true)| ); @@ -1674,9 +1671,10 @@ INSERT INTO tmp_ok SELECT generate_series(1, 5000); qq|SELECT buffer_call_terminate_io($buf_id, for_input=>true, succeed=>true, io_error=>false, release_aio=>false)| ); # Because no IO wref was assigned, block 3 should not report foreign IO - pump_until($psql_a->{run}, $psql_a->{timeout}, \$psql_a->{stdout}, - qr/0\|1\|t\|f\|2\n2\|3\|f\|f\|1\n3\|4\|t\|f\|2/); - ok(1, + like( + $psql_a->wait_for_async_pattern( + qr/0\|1\|t\|f\|2\n2\|3\|f\|f\|1\n3\|4\|t\|f\|2/), + qr/0\|1\|t\|f\|2\n2\|3\|f\|f\|1\n3\|4\|t\|f\|2/, "$io_method: $persistency: IO was split due to concurrent successful IO" ); } @@ -1692,9 +1690,9 @@ sub test_read_buffers_inject my $io_method = shift; my $node = shift; - my $psql_a = $node->background_psql('postgres', on_error_stop => 0); - my $psql_b = $node->background_psql('postgres', on_error_stop => 0); - my $psql_c = $node->background_psql('postgres', on_error_stop => 0); + my $psql_a = PostgreSQL::Test::Session->new(node => $node); + my $psql_b = PostgreSQL::Test::Session->new(node => $node); + my $psql_c = PostgreSQL::Test::Session->new(node => $node); my $expected; @@ -1763,12 +1761,13 @@ sub test_read_buffers_inject # return for something with misses in sync mode. $expected = qr/0\|1\|t\|f\|4/; } - pump_until($psql_a->{run}, $psql_a->{timeout}, \$psql_a->{stdout}, - $expected); - ok(1, + like($psql_a->wait_for_async_pattern($expected), $expected, "$io_method: $persistency: read 1-3, blocked on in-progress 1, see expected result" ); - $psql_a->{stdout} = ''; + + # B's low-level read has completed now that C released it; drain its + # result before B is reused below. + $psql_b->wait_for_completion; ### @@ -1829,12 +1828,12 @@ read_buffers('$table', 0, 4)|, # return for something with misses in sync mode. $expected = qr/0\|0\|t\|f\|4/; } - pump_until($psql_a->{run}, $psql_a->{timeout}, \$psql_a->{stdout}, - $expected); - ok(1, + like($psql_a->wait_for_async_pattern($expected), $expected, "$io_method: $persistency: read 0-3, blocked on in-progress 2+3, see expected result" ); - $psql_a->{stdout} = ''; + + # Drain B's now-completed low-level read before closing. + $psql_b->wait_for_completion; $psql_a->quit(); diff --git a/src/test/modules/test_aio/t/004_read_stream.pl b/src/test/modules/test_aio/t/004_read_stream.pl index 32311c07ac0..782b62c452f 100644 --- a/src/test/modules/test_aio/t/004_read_stream.pl +++ b/src/test/modules/test_aio/t/004_read_stream.pl @@ -5,6 +5,7 @@ use warnings FATAL => 'all'; use PostgreSQL::Test::Cluster; use PostgreSQL::Test::Utils; +use PostgreSQL::Test::Session; use Test::More; use FindBin; @@ -60,7 +61,7 @@ sub test_repeated_blocks my $io_method = shift; my $node = shift; - my $psql = $node->background_psql('postgres', on_error_stop => 0); + my $psql = PostgreSQL::Test::Session->new(node => $node); # Preventing larger reads makes testing easier $psql->query_safe(qq/SET io_combine_limit = 1/); @@ -111,7 +112,7 @@ sub test_repeated_blocks ARRAY[0, 2, 2, 4, 4]);/); ok(1, "$io_method: temp stream hitting the same block repeatedly"); - $psql->quit(); + $psql->close(); } @@ -120,10 +121,10 @@ sub test_inject_foreign my $io_method = shift; my $node = shift; - my $psql_a = $node->background_psql('postgres', on_error_stop => 0); - my $psql_b = $node->background_psql('postgres', on_error_stop => 0); + my $psql_a = PostgreSQL::Test::Session->new(node => $node); + my $psql_b = PostgreSQL::Test::Session->new(node => $node); - my $pid_a = $psql_a->query_safe(qq/SELECT pg_backend_pid();/); + my $pid_a = $psql_a->query_oneval(qq/SELECT pg_backend_pid();/); ### @@ -136,9 +137,8 @@ sub test_inject_foreign qq/SELECT inj_io_completion_wait(pid=>pg_backend_pid(), relfilenode=>pg_relation_filenode('largeish'));/); - $psql_b->{stdin} .= qq/SELECT read_rel_block_ll('largeish', - blockno=>5, nblocks=>1);\n/; - $psql_b->{run}->pump_nb(); + $psql_b->do_async(qq/SELECT read_rel_block_ll('largeish', + blockno=>5, nblocks=>1);/); $node->poll_query_until( 'postgres', qq/SELECT wait_event FROM pg_stat_activity @@ -147,9 +147,8 @@ sub test_inject_foreign # Block 5 is undergoing IO in session b, so session a will move on to start # a new IO for block 7. - $psql_a->{stdin} .= qq/SELECT array_agg(blocknum) FROM - read_stream_for_blocks('largeish', ARRAY[0, 2, 5, 7]);\n/; - $psql_a->{run}->pump_nb(); + $psql_a->do_async(qq/SELECT array_agg(blocknum) FROM + read_stream_for_blocks('largeish', ARRAY[0, 2, 5, 7]);/); $node->poll_query_until('postgres', qq(SELECT wait_event FROM pg_stat_activity WHERE pid = $pid_a), @@ -157,10 +156,10 @@ sub test_inject_foreign $node->safe_psql('postgres', qq/SELECT inj_io_completion_continue()/); - pump_until( - $psql_a->{run}, $psql_a->{timeout}, - \$psql_a->{stdout}, qr/\{0,2,5,7\}/); - $psql_a->{stdout} = ''; + $psql_a->wait_for_async_pattern(qr/\{0,2,5,7\}/); + + # Drain session b's now-completed low-level read before reusing it. + $psql_b->wait_for_completion; ok(1, qq/$io_method: read stream encounters succeeding IO by another backend/ @@ -181,9 +180,8 @@ sub test_inject_foreign pid=>pg_backend_pid(), relfilenode=>pg_relation_filenode('largeish'));/); - $psql_b->{stdin} .= qq/SELECT read_rel_block_ll('largeish', - blockno=>5, nblocks=>1);\n/; - $psql_b->{run}->pump_nb(); + $psql_b->do_async(qq/SELECT read_rel_block_ll('largeish', + blockno=>5, nblocks=>1);/); $node->poll_query_until( 'postgres', @@ -191,9 +189,8 @@ sub test_inject_foreign WHERE wait_event = 'completion_wait';/, 'completion_wait'); - $psql_a->{stdin} .= qq/SELECT array_agg(blocknum) FROM - read_stream_for_blocks('largeish', ARRAY[0, 2, 5, 7]);\n/; - $psql_a->{run}->pump_nb(); + $psql_a->do_async(qq/SELECT array_agg(blocknum) FROM + read_stream_for_blocks('largeish', ARRAY[0, 2, 5, 7]);/); $node->poll_query_until('postgres', qq(SELECT wait_event FROM pg_stat_activity WHERE pid = $pid_a), @@ -201,15 +198,13 @@ sub test_inject_foreign $node->safe_psql('postgres', qq/SELECT inj_io_completion_continue()/); - pump_until( - $psql_a->{run}, $psql_a->{timeout}, - \$psql_a->{stdout}, qr/\{0,2,5,7\}/); - $psql_a->{stdout} = ''; + $psql_a->wait_for_async_pattern(qr/\{0,2,5,7\}/); - pump_until($psql_b->{run}, $psql_b->{timeout}, \$psql_b->{stderr}, - qr/ERROR.*could not read blocks 5\.\.5/); - ok(1, "$io_method: injected error occurred"); - $psql_b->{stderr} = ''; + # Session b's low-level read hits the injected error. + my $res_b = $psql_b->get_async_result(); + like($res_b->{error_message}, qr/ERROR.*could not read blocks 5\.\.5/, + "$io_method: injected error occurred"); + $psql_b->clear_stderr(); $psql_b->query_safe(qq/SELECT inj_io_short_read_detach();/); ok(1, @@ -226,9 +221,8 @@ sub test_inject_foreign qq/SELECT inj_io_completion_wait(pid=>pg_backend_pid(), relfilenode=>pg_relation_filenode('largeish'));/); - $psql_b->{stdin} .= qq/SELECT read_rel_block_ll('largeish', - blockno=>2, nblocks=>3);\n/; - $psql_b->{run}->pump_nb(); + $psql_b->do_async(qq/SELECT read_rel_block_ll('largeish', + blockno=>2, nblocks=>3);/); $node->poll_query_until( 'postgres', @@ -237,9 +231,8 @@ sub test_inject_foreign 'completion_wait'); # Blocks 2 and 4 are undergoing IO initiated by session b - $psql_a->{stdin} .= qq/SELECT array_agg(blocknum) FROM - read_stream_for_blocks('largeish', ARRAY[0, 2, 4]);\n/; - $psql_a->{run}->pump_nb(); + $psql_a->do_async(qq/SELECT array_agg(blocknum) FROM + read_stream_for_blocks('largeish', ARRAY[0, 2, 4]);/); $node->poll_query_until('postgres', qq(SELECT wait_event FROM pg_stat_activity WHERE pid = $pid_a), @@ -247,15 +240,15 @@ sub test_inject_foreign $node->safe_psql('postgres', qq/SELECT inj_io_completion_continue()/); - pump_until( - $psql_a->{run}, $psql_a->{timeout}, - \$psql_a->{stdout}, qr/\{0,2,4\}/); - $psql_a->{stdout} = ''; + $psql_a->wait_for_async_pattern(qr/\{0,2,4\}/); + + # Drain session b's now-completed low-level read. + $psql_b->wait_for_completion; ok(1, qq/$io_method: read stream encounters two buffer read in one IO/); - $psql_a->quit(); - $psql_b->quit(); + $psql_a->close(); + $psql_b->close(); } diff --git a/src/test/modules/test_checksums/t/002_restarts.pl b/src/test/modules/test_checksums/t/002_restarts.pl index 1aa2c0c65e5..cd9a7314c8a 100644 --- a/src/test/modules/test_checksums/t/002_restarts.pl +++ b/src/test/modules/test_checksums/t/002_restarts.pl @@ -8,6 +8,7 @@ use warnings FATAL => 'all'; use PostgreSQL::Test::Cluster; use PostgreSQL::Test::Utils; +use PostgreSQL::Test::Session; use Test::More; use FindBin; @@ -44,8 +45,8 @@ SKIP: # # This is a similar test to the synthetic variant in 005_injection.pl # which fakes this scenario. - my $bsession = $node->background_psql('postgres'); - $bsession->query_safe('CREATE TEMPORARY TABLE tt (a integer);'); + my $bsession = PostgreSQL::Test::Session->new(node => $node); + $bsession->do('CREATE TEMPORARY TABLE tt (a integer);'); # In another session, make sure we can see the blocking temp table but # start processing anyways and check that we are blocked with a proper @@ -85,7 +86,7 @@ SKIP: # session first since the brief period between closing and stopping might # be enough for checksums to get enabled. $node->stop; - $bsession->quit; + $bsession->close; $node->start; # Ensure the checksums aren't enabled across the restart. This leaves the diff --git a/src/test/modules/test_checksums/t/003_standby_restarts.pl b/src/test/modules/test_checksums/t/003_standby_restarts.pl index bb35ed0b325..67d4e76e7b5 100644 --- a/src/test/modules/test_checksums/t/003_standby_restarts.pl +++ b/src/test/modules/test_checksums/t/003_standby_restarts.pl @@ -7,6 +7,7 @@ use strict; use warnings FATAL => 'all'; use PostgreSQL::Test::Cluster; use PostgreSQL::Test::Utils; +use PostgreSQL::Test::Session; use Test::More; use FindBin; @@ -255,11 +256,11 @@ $node_primary->wait_for_catchup($node_standby, 'replay'); # Open a background psql connection on the primary and inject a barrier to # block progress on to keep the state from advancing past inprogress-on -my $node_primary_bpsql = $node_primary->background_psql('postgres'); -$node_primary_bpsql->query_safe('CREATE TEMPORARY TABLE tt (a integer);'); +my $node_primary_bpsql = PostgreSQL::Test::Session->new(node => $node_primary); +$node_primary_bpsql->do('CREATE TEMPORARY TABLE tt (a integer);'); # Also open a background psql connection to the standby to make sure we have # an active backend during promotion. -my $node_standby_bpsql = $node_standby->background_psql('postgres'); +my $node_standby_bpsql = PostgreSQL::Test::Session->new(node => $node_standby); # Start to enable checksums and wait until both primary and standby have moved # to the inprogress-on state. Processing will block here as the temporary rel @@ -281,7 +282,11 @@ $result = $node_standby_bpsql->query_safe("SHOW data_checksums;"); is($result, 'off', 'ensure checksums are set to off after promotion during inprogress-on'); -$node_standby_bpsql->quit; +# The primary's session was kept open only to hold the blocking temp table; +# close it explicitly (its backend is already gone after the crash) so it is +# not left to be torn down at global destruction. +$node_primary_bpsql->close; +$node_standby_bpsql->close; $node_standby->stop; done_testing(); diff --git a/src/test/modules/test_checksums/t/004_offline.pl b/src/test/modules/test_checksums/t/004_offline.pl index 73c279e75e0..be6b6b6e4c6 100644 --- a/src/test/modules/test_checksums/t/004_offline.pl +++ b/src/test/modules/test_checksums/t/004_offline.pl @@ -8,6 +8,7 @@ use warnings FATAL => 'all'; use PostgreSQL::Test::Cluster; use PostgreSQL::Test::Utils; +use PostgreSQL::Test::Session; use Test::More; use FindBin; @@ -53,8 +54,8 @@ test_checksum_state($node, 'off'); # can accomplish this by setting up an interactive psql process which keeps the # temporary table created as we enable checksums in another psql process. -my $bsession = $node->background_psql('postgres'); -$bsession->query_safe('CREATE TEMPORARY TABLE tt (a integer);'); +my $bsession = PostgreSQL::Test::Session->new(node => $node); +$bsession->do('CREATE TEMPORARY TABLE tt (a integer);'); # In another session, make sure we can see the blocking temp table but start # processing anyways and check that we are blocked with a proper wait event. @@ -70,7 +71,7 @@ enable_data_checksums($node, wait => 'inprogress-on'); # Stop the cluster before exiting the background session since otherwise # checksums might have time to get enabled before shutting down the cluster. $node->stop('fast'); -$bsession->quit; +$bsession->close; $node->checksum_enable_offline; $node->start; diff --git a/src/test/modules/test_misc/t/005_timeouts.pl b/src/test/modules/test_misc/t/005_timeouts.pl index c16b7dbf5e6..d64a787a9ab 100644 --- a/src/test/modules/test_misc/t/005_timeouts.pl +++ b/src/test/modules/test_misc/t/005_timeouts.pl @@ -6,6 +6,7 @@ use warnings FATAL => 'all'; use locale; use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Session; use PostgreSQL::Test::Utils; use Time::HiRes qw(usleep); use Test::More; @@ -42,24 +43,16 @@ $node->safe_psql('postgres', 'CREATE EXTENSION injection_points;'); $node->safe_psql('postgres', "SELECT injection_points_attach('transaction-timeout', 'wait');"); -my $psql_session = $node->background_psql('postgres'); +my $psql_session = PostgreSQL::Test::Session->new(node => $node); -# The following query will generate a stream of SELECT 1 queries. This is done -# so to exercise transaction timeout in the presence of short queries. -# Note: the interval value is parsed with locale-aware strtod() -$psql_session->query_until( - qr/starting_bg_psql/, - sprintf( - q(\echo starting_bg_psql - SET transaction_timeout to '10ms'; - BEGIN; - SELECT 1 \watch %g - \q -), 0.001)); +$psql_session->do("SET transaction_timeout to '10ms';"); + +$psql_session->do_async("BEGIN; DO ' begin loop PERFORM pg_sleep(0.001); end loop; end ';"); # Wait until the backend enters the timeout injection point. Will get an error # here if anything goes wrong. $node->wait_for_event('client backend', 'transaction-timeout'); +pass("got transaction timeout event"); my $log_offset = -s $node->logfile; @@ -70,11 +63,9 @@ $node->safe_psql('postgres', # Check that the timeout was logged. $node->wait_for_log('terminating connection due to transaction timeout', $log_offset); +pass("got transaction timeout log"); -# If we send \q with $psql_session->quit the command can be sent to the session -# already closed. So \q is in initial script, here we only finish IPC::Run. -$psql_session->{run}->finish; - +$psql_session->close; # # 2. Test of the idle in transaction timeout @@ -85,10 +76,8 @@ $node->safe_psql('postgres', ); # We begin a transaction and the hand on the line -$psql_session = $node->background_psql('postgres'); -$psql_session->query_until( - qr/starting_bg_psql/, q( - \echo starting_bg_psql +$psql_session->reconnect; +$psql_session->do(q( SET idle_in_transaction_session_timeout to '10ms'; BEGIN; )); @@ -96,6 +85,7 @@ $psql_session->query_until( # Wait until the backend enters the timeout injection point. $node->wait_for_event('client backend', 'idle-in-transaction-session-timeout'); +pass("got idle in transaction timeout event"); $log_offset = -s $node->logfile; @@ -106,8 +96,9 @@ $node->safe_psql('postgres', # Check that the timeout was logged. $node->wait_for_log( 'terminating connection due to idle-in-transaction timeout', $log_offset); +pass("got idle in transaction timeout log"); -ok($psql_session->quit); +$psql_session->close; # @@ -117,15 +108,14 @@ $node->safe_psql('postgres', "SELECT injection_points_attach('idle-session-timeout', 'wait');"); # We just initialize the GUC and wait. No transaction is required. -$psql_session = $node->background_psql('postgres'); -$psql_session->query_until( - qr/starting_bg_psql/, q( - \echo starting_bg_psql +$psql_session->reconnect; +$psql_session->do(q( SET idle_session_timeout to '10ms'; )); # Wait until the backend enters the timeout injection point. $node->wait_for_event('client backend', 'idle-session-timeout'); +pass("got idle session timeout event"); $log_offset = -s $node->logfile; @@ -136,7 +126,8 @@ $node->safe_psql('postgres', # Check that the timeout was logged. $node->wait_for_log('terminating connection due to idle-session timeout', $log_offset); +pass("got idle sesion tiemout log"); -ok($psql_session->quit); +$psql_session->close; done_testing(); diff --git a/src/test/modules/test_misc/t/007_catcache_inval.pl b/src/test/modules/test_misc/t/007_catcache_inval.pl index 424556261c9..e3b3fd8bae9 100644 --- a/src/test/modules/test_misc/t/007_catcache_inval.pl +++ b/src/test/modules/test_misc/t/007_catcache_inval.pl @@ -46,12 +46,12 @@ $node->safe_psql( CREATE FUNCTION foofunc(dummy integer) RETURNS integer AS \$\$ SELECT 1; /* $longtext */ \$\$ LANGUAGE SQL ]); -my $psql_session = $node->background_psql('postgres'); -my $psql_session2 = $node->background_psql('postgres'); +my $psql_session = PostgreSQL::Test::Session->new(node => $node); +my $psql_session2 = PostgreSQL::Test::Session->new(node => $node); # Set injection point in the session, to pause while populating the # catcache list -$psql_session->query_safe( +$psql_session->do( qq[ SELECT injection_points_set_local(); SELECT injection_points_attach('catcache-list-miss-systable-scan-started', 'wait'); @@ -59,10 +59,9 @@ $psql_session->query_safe( # This pauses on the injection point while populating catcache list # for functions with name "foofunc" -$psql_session->query_until( - qr/starting_bg_psql/, q( - \echo starting_bg_psql - SELECT foofunc(1); +$psql_session->do_async( + q( + SELECT foofunc(1); )); # While the first session is building the catcache list, create a new @@ -83,16 +82,19 @@ $node->safe_psql( # trying to exercise here.) # # The "SELECT foofunc(1)" query will now finish. -$psql_session2->query_safe( +$psql_session2->do( qq[ SELECT injection_points_wakeup('catcache-list-miss-systable-scan-started'); SELECT injection_points_detach('catcache-list-miss-systable-scan-started'); ]); # Test that the new function is visible to the session. -$psql_session->query_safe("SELECT foofunc();"); +$psql_session->wait_for_completion; +my $res = $psql_session->query("SELECT foofunc();"); -ok($psql_session->quit); -ok($psql_session2->quit); +is($res->{status}, 2, "got TUPLES_OK"); + +$psql_session->close; +$psql_session2->close; done_testing(); diff --git a/src/test/modules/test_misc/t/010_index_concurrently_upsert.pl b/src/test/modules/test_misc/t/010_index_concurrently_upsert.pl index 50a0e7db8f7..a7359cb0624 100644 --- a/src/test/modules/test_misc/t/010_index_concurrently_upsert.pl +++ b/src/test/modules/test_misc/t/010_index_concurrently_upsert.pl @@ -13,6 +13,7 @@ use strict; use warnings FATAL => 'all'; use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Session; use PostgreSQL::Test::Utils; use Test::More; use Time::HiRes qw(usleep); @@ -51,47 +52,38 @@ ALTER TABLE test.tblexpr SET (parallel_workers=0); ############################################################################ note('Test: REINDEX CONCURRENTLY + UPSERT (wakeup at set-dead phase)'); -# Create sessions with on_error_stop => 0 so psql doesn't exit on SQL errors. -# This allows us to collect stderr and detect errors after the test completes. -my $s1 = $node->background_psql('postgres', on_error_stop => 0); -my $s2 = $node->background_psql('postgres', on_error_stop => 0); -my $s3 = $node->background_psql('postgres', on_error_stop => 0); +# Create sessions for concurrent operations +my $s1 = PostgreSQL::Test::Session->new(node => $node); +my $s2 = PostgreSQL::Test::Session->new(node => $node); +my $s3 = PostgreSQL::Test::Session->new(node => $node); # Setup injection points for each session -$s1->query_safe( +$s1->do( q[ SELECT injection_points_set_local(); SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait'); ]); -$s2->query_safe( +$s2->do( q[ SELECT injection_points_set_local(); SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait'); ]); -$s3->query_safe( +$s3->do( q[ SELECT injection_points_set_local(); SELECT injection_points_attach('reindex-relation-concurrently-before-set-dead', 'wait'); ]); # s3 starts REINDEX (will block on reindex-relation-concurrently-before-set-dead) -$s3->query_until( - qr/starting_reindex/, q[ -\echo starting_reindex -REINDEX INDEX CONCURRENTLY test.tblpk_pkey; -]); +$s3->do_async(q[REINDEX INDEX CONCURRENTLY test.tblpk_pkey;]); # Wait for s3 to hit injection point ok_injection_point($node, 'reindex-relation-concurrently-before-set-dead'); # s1 starts UPSERT (will block on check-exclusion-or-unique-constraint-no-conflict) -$s1->query_until( - qr/starting_upsert_s1/, q[ -\echo starting_upsert_s1 -INSERT INTO test.tblpk VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now(); -]); +$s1->do_async(q[INSERT INTO test.tblpk VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();]); # Wait for s1 to hit injection point ok_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict'); @@ -101,11 +93,7 @@ wakeup_injection_point($node, 'reindex-relation-concurrently-before-set-dead'); # s2 starts UPSERT (will block on exec-insert-before-insert-speculative) -$s2->query_until( - qr/starting_upsert_s2/, q[ -\echo starting_upsert_s2 -INSERT INTO test.tblpk VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now(); -]); +$s2->do_async(q[INSERT INTO test.tblpk VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();]); # Wait for s2 to hit injection point ok_injection_point($node, 'exec-insert-before-insert-speculative'); @@ -125,51 +113,39 @@ $node->safe_psql('postgres', 'TRUNCATE TABLE test.tblpk'); ############################################################################ note('Test: REINDEX CONCURRENTLY + UPSERT (wakeup at swap phase)'); -$s1 = $node->background_psql('postgres', on_error_stop => 0); -$s2 = $node->background_psql('postgres', on_error_stop => 0); -$s3 = $node->background_psql('postgres', on_error_stop => 0); +$s1 = PostgreSQL::Test::Session->new(node => $node); +$s2 = PostgreSQL::Test::Session->new(node => $node); +$s3 = PostgreSQL::Test::Session->new(node => $node); -$s1->query_safe( +$s1->do( q[ SELECT injection_points_set_local(); SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait'); ]); -$s2->query_safe( +$s2->do( q[ SELECT injection_points_set_local(); SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait'); ]); -$s3->query_safe( +$s3->do( q[ SELECT injection_points_set_local(); SELECT injection_points_attach('reindex-relation-concurrently-before-swap', 'wait'); ]); -$s3->query_until( - qr/starting_reindex/, q[ -\echo starting_reindex -REINDEX INDEX CONCURRENTLY test.tblpk_pkey; -]); +$s3->do_async(q[REINDEX INDEX CONCURRENTLY test.tblpk_pkey;]); ok_injection_point($node, 'reindex-relation-concurrently-before-swap'); -$s1->query_until( - qr/starting_upsert_s1/, q[ -\echo starting_upsert_s1 -INSERT INTO test.tblpk VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now(); -]); +$s1->do_async(q[INSERT INTO test.tblpk VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();]); ok_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict'); wakeup_injection_point($node, 'reindex-relation-concurrently-before-swap'); -$s2->query_until( - qr/starting_upsert_s2/, q[ -\echo starting_upsert_s2 -INSERT INTO test.tblpk VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now(); -]); +$s2->do_async(q[INSERT INTO test.tblpk VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();]); ok_injection_point($node, 'exec-insert-before-insert-speculative'); @@ -184,50 +160,38 @@ $node->safe_psql('postgres', 'TRUNCATE TABLE test.tblpk'); ############################################################################ note('Test: REINDEX CONCURRENTLY + UPSERT (s1 wakes before reindex)'); -$s1 = $node->background_psql('postgres', on_error_stop => 0); -$s2 = $node->background_psql('postgres', on_error_stop => 0); -$s3 = $node->background_psql('postgres', on_error_stop => 0); +$s1 = PostgreSQL::Test::Session->new(node => $node); +$s2 = PostgreSQL::Test::Session->new(node => $node); +$s3 = PostgreSQL::Test::Session->new(node => $node); -$s1->query_safe( +$s1->do( q[ SELECT injection_points_set_local(); SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait'); ]); -$s2->query_safe( +$s2->do( q[ SELECT injection_points_set_local(); SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait'); ]); -$s3->query_safe( +$s3->do( q[ SELECT injection_points_set_local(); SELECT injection_points_attach('reindex-relation-concurrently-before-set-dead', 'wait'); ]); -$s3->query_until( - qr/starting_reindex/, q[ -\echo starting_reindex -REINDEX INDEX CONCURRENTLY test.tblpk_pkey; -]); +$s3->do_async(q[REINDEX INDEX CONCURRENTLY test.tblpk_pkey;]); ok_injection_point($node, 'reindex-relation-concurrently-before-set-dead'); -$s1->query_until( - qr/starting_upsert_s1/, q[ -\echo starting_upsert_s1 -INSERT INTO test.tblpk VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now(); -]); +$s1->do_async(q[INSERT INTO test.tblpk VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();]); ok_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict'); # Start s2 BEFORE waking reindex (key difference from permutation 1) -$s2->query_until( - qr/starting_upsert_s2/, q[ -\echo starting_upsert_s2 -INSERT INTO test.tblpk VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now(); -]); +$s2->do_async(q[INSERT INTO test.tblpk VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();]); ok_injection_point($node, 'exec-insert-before-insert-speculative'); @@ -245,52 +209,40 @@ $node->safe_psql('postgres', 'TRUNCATE TABLE test.tblpk'); ############################################################################ note('Test: REINDEX + UPSERT ON CONSTRAINT (set-dead phase)'); -$s1 = $node->background_psql('postgres', on_error_stop => 0); -$s2 = $node->background_psql('postgres', on_error_stop => 0); -$s3 = $node->background_psql('postgres', on_error_stop => 0); +$s1 = PostgreSQL::Test::Session->new(node => $node); +$s2 = PostgreSQL::Test::Session->new(node => $node); +$s3 = PostgreSQL::Test::Session->new(node => $node); -$s1->query_safe( +$s1->do( q[ SELECT injection_points_set_local(); SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait'); ]); -$s2->query_safe( +$s2->do( q[ SELECT injection_points_set_local(); SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait'); ]); -$s3->query_safe( +$s3->do( q[ SELECT injection_points_set_local(); SELECT injection_points_attach('reindex-relation-concurrently-before-set-dead', 'wait'); ]); -$s3->query_until( - qr/starting_reindex/, q[ -\echo starting_reindex -REINDEX INDEX CONCURRENTLY test.tblpk_pkey; -]); +$s3->do_async(q[REINDEX INDEX CONCURRENTLY test.tblpk_pkey;]); ok_injection_point($node, 'reindex-relation-concurrently-before-set-dead'); -$s1->query_until( - qr/starting_upsert_s1/, q[ -\echo starting_upsert_s1 -INSERT INTO test.tblpk VALUES (13, now()) ON CONFLICT ON CONSTRAINT tblpk_pkey DO UPDATE SET updated_at = now(); -]); +$s1->do_async(q[INSERT INTO test.tblpk VALUES (13, now()) ON CONFLICT ON CONSTRAINT tblpk_pkey DO UPDATE SET updated_at = now();]); ok_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict'); wakeup_injection_point($node, 'reindex-relation-concurrently-before-set-dead'); -$s2->query_until( - qr/starting_upsert_s2/, q[ -\echo starting_upsert_s2 -INSERT INTO test.tblpk VALUES (13, now()) ON CONFLICT ON CONSTRAINT tblpk_pkey DO UPDATE SET updated_at = now(); -]); +$s2->do_async(q[INSERT INTO test.tblpk VALUES (13, now()) ON CONFLICT ON CONSTRAINT tblpk_pkey DO UPDATE SET updated_at = now();]); ok_injection_point($node, 'exec-insert-before-insert-speculative'); @@ -305,51 +257,39 @@ $node->safe_psql('postgres', 'TRUNCATE TABLE test.tblpk'); ############################################################################ note('Test: REINDEX + UPSERT ON CONSTRAINT (swap phase)'); -$s1 = $node->background_psql('postgres', on_error_stop => 0); -$s2 = $node->background_psql('postgres', on_error_stop => 0); -$s3 = $node->background_psql('postgres', on_error_stop => 0); +$s1 = PostgreSQL::Test::Session->new(node => $node); +$s2 = PostgreSQL::Test::Session->new(node => $node); +$s3 = PostgreSQL::Test::Session->new(node => $node); -$s1->query_safe( +$s1->do( q[ SELECT injection_points_set_local(); SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait'); ]); -$s2->query_safe( +$s2->do( q[ SELECT injection_points_set_local(); SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait'); ]); -$s3->query_safe( +$s3->do( q[ SELECT injection_points_set_local(); SELECT injection_points_attach('reindex-relation-concurrently-before-swap', 'wait'); ]); -$s3->query_until( - qr/starting_reindex/, q[ -\echo starting_reindex -REINDEX INDEX CONCURRENTLY test.tblpk_pkey; -]); +$s3->do_async(q[REINDEX INDEX CONCURRENTLY test.tblpk_pkey;]); ok_injection_point($node, 'reindex-relation-concurrently-before-swap'); -$s1->query_until( - qr/starting_upsert_s1/, q[ -\echo starting_upsert_s1 -INSERT INTO test.tblpk VALUES (13, now()) ON CONFLICT ON CONSTRAINT tblpk_pkey DO UPDATE SET updated_at = now(); -]); +$s1->do_async(q[INSERT INTO test.tblpk VALUES (13, now()) ON CONFLICT ON CONSTRAINT tblpk_pkey DO UPDATE SET updated_at = now();]); ok_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict'); wakeup_injection_point($node, 'reindex-relation-concurrently-before-swap'); -$s2->query_until( - qr/starting_upsert_s2/, q[ -\echo starting_upsert_s2 -INSERT INTO test.tblpk VALUES (13, now()) ON CONFLICT ON CONSTRAINT tblpk_pkey DO UPDATE SET updated_at = now(); -]); +$s2->do_async(q[INSERT INTO test.tblpk VALUES (13, now()) ON CONFLICT ON CONSTRAINT tblpk_pkey DO UPDATE SET updated_at = now();]); ok_injection_point($node, 'exec-insert-before-insert-speculative'); @@ -364,50 +304,38 @@ $node->safe_psql('postgres', 'TRUNCATE TABLE test.tblpk'); ############################################################################ note('Test: REINDEX + UPSERT ON CONSTRAINT (s1 wakes before reindex)'); -$s1 = $node->background_psql('postgres', on_error_stop => 0); -$s2 = $node->background_psql('postgres', on_error_stop => 0); -$s3 = $node->background_psql('postgres', on_error_stop => 0); +$s1 = PostgreSQL::Test::Session->new(node => $node); +$s2 = PostgreSQL::Test::Session->new(node => $node); +$s3 = PostgreSQL::Test::Session->new(node => $node); -$s1->query_safe( +$s1->do( q[ SELECT injection_points_set_local(); SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait'); ]); -$s2->query_safe( +$s2->do( q[ SELECT injection_points_set_local(); SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait'); ]); -$s3->query_safe( +$s3->do( q[ SELECT injection_points_set_local(); SELECT injection_points_attach('reindex-relation-concurrently-before-set-dead', 'wait'); ]); -$s3->query_until( - qr/starting_reindex/, q[ -\echo starting_reindex -REINDEX INDEX CONCURRENTLY test.tblpk_pkey; -]); +$s3->do_async(q[REINDEX INDEX CONCURRENTLY test.tblpk_pkey;]); ok_injection_point($node, 'reindex-relation-concurrently-before-set-dead'); -$s1->query_until( - qr/starting_upsert_s1/, q[ -\echo starting_upsert_s1 -INSERT INTO test.tblpk VALUES (13, now()) ON CONFLICT ON CONSTRAINT tblpk_pkey DO UPDATE SET updated_at = now(); -]); +$s1->do_async(q[INSERT INTO test.tblpk VALUES (13, now()) ON CONFLICT ON CONSTRAINT tblpk_pkey DO UPDATE SET updated_at = now();]); ok_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict'); # Start s2 BEFORE waking reindex -$s2->query_until( - qr/starting_upsert_s2/, q[ -\echo starting_upsert_s2 -INSERT INTO test.tblpk VALUES (13, now()) ON CONFLICT ON CONSTRAINT tblpk_pkey DO UPDATE SET updated_at = now(); -]); +$s2->do_async(q[INSERT INTO test.tblpk VALUES (13, now()) ON CONFLICT ON CONSTRAINT tblpk_pkey DO UPDATE SET updated_at = now();]); ok_injection_point($node, 'exec-insert-before-insert-speculative'); @@ -425,52 +353,40 @@ $node->safe_psql('postgres', 'TRUNCATE TABLE test.tblpk'); ############################################################################ note('Test: REINDEX on partitioned table (set-dead phase)'); -$s1 = $node->background_psql('postgres', on_error_stop => 0); -$s2 = $node->background_psql('postgres', on_error_stop => 0); -$s3 = $node->background_psql('postgres', on_error_stop => 0); +$s1 = PostgreSQL::Test::Session->new(node => $node); +$s2 = PostgreSQL::Test::Session->new(node => $node); +$s3 = PostgreSQL::Test::Session->new(node => $node); -$s1->query_safe( +$s1->do( q[ SELECT injection_points_set_local(); SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait'); ]); -$s2->query_safe( +$s2->do( q[ SELECT injection_points_set_local(); SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait'); ]); -$s3->query_safe( +$s3->do( q[ SELECT injection_points_set_local(); SELECT injection_points_attach('reindex-relation-concurrently-before-set-dead', 'wait'); ]); -$s3->query_until( - qr/starting_reindex/, q[ -\echo starting_reindex -REINDEX INDEX CONCURRENTLY test.tbl_partition_pkey; -]); +$s3->do_async(q[REINDEX INDEX CONCURRENTLY test.tbl_partition_pkey;]); ok_injection_point($node, 'reindex-relation-concurrently-before-set-dead'); -$s1->query_until( - qr/starting_upsert_s1/, q[ -\echo starting_upsert_s1 -INSERT INTO test.tblparted VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now(); -]); +$s1->do_async(q[INSERT INTO test.tblparted VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();]); ok_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict'); wakeup_injection_point($node, 'reindex-relation-concurrently-before-set-dead'); -$s2->query_until( - qr/starting_upsert_s2/, q[ -\echo starting_upsert_s2 -INSERT INTO test.tblparted VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now(); -]); +$s2->do_async(q[INSERT INTO test.tblparted VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();]); ok_injection_point($node, 'exec-insert-before-insert-speculative'); @@ -485,51 +401,39 @@ $node->safe_psql('postgres', 'TRUNCATE TABLE test.tblparted'); ############################################################################ note('Test: REINDEX on partitioned table (swap phase)'); -$s1 = $node->background_psql('postgres', on_error_stop => 0); -$s2 = $node->background_psql('postgres', on_error_stop => 0); -$s3 = $node->background_psql('postgres', on_error_stop => 0); +$s1 = PostgreSQL::Test::Session->new(node => $node); +$s2 = PostgreSQL::Test::Session->new(node => $node); +$s3 = PostgreSQL::Test::Session->new(node => $node); -$s1->query_safe( +$s1->do( q[ SELECT injection_points_set_local(); SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait'); ]); -$s2->query_safe( +$s2->do( q[ SELECT injection_points_set_local(); SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait'); ]); -$s3->query_safe( +$s3->do( q[ SELECT injection_points_set_local(); SELECT injection_points_attach('reindex-relation-concurrently-before-swap', 'wait'); ]); -$s3->query_until( - qr/starting_reindex/, q[ -\echo starting_reindex -REINDEX INDEX CONCURRENTLY test.tbl_partition_pkey; -]); +$s3->do_async(q[REINDEX INDEX CONCURRENTLY test.tbl_partition_pkey;]); ok_injection_point($node, 'reindex-relation-concurrently-before-swap'); -$s1->query_until( - qr/starting_upsert_s1/, q[ -\echo starting_upsert_s1 -INSERT INTO test.tblparted VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now(); -]); +$s1->do_async(q[INSERT INTO test.tblparted VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();]); ok_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict'); wakeup_injection_point($node, 'reindex-relation-concurrently-before-swap'); -$s2->query_until( - qr/starting_upsert_s2/, q[ -\echo starting_upsert_s2 -INSERT INTO test.tblparted VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now(); -]); +$s2->do_async(q[INSERT INTO test.tblparted VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();]); ok_injection_point($node, 'exec-insert-before-insert-speculative'); @@ -544,50 +448,38 @@ $node->safe_psql('postgres', 'TRUNCATE TABLE test.tblparted'); ############################################################################ note('Test: REINDEX on partitioned table (s1 wakes before reindex)'); -$s1 = $node->background_psql('postgres', on_error_stop => 0); -$s2 = $node->background_psql('postgres', on_error_stop => 0); -$s3 = $node->background_psql('postgres', on_error_stop => 0); +$s1 = PostgreSQL::Test::Session->new(node => $node); +$s2 = PostgreSQL::Test::Session->new(node => $node); +$s3 = PostgreSQL::Test::Session->new(node => $node); -$s1->query_safe( +$s1->do( q[ SELECT injection_points_set_local(); SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait'); ]); -$s2->query_safe( +$s2->do( q[ SELECT injection_points_set_local(); SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait'); ]); -$s3->query_safe( +$s3->do( q[ SELECT injection_points_set_local(); SELECT injection_points_attach('reindex-relation-concurrently-before-set-dead', 'wait'); ]); -$s3->query_until( - qr/starting_reindex/, q[ -\echo starting_reindex -REINDEX INDEX CONCURRENTLY test.tbl_partition_pkey; -]); +$s3->do_async(q[REINDEX INDEX CONCURRENTLY test.tbl_partition_pkey;]); ok_injection_point($node, 'reindex-relation-concurrently-before-set-dead'); -$s1->query_until( - qr/starting_upsert_s1/, q[ -\echo starting_upsert_s1 -INSERT INTO test.tblparted VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now(); -]); +$s1->do_async(q[INSERT INTO test.tblparted VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();]); ok_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict'); # Start s2 BEFORE waking reindex -$s2->query_until( - qr/starting_upsert_s2/, q[ -\echo starting_upsert_s2 -INSERT INTO test.tblparted VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now(); -]); +$s2->do_async(q[INSERT INTO test.tblparted VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();]); ok_injection_point($node, 'exec-insert-before-insert-speculative'); @@ -607,35 +499,27 @@ note( 'Test: REINDEX on partitioned table, cache inval between two get_partition_ancestors' ); -$s1 = $node->background_psql('postgres', on_error_stop => 0); -$s2 = $node->background_psql('postgres', on_error_stop => 0); -$s3 = $node->background_psql('postgres', on_error_stop => 0); +$s1 = PostgreSQL::Test::Session->new(node => $node); +$s2 = PostgreSQL::Test::Session->new(node => $node); +$s3 = PostgreSQL::Test::Session->new(node => $node); -$s1->query_safe( +$s1->do( q[ SELECT injection_points_set_local(); SELECT injection_points_attach('exec-init-partition-after-get-partition-ancestors', 'wait'); ]); -$s2->query_safe( +$s2->do( q[ SELECT injection_points_set_local(); SELECT injection_points_attach('reindex-relation-concurrently-before-swap', 'wait'); ]); -$s2->query_until( - qr/starting_reindex/, q[ -\echo starting_reindex -REINDEX INDEX CONCURRENTLY test.tbl_partition_pkey; -]); +$s2->do_async(q[REINDEX INDEX CONCURRENTLY test.tbl_partition_pkey;]); ok_injection_point($node, 'reindex-relation-concurrently-before-swap'); -$s1->query_until( - qr/starting_upsert_s1/, q[ -\echo starting_upsert_s1 -INSERT INTO test.tblparted VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now(); -]); +$s1->do_async(q[INSERT INTO test.tblparted VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();]); ok_injection_point($node, 'exec-init-partition-after-get-partition-ancestors'); @@ -654,26 +538,24 @@ note('Test: CREATE INDEX CONCURRENTLY + UPSERT'); # Uses invalidate-catalog-snapshot-end to test catalog invalidation # during UPSERT -$s1 = $node->background_psql('postgres', on_error_stop => 0); -$s2 = $node->background_psql('postgres', on_error_stop => 0); -$s3 = $node->background_psql('postgres', on_error_stop => 0); +$s1 = PostgreSQL::Test::Session->new(node => $node); +$s2 = PostgreSQL::Test::Session->new(node => $node); +$s3 = PostgreSQL::Test::Session->new(node => $node); -my $s1_pid = $s1->query_safe('SELECT pg_backend_pid()'); +# Get the session's backend PID before attaching injection points +my $s1_pid = $s1->query_oneval('SELECT pg_backend_pid()'); # s1 attaches BOTH injection points - the unique constraint check AND catalog snapshot -$s1->query_safe( +$s1->do( q[ SELECT injection_points_set_local(); SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait'); ]); -$s1->query_until( - qr/attaching_injection_point/, q[ -\echo attaching_injection_point -SELECT injection_points_attach('invalidate-catalog-snapshot-end', 'wait'); -]); - # In cases of cache clobbering, s1 may hit the injection point during attach. +# Start attach asynchronously so we can check if it blocks. +$s1->do_async(q[SELECT injection_points_attach('invalidate-catalog-snapshot-end', 'wait');]); + # Wait for that session to become idle (attach completed), or wake it up if # it becomes stuck on injection point. if (!wait_for_idle($node, $s1_pid)) @@ -687,34 +569,28 @@ if (!wait_for_idle($node, $s1_pid)) SELECT injection_points_wakeup('invalidate-catalog-snapshot-end'); ]); } +# Wait for async command to complete +$s1->wait_for_completion; -$s2->query_safe( +$s2->do( q[ SELECT injection_points_set_local(); SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait'); ]); -$s3->query_safe( +$s3->do( q[ SELECT injection_points_set_local(); SELECT injection_points_attach('define-index-before-set-valid', 'wait'); ]); # s3: Start CREATE INDEX CONCURRENTLY (blocks on define-index-before-set-valid) -$s3->query_until( - qr/starting_create_index/, q[ -\echo starting_create_index -CREATE UNIQUE INDEX CONCURRENTLY tbl_pkey_duplicate ON test.tblpk(i); -]); +$s3->do_async(q[CREATE UNIQUE INDEX CONCURRENTLY tbl_pkey_duplicate ON test.tblpk(i);]); ok_injection_point($node, 'define-index-before-set-valid'); # s1: Start UPSERT (blocks on invalidate-catalog-snapshot-end) -$s1->query_until( - qr/starting_upsert_s1/, q[ -\echo starting_upsert_s1 -INSERT INTO test.tblpk VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now(); -]); +$s1->do_async(q[INSERT INTO test.tblpk VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();]); ok_injection_point($node, 'invalidate-catalog-snapshot-end'); @@ -722,11 +598,7 @@ ok_injection_point($node, 'invalidate-catalog-snapshot-end'); wakeup_injection_point($node, 'define-index-before-set-valid'); # s2: Start UPSERT (blocks on exec-insert-before-insert-speculative) -$s2->query_until( - qr/starting_upsert_s2/, q[ -\echo starting_upsert_s2 -INSERT INTO test.tblpk VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now(); -]); +$s2->do_async(q[INSERT INTO test.tblpk VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();]); ok_injection_point($node, 'exec-insert-before-insert-speculative'); @@ -747,24 +619,20 @@ $node->safe_psql('postgres', 'TRUNCATE TABLE test.tblparted'); note('Test: CREATE INDEX CONCURRENTLY on partial index + UPSERT'); # Uses invalidate-catalog-snapshot-end to test catalog invalidation during UPSERT -$s1 = $node->background_psql('postgres', on_error_stop => 0); -$s2 = $node->background_psql('postgres', on_error_stop => 0); -$s3 = $node->background_psql('postgres', on_error_stop => 0); +$s1 = PostgreSQL::Test::Session->new(node => $node); +$s2 = PostgreSQL::Test::Session->new(node => $node); +$s3 = PostgreSQL::Test::Session->new(node => $node); -$s1_pid = $s1->query_safe('SELECT pg_backend_pid()'); +$s1_pid = $s1->query_oneval('SELECT pg_backend_pid()'); # s1 attaches BOTH injection points - the unique constraint check AND catalog snapshot -$s1->query_safe( +$s1->do( q[ SELECT injection_points_set_local(); SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait'); ]); -$s1->query_until( - qr/attaching_injection_point/, q[ -\echo attaching_injection_point -SELECT injection_points_attach('invalidate-catalog-snapshot-end', 'wait'); -]); +$s1->do(q[SELECT injection_points_attach('invalidate-catalog-snapshot-end', 'wait');]); # In cases of cache clobbering, s1 may hit the injection point during attach. # Wait for that session to become idle (attach completed), or wake it up if @@ -781,33 +649,25 @@ if (!wait_for_idle($node, $s1_pid)) ]); } -$s2->query_safe( +$s2->do( q[ SELECT injection_points_set_local(); SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait'); ]); -$s3->query_safe( +$s3->do( q[ SELECT injection_points_set_local(); SELECT injection_points_attach('define-index-before-set-valid', 'wait'); ]); # s3: Start CREATE INDEX CONCURRENTLY (blocks on define-index-before-set-valid) -$s3->query_until( - qr/starting_create_index/, q[ -\echo starting_create_index -CREATE UNIQUE INDEX CONCURRENTLY tbl_pkey_special_duplicate ON test.tblexpr(abs(i)) WHERE i < 10000; -]); +$s3->do_async(q[CREATE UNIQUE INDEX CONCURRENTLY tbl_pkey_special_duplicate ON test.tblexpr(abs(i)) WHERE i < 10000;]); ok_injection_point($node, 'define-index-before-set-valid'); # s1: Start UPSERT (blocks on invalidate-catalog-snapshot-end) -$s1->query_until( - qr/starting_upsert_s1/, q[ -\echo starting_upsert_s1 -INSERT INTO test.tblexpr VALUES(13,now()) ON CONFLICT (abs(i)) WHERE i < 100 DO UPDATE SET updated_at = now(); -]); +$s1->do_async(q[INSERT INTO test.tblexpr VALUES(13,now()) ON CONFLICT (abs(i)) WHERE i < 100 DO UPDATE SET updated_at = now();]); ok_injection_point($node, 'invalidate-catalog-snapshot-end'); @@ -815,11 +675,7 @@ ok_injection_point($node, 'invalidate-catalog-snapshot-end'); wakeup_injection_point($node, 'define-index-before-set-valid'); # s2: Start UPSERT (blocks on exec-insert-before-insert-speculative) -$s2->query_until( - qr/starting_upsert_s2/, q[ -\echo starting_upsert_s2 -INSERT INTO test.tblexpr VALUES(13,now()) ON CONFLICT (abs(i)) WHERE i < 100 DO UPDATE SET updated_at = now(); -]); +$s2->do_async(q[INSERT INTO test.tblexpr VALUES(13,now()) ON CONFLICT (abs(i)) WHERE i < 100 DO UPDATE SET updated_at = now();]); ok_injection_point($node, 'exec-insert-before-insert-speculative'); wakeup_injection_point($node, 'invalidate-catalog-snapshot-end'); @@ -920,33 +776,23 @@ SELECT injection_points_wakeup('$point_name'); ]); } -# Wait for any pending query to complete, capture stderr, and close the session. -# Returns the stderr output (excluding internal markers). +# Wait for any pending query to complete and close the session. +# Returns empty string on success, error message on failure. sub safe_quit { my ($session) = @_; - # Send a marker and wait for it to ensure any pending query completes - my $banner = "safe_quit_marker"; - my $banner_match = qr/(^|\n)$banner\r?\n/; + # Wait for any async queries to complete + $session->wait_for_completion; - $session->{stdin} .= "\\echo $banner\n\\warn $banner\n"; - - pump_until( - $session->{run}, $session->{timeout}, - \$session->{stdout}, $banner_match); - pump_until( - $session->{run}, $session->{timeout}, - \$session->{stderr}, $banner_match); - - # Capture stderr (excluding the banner) - my $stderr = $session->{stderr}; - $stderr =~ s/$banner_match//; + # Check connection status + my $status = $session->conn_status; # Close the session - $session->quit; + $session->close; - return $stderr; + # Return empty string if connection was OK, otherwise return error + return ($status == PostgreSQL::PqFFI::CONNECTION_OK()) ? '' : 'connection error'; } # Helper function: verify that the given sessions exit cleanly. diff --git a/src/test/modules/test_misc/t/011_lock_stats.pl b/src/test/modules/test_misc/t/011_lock_stats.pl index 45d7d26f70c..00c1ab00df2 100644 --- a/src/test/modules/test_misc/t/011_lock_stats.pl +++ b/src/test/modules/test_misc/t/011_lock_stats.pl @@ -19,6 +19,7 @@ use warnings FATAL => 'all'; use PostgreSQL::Test::Cluster; use PostgreSQL::Test::Utils; +use PostgreSQL::Test::Session; use Test::More; plan skip_all => 'Injection points not supported by this build' @@ -32,15 +33,12 @@ my $node; # Setup the 2 sessions sub setup_sessions { - $s1 = $node->background_psql('postgres'); - $s2 = $node->background_psql('postgres'); + $s1 = PostgreSQL::Test::Session->new(node => $node); + $s2 = PostgreSQL::Test::Session->new(node => $node); # Setup injection points for the waiting session - $s2->query_until( - qr/attaching_injection_point/, q[ - \echo attaching_injection_point - SELECT injection_points_attach('deadlock-timeout-fired', 'wait'); - ]); + $s2->do( + q[SELECT injection_points_attach('deadlock-timeout-fired', 'wait');]); } # Fetch waits and wait_time from pg_stat_lock for a given lock type @@ -98,7 +96,7 @@ setup_sessions(); my $log_offset = -s $node->logfile; -$s1->query_safe( +$s1->do( q[ SELECT pg_stat_reset_shared('lock'); BEGIN; @@ -106,17 +104,13 @@ LOCK TABLE test_stat_tab; ]); # s2 setup -$s2->query_safe( +$s2->do( q[ BEGIN; SELECT pg_stat_force_next_flush(); ]); # s2 blocks on LOCK. -$s2->query_until( - qr/lock_s2/, q[ -\echo lock_s2 -LOCK TABLE test_stat_tab; -]); +$s2->do_async(q(LOCK TABLE test_stat_tab;)); wait_and_detach($node, 'deadlock-timeout-fired'); @@ -136,8 +130,9 @@ $node->safe_psql( $node->wait_for_log(qr/logging memory contexts/, $log_offset); # deadlock_timeout fired, now commit in s1 and s2 -$s1->query_safe(q(COMMIT)); -$s2->query_safe(q(COMMIT)); +$s1->do(q(COMMIT)); +$s2->wait_for_completion; +$s2->do(q(COMMIT)); # check that pg_stat_lock has been updated wait_for_pg_stat_lock($node, 'relation'); @@ -158,8 +153,8 @@ is( scalar @still_waiting, ); # close sessions -$s1->quit; -$s2->quit; +$s1->close; +$s2->close; ####### transaction lock @@ -167,27 +162,31 @@ setup_sessions(); $log_offset = -s $node->logfile; -$s1->query_safe( +# The INSERT must autocommit before the explicit transaction is opened, so +# that session s2 can see rows k1/k2/k3 and block on s1's row lock. Send it +# separately from the BEGIN block: a single multi-statement query containing +# BEGIN would run the INSERT inside the still-open transaction, leaving the +# rows invisible to s2 (so its UPDATE would match nothing and never wait). +$s1->do( q[ SELECT pg_stat_reset_shared('lock'); INSERT INTO test_stat_tab(key, value) VALUES('k1', 1), ('k2', 1), ('k3', 1); +]); +$s1->do( + q[ BEGIN; UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1'; ]); # s2 setup -$s2->query_safe( +$s2->do( q[ SET log_lock_waits = on; BEGIN; SELECT pg_stat_force_next_flush(); ]); # s2 blocks here on UPDATE -$s2->query_until( - qr/lock_s2/, q[ -\echo lock_s2 -UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1'; -]); +$s2->do_async(q(UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';)); wait_and_detach($node, 'deadlock-timeout-fired'); @@ -196,8 +195,9 @@ $node->wait_for_log(qr/still waiting for ShareLock on transaction/, $log_offset); # deadlock_timeout fired, now commit in s1 and s2 -$s1->query_safe(q(COMMIT)); -$s2->query_safe(q(COMMIT)); +$s1->do(q(COMMIT)); +$s2->wait_for_completion; +$s2->do(q(COMMIT)); # check that pg_stat_lock has been updated wait_for_pg_stat_lock($node, 'transactionid'); @@ -208,8 +208,8 @@ ok(1, "Lock stats ok for transactionid"); $node->wait_for_log(qr/acquired ShareLock on transaction/, $log_offset); # Close sessions -$s1->quit; -$s2->quit; +$s1->close; +$s2->close; ####### advisory lock @@ -217,25 +217,21 @@ setup_sessions(); $log_offset = -s $node->logfile; -$s1->query_safe( +$s1->do( q[ SELECT pg_stat_reset_shared('lock'); SELECT pg_advisory_lock(1); ]); # s2 setup -$s2->query_safe( +$s2->do( q[ SET log_lock_waits = on; BEGIN; SELECT pg_stat_force_next_flush(); ]); # s2 blocks on the advisory lock. -$s2->query_until( - qr/lock_s2/, q[ -\echo lock_s2 -SELECT pg_advisory_lock(1); -]); +$s2->do_async(q(SELECT pg_advisory_lock(1);)); wait_and_detach($node, 'deadlock-timeout-fired'); @@ -244,8 +240,9 @@ $node->wait_for_log(qr/still waiting for ExclusiveLock on advisory lock/, $log_offset); # deadlock_timeout fired, now unlock and commit s2 -$s1->query_safe(q(SELECT pg_advisory_unlock(1))); -$s2->query_safe( +$s1->do(q(SELECT pg_advisory_unlock(1))); +$s2->wait_for_completion; +$s2->do( q[ SELECT pg_advisory_unlock(1); COMMIT; @@ -260,8 +257,8 @@ ok(1, "Lock stats ok for advisory"); $node->wait_for_log(qr/acquired ExclusiveLock on advisory lock/, $log_offset); # Close sessions -$s1->quit; -$s2->quit; +$s1->close; +$s2->close; ####### Ensure log_lock_waits has no impact @@ -269,7 +266,7 @@ setup_sessions(); $log_offset = -s $node->logfile; -$s1->query_safe( +$s1->do( q[ SELECT pg_stat_reset_shared('lock'); BEGIN; @@ -277,24 +274,21 @@ LOCK TABLE test_stat_tab; ]); # s2 setup -$s2->query_safe( +$s2->do( q[ SET log_lock_waits = off; BEGIN; SELECT pg_stat_force_next_flush(); ]); # s2 blocks on LOCK. -$s2->query_until( - qr/lock_s2/, q[ -\echo lock_s2 -LOCK TABLE test_stat_tab; -]); +$s2->do_async(q(LOCK TABLE test_stat_tab;)); wait_and_detach($node, 'deadlock-timeout-fired'); # deadlock_timeout fired, now commit in s1 and s2 -$s1->query_safe(q(COMMIT)); -$s2->query_safe(q(COMMIT)); +$s1->do(q(COMMIT)); +$s2->wait_for_completion; +$s2->do(q(COMMIT)); # check that pg_stat_lock has been updated wait_for_pg_stat_lock($node, 'relation'); @@ -310,8 +304,8 @@ ok( !$node->log_contains( ); # close sessions -$s1->quit; -$s2->quit; +$s1->close; +$s2->close; # cleanup $node->safe_psql('postgres', q[DROP TABLE test_stat_tab;]); diff --git a/src/test/modules/test_misc/t/013_temp_obj_multisession.pl b/src/test/modules/test_misc/t/013_temp_obj_multisession.pl index 5f3cc7d2fc5..0b15b0bde08 100644 --- a/src/test/modules/test_misc/t/013_temp_obj_multisession.pl +++ b/src/test/modules/test_misc/t/013_temp_obj_multisession.pl @@ -20,21 +20,21 @@ use strict; use warnings; use PostgreSQL::Test::Cluster; use PostgreSQL::Test::Utils; -use PostgreSQL::Test::BackgroundPsql; +use PostgreSQL::Test::Session; use Test::More; my $node = PostgreSQL::Test::Cluster->new('temp_lock'); $node->init; $node->start; -# Owner session. Created via background_psql so it stays alive while -# the second session probes its temp objects. -my $psql1 = $node->background_psql('postgres'); +# Owner session. Created as a persistent libpq session so it stays alive +# while the second session probes its temp objects. +my $psql1 = PostgreSQL::Test::Session->new(node => $node); # Initially create the table without an index, so read paths go straight # through the read-stream / buffer-manager entry points without being # masked by an index scan that would hit ReadBuffer_common from nbtree. -$psql1->query_safe(q(CREATE TEMP TABLE foo AS SELECT 42 AS val;)); +$psql1->do(q(CREATE TEMP TABLE foo AS SELECT 42 AS val;)); # Resolve the owner's temp schema so the probing session can refer to # the table by a fully-qualified name. @@ -130,7 +130,7 @@ like($stderr, # Now create an index to exercise the index-scan path. nbtree calls # ReadBuffer (which is ReadBufferExtended -> ReadBuffer_common), so # this exercises a different chain of buffer-manager entry points. -$psql1->query_safe(q(CREATE INDEX ON foo(val);)); +$psql1->do(q(CREATE INDEX ON foo(val);)); $node->psql( 'postgres', @@ -156,7 +156,7 @@ like($stderr, qr/cannot alter temporary tables of other sessions/, # operations -- they don't read the underlying table -- which # documents the boundary between catalog and data access for temp # objects. -$psql1->query_safe( +$psql1->do( q[CREATE FUNCTION pg_temp.foo_id(r foo) RETURNS int LANGUAGE SQL ] . q[AS 'SELECT r.val';]); @@ -187,7 +187,7 @@ is($stderr, '', 'DROP TABLE is allowed'); # into the creator's pg_temp namespace with an auto-dependency on # the borrowed type, so it disappears together with the session that # created it. -$psql1->query_safe(q(CREATE TEMP TABLE foo2 AS SELECT 42 AS val;)); +$psql1->do(q(CREATE TEMP TABLE foo2 AS SELECT 42 AS val;)); $node->psql( 'postgres', @@ -216,8 +216,8 @@ my $foo2_oid = $node->safe_psql('postgres', # Cross-session LOCK TABLE scenario. Ensure that LockRelationOid is working # properly for other temp tables since this mechanism is also used by # autovacuum during orphaned tables cleanup. -my $psql2 = $node->background_psql('postgres'); -$psql2->query_safe( +my $psql2 = PostgreSQL::Test::Session->new(node => $node); +$psql2->do( qq{ BEGIN; LOCK TABLE $tempschema.foo2 IN ACCESS SHARE MODE; @@ -233,15 +233,15 @@ $psql2->query_safe( # owner will try to acquire deletion lock all its temp objects via # findDependentObjects. my $log_offset = -s $node->logfile; -$psql1->quit; +$psql1->close; # Check whether session-exit cleanup is blocked. $node->wait_for_log(qr/waiting for AccessExclusiveLock on relation $foo2_oid/, $log_offset); # Release lock on foo2 and allow session-exit cleanup to finish. -$psql2->query_safe(q(COMMIT;)); -$psql2->quit; +$psql2->do(q(COMMIT;)); +$psql2->close; # After releasing the lock, the owner can finally acquire # AccessExclusiveLock on foo2 and finish session-exit cleanup. Verify diff --git a/src/test/modules/test_slru/t/001_multixact.pl b/src/test/modules/test_slru/t/001_multixact.pl index f6f45895ebd..2d86434d7bc 100644 --- a/src/test/modules/test_slru/t/001_multixact.pl +++ b/src/test/modules/test_slru/t/001_multixact.pl @@ -6,6 +6,7 @@ use strict; use warnings FATAL => 'all'; use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Session; use PostgreSQL::Test::Utils; use Test::More; @@ -30,8 +31,8 @@ $node->safe_psql('postgres', q(CREATE EXTENSION test_slru)); # lost. # Create the first multixact -my $bg_psql = $node->background_psql('postgres'); -my $multi1 = $bg_psql->query_safe(q(SELECT test_create_multixact();)); +my $bg_session = PostgreSQL::Test::Session->new(node => $node); +my $multi1 = $bg_session->query_oneval(q(SELECT test_create_multixact();)); # Assign the middle multixact. Use an injection point to prevent it # from being fully recorded. @@ -39,11 +40,9 @@ $node->safe_psql('postgres', q{SELECT injection_points_attach('multixact-create-from-members','wait');} ); -$bg_psql->query_until( - qr/assigning lost multi/, q( -\echo assigning lost multi - SELECT test_create_multixact(); -)); +# Start the second multixact creation asynchronously - it will block at +# the injection point +$bg_session->do_async(q(SELECT test_create_multixact();)); $node->wait_for_event('client backend', 'multixact-create-from-members'); $node->safe_psql('postgres', @@ -52,10 +51,10 @@ $node->safe_psql('postgres', # Create the third multixid my $multi2 = $node->safe_psql('postgres', q{SELECT test_create_multixact();}); -# All set and done, it's time for hard restart +# All set and done, it's time for hard restart. The background session +# will be terminated by the crash. $node->stop('immediate'); $node->start; -$bg_psql->{run}->finish; # Verify that the recorded multixids are readable is( $node->safe_psql('postgres', qq{SELECT test_read_multixact('$multi1');}), diff --git a/src/test/modules/xid_wraparound/t/001_emergency_vacuum.pl b/src/test/modules/xid_wraparound/t/001_emergency_vacuum.pl index 213f9052ed2..304076735b4 100644 --- a/src/test/modules/xid_wraparound/t/001_emergency_vacuum.pl +++ b/src/test/modules/xid_wraparound/t/001_emergency_vacuum.pl @@ -4,6 +4,7 @@ use strict; use warnings FATAL => 'all'; use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Session; use PostgreSQL::Test::Utils; use Test::More; @@ -47,17 +48,10 @@ CREATE TABLE small_trunc(id serial primary key, data text, filler text default r INSERT INTO small_trunc(data) SELECT generate_series(1,15000); ]); -# Bump the query timeout to avoid false negatives on slow test systems. -my $psql_timeout_secs = 4 * $PostgreSQL::Test::Utils::timeout_default; - # Start a background session, which holds a transaction open, preventing # autovacuum from advancing relfrozenxid and datfrozenxid. -my $background_psql = $node->background_psql( - 'postgres', - on_error_stop => 0, - timeout => $psql_timeout_secs); -$background_psql->set_query_timer_restart(); -$background_psql->query_safe( +my $background_session = PostgreSQL::Test::Session->new(node => $node); +$background_session->do( qq[ BEGIN; DELETE FROM large WHERE id % 2 = 0; @@ -89,8 +83,8 @@ my $log_offset = -s $node->logfile; # Finish the old transaction, to allow vacuum freezing to advance # relfrozenxid and datfrozenxid again. -$background_psql->query_safe(qq[COMMIT]); -$background_psql->quit; +$background_session->do(qq[COMMIT;]); +$background_session->close; # Wait until autovacuum processed all tables and advanced the # system-wide oldest-XID. diff --git a/src/test/modules/xid_wraparound/t/002_limits.pl b/src/test/modules/xid_wraparound/t/002_limits.pl index 86632a8d510..0ef67d0f4b8 100644 --- a/src/test/modules/xid_wraparound/t/002_limits.pl +++ b/src/test/modules/xid_wraparound/t/002_limits.pl @@ -10,6 +10,7 @@ use strict; use warnings FATAL => 'all'; use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Session; use PostgreSQL::Test::Utils; use Test::More; use Time::HiRes qw(usleep); @@ -29,6 +30,8 @@ $node->append_conf( 'postgresql.conf', qq[ autovacuum_naptime = 1s log_autovacuum_min_duration = 0 +log_connections = on +log_statement = 'all' ]); $node->start; $node->safe_psql('postgres', 'CREATE EXTENSION xid_wraparound'); @@ -41,16 +44,10 @@ CREATE TABLE wraparoundtest(t text) WITH (autovacuum_enabled = off); INSERT INTO wraparoundtest VALUES ('start'); ]); -# Bump the query timeout to avoid false negatives on slow test systems. -my $psql_timeout_secs = 4 * $PostgreSQL::Test::Utils::timeout_default; - # Start a background session, which holds a transaction open, preventing # autovacuum from advancing relfrozenxid and datfrozenxid. -my $background_psql = $node->background_psql( - 'postgres', - on_error_stop => 0, - timeout => $psql_timeout_secs); -$background_psql->query_safe( +my $background_session = PostgreSQL::Test::Session->new(node => $node); +$background_session->do( qq[ BEGIN; INSERT INTO wraparoundtest VALUES ('oldxact'); @@ -108,8 +105,8 @@ like( # Finish the old transaction, to allow vacuum freezing to advance # relfrozenxid and datfrozenxid again. -$background_psql->query_safe(qq[COMMIT]); -$background_psql->quit; +$background_session->do(qq[COMMIT;]); +$background_session->close; # VACUUM, to freeze the tables and advance datfrozenxid. # @@ -122,8 +119,8 @@ $node->safe_psql('postgres', 'VACUUM'); # the system-wide oldest-XID. $ret = $node->poll_query_until('postgres', - qq[INSERT INTO wraparoundtest VALUES ('after VACUUM')], - 'INSERT 0 1'); + qq[INSERT INTO wraparoundtest VALUES ('after VACUUM') RETURNING true], + ); # Check the table contents $ret = $node->safe_psql('postgres', qq[SELECT * from wraparoundtest]); diff --git a/src/test/modules/xid_wraparound/t/004_notify_freeze.pl b/src/test/modules/xid_wraparound/t/004_notify_freeze.pl index d0a1f1fe2fc..f2fa4e5a6b9 100644 --- a/src/test/modules/xid_wraparound/t/004_notify_freeze.pl +++ b/src/test/modules/xid_wraparound/t/004_notify_freeze.pl @@ -7,6 +7,7 @@ use strict; use warnings FATAL => 'all'; use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Session; use Test::More; my $node = PostgreSQL::Test::Cluster->new('node'); @@ -24,9 +25,9 @@ $node->safe_psql('postgres', 'ALTER DATABASE template0 WITH ALLOW_CONNECTIONS true'); # Start Session 1 and leave it idle in transaction -my $psql_session1 = $node->background_psql('postgres'); -$psql_session1->query_safe('listen s;'); -$psql_session1->query_safe('begin;'); +my $session1 = PostgreSQL::Test::Session->new(node => $node); +$session1->do('LISTEN s'); +$session1->do('BEGIN'); # Send some notifys from other sessions for my $i (1 .. 10) @@ -54,18 +55,20 @@ my $datafronzenxid_freeze = $node->safe_psql('postgres', "select min(datfrozenxid::text::bigint) from pg_database"); ok($datafronzenxid_freeze > $datafronzenxid, 'datfrozenxid advanced'); -# On Session 1, commit and ensure that the all the notifications are +# On Session 1, commit and ensure that all the notifications are # received. This depends on correctly freezing the XIDs in the pending # notification entries. -my $res = $psql_session1->query_safe('commit;'); -my $notifications_count = 0; -foreach my $i (split('\n', $res)) +$session1->do('COMMIT'); + +my $notifications = $session1->get_all_notifications(); +is(scalar(@$notifications), 10, 'received all committed notifications'); + +my $expected_payload = 1; +foreach my $notify (@$notifications) { - $notifications_count++; - like($i, - qr/Asynchronous notification "s" with payload "$notifications_count" received/ - ); + is($notify->{channel}, 's', "notification $expected_payload has correct channel"); + is($notify->{payload}, $expected_payload, "notification $expected_payload has correct payload"); + $expected_payload++; } -is($notifications_count, 10, 'received all committed notifications'); done_testing(); diff --git a/src/test/postmaster/t/002_connection_limits.pl b/src/test/postmaster/t/002_connection_limits.pl index 8c67c4a86c7..d5019fac7b8 100644 --- a/src/test/postmaster/t/002_connection_limits.pl +++ b/src/test/postmaster/t/002_connection_limits.pl @@ -7,6 +7,7 @@ use strict; use warnings FATAL => 'all'; use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Session; use PostgreSQL::Test::Utils; use Test::More; @@ -33,19 +34,19 @@ GRANT pg_use_reserved_connections TO regress_reserved; CREATE USER regress_superuser LOGIN SUPERUSER; }); +my $node_connstr = $node->connstr('postgres'); + # With the limits we set in postgresql.conf, we can establish: # - 3 connections for any user with no special privileges # - 2 more connections for users belonging to "pg_use_reserved_connections" # - 1 more connection for superuser -sub background_psql_as_user +sub session_as_user { my $user = shift; + my $connstr = "$node_connstr user=$user"; - return $node->background_psql( - 'postgres', - on_error_die => 1, - extra_params => [ '--username' => $user ]); + return PostgreSQL::Test::Session->new(node => $node, connstr => $connstr); } # Like connect_fails(), except that we also wait for the failed backend to @@ -82,9 +83,9 @@ $node->restart; my @sessions = (); my @raw_connections = (); -push(@sessions, background_psql_as_user('regress_regular')); -push(@sessions, background_psql_as_user('regress_regular')); -push(@sessions, background_psql_as_user('regress_regular')); +push(@sessions, session_as_user('regress_regular')); +push(@sessions, session_as_user('regress_regular')); +push(@sessions, session_as_user('regress_regular')); connect_fails_wait( $node, "dbname=postgres user=regress_regular", @@ -93,8 +94,8 @@ connect_fails_wait( qr/FATAL: remaining connection slots are reserved for roles with privileges of the "pg_use_reserved_connections" role/ ); -push(@sessions, background_psql_as_user('regress_reserved')); -push(@sessions, background_psql_as_user('regress_reserved')); +push(@sessions, session_as_user('regress_reserved')); +push(@sessions, session_as_user('regress_reserved')); connect_fails_wait( $node, "dbname=postgres user=regress_reserved", @@ -103,7 +104,7 @@ connect_fails_wait( qr/FATAL: remaining connection slots are reserved for roles with the SUPERUSER attribute/ ); -push(@sessions, background_psql_as_user('regress_superuser')); +push(@sessions, session_as_user('regress_superuser')); connect_fails_wait( $node, "dbname=postgres user=regress_superuser", @@ -150,7 +151,7 @@ SKIP: # Clean up foreach my $session (@sessions) { - $session->quit; + $session->close; } foreach my $socket (@raw_connections) { diff --git a/src/test/recovery/t/009_twophase.pl b/src/test/recovery/t/009_twophase.pl index aa73d3e106c..9fe037bb0a1 100644 --- a/src/test/recovery/t/009_twophase.pl +++ b/src/test/recovery/t/009_twophase.pl @@ -6,6 +6,7 @@ use strict; use warnings FATAL => 'all'; use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Session; use PostgreSQL::Test::Utils; use Test::More; @@ -331,11 +332,10 @@ $cur_primary->stop; $cur_standby->restart; # Acquire a snapshot in standby, before we commit the prepared transaction -my $standby_session = - $cur_standby->background_psql('postgres', on_error_die => 1); -$standby_session->query_safe("BEGIN ISOLATION LEVEL REPEATABLE READ"); +my $standby_session = PostgreSQL::Test::Session->new(node => $cur_standby); +$standby_session->do("BEGIN ISOLATION LEVEL REPEATABLE READ"); $psql_out = - $standby_session->query_safe("SELECT count(*) FROM t_009_tbl_standby_mvcc"); + $standby_session->query_oneval("SELECT count(*) FROM t_009_tbl_standby_mvcc"); is($psql_out, '0', "Prepared transaction not visible in standby before commit"); @@ -349,17 +349,17 @@ COMMIT PREPARED 'xact_009_standby_mvcc'; # Still not visible to the old snapshot $psql_out = - $standby_session->query_safe("SELECT count(*) FROM t_009_tbl_standby_mvcc"); + $standby_session->query_oneval("SELECT count(*) FROM t_009_tbl_standby_mvcc"); is($psql_out, '0', "Committed prepared transaction not visible to old snapshot in standby"); # Is visible to a new snapshot -$standby_session->query_safe("COMMIT"); +$standby_session->do("COMMIT"); $psql_out = - $standby_session->query_safe("SELECT count(*) FROM t_009_tbl_standby_mvcc"); + $standby_session->query_oneval("SELECT count(*) FROM t_009_tbl_standby_mvcc"); is($psql_out, '2', "Committed prepared transaction is visible to new snapshot in standby"); -$standby_session->quit; +$standby_session->close; ############################################################################### # Check for a lock conflict between prepared transaction with DDL inside and diff --git a/src/test/recovery/t/013_crash_restart.pl b/src/test/recovery/t/013_crash_restart.pl index 0fde920713e..b62b0000edf 100644 --- a/src/test/recovery/t/013_crash_restart.pl +++ b/src/test/recovery/t/013_crash_restart.pl @@ -149,7 +149,7 @@ ok( pump_until( $monitor->finish; # Wait till server restarts -is($node->poll_query_until('postgres', undef, ''), +is($node->poll_until_connection('postgres'), "1", "reconnected after SIGQUIT"); @@ -238,7 +238,7 @@ ok( pump_until( $monitor->finish; # Wait till server restarts -is($node->poll_query_until('postgres', undef, ''), +is($node->poll_until_connection('postgres'), "1", "reconnected after SIGKILL"); # Make sure the committed rows survived, in-progress ones not diff --git a/src/test/recovery/t/022_crash_temp_files.pl b/src/test/recovery/t/022_crash_temp_files.pl index 5de9b0fb0eb..6c55fcf40b1 100644 --- a/src/test/recovery/t/022_crash_temp_files.pl +++ b/src/test/recovery/t/022_crash_temp_files.pl @@ -146,7 +146,7 @@ ok( pump_until( $killme2->finish; # Wait till server finishes restarting -$node->poll_query_until('postgres', undef, ''); +$node->poll_until_connection('postgres'); # Check for temporary files is( $node->safe_psql( @@ -253,7 +253,7 @@ ok( pump_until( $killme2->finish; # Wait till server finishes restarting -$node->poll_query_until('postgres', undef, ''); +$node->poll_until_connection('postgres'); # Check for temporary files -- should be there is( $node->safe_psql( diff --git a/src/test/recovery/t/031_recovery_conflict.pl b/src/test/recovery/t/031_recovery_conflict.pl index 7a740f69806..74349749170 100644 --- a/src/test/recovery/t/031_recovery_conflict.pl +++ b/src/test/recovery/t/031_recovery_conflict.pl @@ -7,6 +7,7 @@ use strict; use warnings FATAL => 'all'; use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Session; use PostgreSQL::Test::Utils; use Test::More; @@ -67,8 +68,7 @@ $node_primary->wait_for_replay_catchup($node_standby); # a longrunning psql that we can use to trigger conflicts -my $psql_standby = - $node_standby->background_psql($test_db, on_error_stop => 0); +my $psql_standby = PostgreSQL::Test::Session->new(node => $node_standby, dbname => $test_db); my $expected_conflicts = 0; @@ -96,7 +96,7 @@ my $cursor1 = "test_recovery_conflict_cursor"; # DECLARE and use a cursor on standby, causing buffer with the only block of # the relation to be pinned on the standby -my $res = $psql_standby->query_safe( +my $res = $psql_standby->query_oneval( qq[ BEGIN; DECLARE $cursor1 CURSOR FOR SELECT b FROM $table1; @@ -119,7 +119,7 @@ $node_primary->safe_psql($test_db, qq[VACUUM FREEZE $table1;]); $node_primary->wait_for_replay_catchup($node_standby); check_conflict_log("User was holding shared buffer pin for too long"); -$psql_standby->reconnect_and_clear(); +$psql_standby->reconnect(); check_conflict_stat("bufferpin"); @@ -132,7 +132,7 @@ $node_primary->safe_psql($test_db, $node_primary->wait_for_replay_catchup($node_standby); # DECLARE and FETCH from cursor on the standby -$res = $psql_standby->query_safe( +$res = $psql_standby->query_oneval( qq[ BEGIN; DECLARE $cursor1 CURSOR FOR SELECT b FROM $table1; @@ -152,7 +152,7 @@ $node_primary->wait_for_replay_catchup($node_standby); check_conflict_log( "User query might have needed to see row versions that must be removed"); -$psql_standby->reconnect_and_clear(); +$psql_standby->reconnect(); check_conflict_stat("snapshot"); @@ -161,7 +161,7 @@ $sect = "lock conflict"; $expected_conflicts++; # acquire lock to conflict with -$res = $psql_standby->query_safe( +$res = $psql_standby->query_oneval( qq[ BEGIN; LOCK TABLE $table1 IN ACCESS SHARE MODE; @@ -175,7 +175,7 @@ $node_primary->safe_psql($test_db, qq[DROP TABLE $table1;]); $node_primary->wait_for_replay_catchup($node_standby); check_conflict_log("User was holding a relation lock for too long"); -$psql_standby->reconnect_and_clear(); +$psql_standby->reconnect(); check_conflict_stat("lock"); @@ -186,7 +186,7 @@ $expected_conflicts++; # DECLARE a cursor for a query which, with sufficiently low work_mem, will # spill tuples into temp files in the temporary tablespace created during # setup. -$res = $psql_standby->query_safe( +$res = $psql_standby->query_oneval( qq[ BEGIN; SET work_mem = '64kB'; @@ -205,7 +205,7 @@ $node_primary->wait_for_replay_catchup($node_standby); check_conflict_log( "User was or might have been using tablespace that must be dropped"); -$psql_standby->reconnect_and_clear(); +$psql_standby->reconnect(); check_conflict_stat("tablespace"); @@ -220,8 +220,9 @@ $node_standby->adjust_conf( 'postgresql.conf', 'max_standby_streaming_delay', "${PostgreSQL::Test::Utils::timeout_default}s"); +$psql_standby->close; $node_standby->restart(); -$psql_standby->reconnect_and_clear(); +$psql_standby->reconnect(); # Generate a few dead rows, to later be cleaned up by vacuum. Then acquire a # lock on another relation in a prepared xact, so it's held continuously by @@ -244,12 +245,15 @@ SELECT txid_current(); $node_primary->wait_for_replay_catchup($node_standby); -$res = $psql_standby->query_until( - qr/^1$/m, qq[ +$res = $psql_standby->query_oneval( + qq[ BEGIN; -- hold pin DECLARE $cursor1 CURSOR FOR SELECT a FROM $table1; FETCH FORWARD FROM $cursor1; +]); +is ($res, 1, "pin held"); +$psql_standby->do_async(qq[ -- wait for lock held by prepared transaction SELECT * FROM $table2; ]); @@ -270,15 +274,16 @@ $node_primary->safe_psql($test_db, qq[VACUUM FREEZE $table1;]); $node_primary->wait_for_replay_catchup($node_standby); check_conflict_log("User transaction caused buffer deadlock with recovery."); -$psql_standby->reconnect_and_clear(); +$psql_standby->reconnect(); check_conflict_stat("deadlock"); # clean up for next tests $node_primary->safe_psql($test_db, qq[ROLLBACK PREPARED 'lock';]); $node_standby->adjust_conf('postgresql.conf', 'max_standby_streaming_delay', - '50ms'); + '50ms'); +$psql_standby->close; $node_standby->restart(); -$psql_standby->reconnect_and_clear(); +$psql_standby->reconnect(); # Check that expected number of conflicts show in pg_stat_database. Needs to @@ -302,7 +307,7 @@ check_conflict_log("User was connected to a database that must be dropped"); # explicitly shut down psql instances gracefully - to avoid hangs or worse on # windows -$psql_standby->quit; +$psql_standby->close; $node_standby->stop(); $node_primary->stop(); diff --git a/src/test/recovery/t/037_invalid_database.pl b/src/test/recovery/t/037_invalid_database.pl index a0947108700..b10a79d0ee7 100644 --- a/src/test/recovery/t/037_invalid_database.pl +++ b/src/test/recovery/t/037_invalid_database.pl @@ -5,6 +5,7 @@ use strict; use warnings FATAL => 'all'; use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Session; use PostgreSQL::Test::Utils; use Test::More; @@ -91,41 +92,38 @@ is($node->psql('postgres', 'DROP DATABASE regression_invalid'), # dropping the database, making it a suitable point to wait. Since relcache # init reads pg_tablespace, establish each connection before locking. This # avoids a connection-time hang with debug_discard_caches. -my $cancel = $node->background_psql('postgres', on_error_stop => 1); -my $bgpsql = $node->background_psql('postgres', on_error_stop => 0); -my $pid = $bgpsql->query('SELECT pg_backend_pid()'); +my $cancel = PostgreSQL::Test::Session->new(node=>$node); +my $bgpsql = PostgreSQL::Test::Session->new(node=>$node); +my $pid = $bgpsql->query_oneval('SELECT pg_backend_pid()'); # create the database, prevent drop database via lock held by a 2PC transaction -$bgpsql->query_safe( - qq( - CREATE DATABASE regression_invalid_interrupt; - BEGIN; +is (1, $bgpsql->do( + qq( + CREATE DATABASE regression_invalid_interrupt;), + qq(BEGIN; LOCK pg_tablespace; - PREPARE TRANSACTION 'lock_tblspc';)); + PREPARE TRANSACTION 'lock_tblspc';))); # Try to drop. This will wait due to the still held lock. -$bgpsql->query_until(qr//, "DROP DATABASE regression_invalid_interrupt;\n"); +$bgpsql->do_async("DROP DATABASE regression_invalid_interrupt;"); # Once the DROP DATABASE is waiting for the lock, interrupt it. -ok( $cancel->query_safe( - qq( +my $cancel_res = $cancel->query( + qq[ DO \$\$ BEGIN WHILE NOT EXISTS(SELECT * FROM pg_locks WHERE NOT granted AND relation = 'pg_tablespace'::regclass AND mode = 'AccessShareLock') LOOP PERFORM pg_sleep(.1); END LOOP; END\$\$; - SELECT pg_cancel_backend($pid);)), - "canceling DROP DATABASE"); -$cancel->quit(); + SELECT pg_cancel_backend($pid)]); +is (2, $cancel_res->{status}, "canceling DROP DATABASE"); # COMMAND_TUPLES_OK +$cancel->close(); +$bgpsql->wait_for_completion; # wait for cancellation to be processed -ok( pump_until( - $bgpsql->{run}, $bgpsql->{timeout}, - \$bgpsql->{stderr}, qr/canceling statement due to user request/), - "cancel processed"); -$bgpsql->{stderr} = ''; +pass("cancel processed"); # Verify that connections to the database aren't allowed. The backend checks # this before relcache init, so the lock won't interfere. @@ -134,9 +132,12 @@ is($node->psql('regression_invalid_interrupt', ''), # To properly drop the database, we need to release the lock previously preventing # doing so. -$bgpsql->query_safe(qq(ROLLBACK PREPARED 'lock_tblspc')); -$bgpsql->query_safe(qq(DROP DATABASE regression_invalid_interrupt)); +ok($bgpsql->do(qq(ROLLBACK PREPARED 'lock_tblspc')), + "unblock DROP DATABASE"); -$bgpsql->quit(); +ok($bgpsql->query(qq(DROP DATABASE regression_invalid_interrupt)), + "DROP DATABASE invalid_interrupt"); + +$bgpsql->close(); done_testing(); diff --git a/src/test/recovery/t/040_standby_failover_slots_sync.pl b/src/test/recovery/t/040_standby_failover_slots_sync.pl index f8922aaa1a2..ef16e0a8740 100644 --- a/src/test/recovery/t/040_standby_failover_slots_sync.pl +++ b/src/test/recovery/t/040_standby_failover_slots_sync.pl @@ -4,6 +4,7 @@ use strict; use warnings FATAL => 'all'; use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Session; use PostgreSQL::Test::Utils; use Test::More; @@ -768,17 +769,13 @@ $primary->safe_psql('postgres', "SELECT pg_create_logical_replication_slot('test_slot', 'test_decoding', false, false, true);" ); -my $back_q = $primary->background_psql( - 'postgres', - on_error_stop => 0, - timeout => $PostgreSQL::Test::Utils::timeout_default); +my $back_q = PostgreSQL::Test::Session->new(node=>$primary); # pg_logical_slot_get_changes will be blocked until the standby catches up, # hence it needs to be executed in a background session. $offset = -s $primary->logfile; -$back_q->query_until( - qr/logical_slot_get_changes/, q( - \echo logical_slot_get_changes +$back_q->do_async( + q( SELECT pg_logical_slot_get_changes('test_slot', NULL, NULL); )); @@ -796,7 +793,8 @@ $primary->reload; # Since there are no slots in synchronized_standby_slots, the function # pg_logical_slot_get_changes should now return, and the session can be # stopped. -$back_q->quit; +$back_q->wait_for_completion; +$back_q->close; $primary->safe_psql('postgres', "SELECT pg_drop_replication_slot('test_slot');"); @@ -1059,13 +1057,8 @@ $standby2->reload; # synchronization until the remote slot catches up. # The API will not return until this happens, to be able to make # further calls, call the API in a background process. -my $h = $standby2->background_psql('postgres', on_error_stop => 0); - -$h->query_until( - qr/start/, q( - \echo start - SELECT pg_sync_replication_slots(); - )); +my $h = PostgreSQL::Test::Session->new(node => $standby2); +$h->do_async(q(SELECT pg_sync_replication_slots();)); # Confirm that the slot sync is skipped due to the remote slot lagging behind $standby2->wait_for_log( @@ -1104,6 +1097,7 @@ $standby2->wait_for_log( qr/newly created replication slot \"lsub1_slot\" is sync-ready now/, $log_offset); -$h->quit; +$h->wait_for_completion; +$h->close; done_testing(); diff --git a/src/test/recovery/t/041_checkpoint_at_promote.pl b/src/test/recovery/t/041_checkpoint_at_promote.pl index d0783fef9ae..fcc01334589 100644 --- a/src/test/recovery/t/041_checkpoint_at_promote.pl +++ b/src/test/recovery/t/041_checkpoint_at_promote.pl @@ -4,6 +4,7 @@ use strict; use warnings FATAL => 'all'; use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Session; use PostgreSQL::Test::Utils; use Time::HiRes qw(usleep); use Test::More; @@ -70,11 +71,9 @@ $node_standby->safe_psql('postgres', # Execute a restart point on the standby, that we will now be waiting on. # This needs to be in the background. my $logstart = -s $node_standby->logfile; -my $psql_session = - $node_standby->background_psql('postgres', on_error_stop => 0); -$psql_session->query_until( - qr/starting_checkpoint/, q( - \echo starting_checkpoint +my $psql_session = PostgreSQL::Test::Session->new(node=> $node_standby); +$psql_session->do_async( + q( CHECKPOINT; )); @@ -159,7 +158,7 @@ ok( pump_until( $killme->finish; # Wait till server finishes restarting. -$node_standby->poll_query_until('postgres', undef, ''); +$node_standby->poll_until_connection('postgres'); # After recovery, the server should be able to start. my $stdout; diff --git a/src/test/recovery/t/042_low_level_backup.pl b/src/test/recovery/t/042_low_level_backup.pl index df4ae029fe6..58489482341 100644 --- a/src/test/recovery/t/042_low_level_backup.pl +++ b/src/test/recovery/t/042_low_level_backup.pl @@ -10,6 +10,7 @@ use warnings FATAL => 'all'; use File::Copy qw(copy); use File::Path qw(rmtree); use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Session; use PostgreSQL::Test::Utils; use Test::More; @@ -20,11 +21,10 @@ $node_primary->start; # Start backup. my $backup_name = 'backup1'; -my $psql = $node_primary->background_psql('postgres'); +my $psql = PostgreSQL::Test::Session->new(node => $node_primary); -$psql->query_safe("SET client_min_messages TO WARNING"); -$psql->set_query_timer_restart; -$psql->query_safe("select pg_backup_start('test label')"); +$psql->do("SET client_min_messages TO WARNING"); +$psql->query("select pg_backup_start('test label')"); # Copy files. my $backup_dir = $node_primary->backup_dir . '/' . $backup_name; @@ -81,9 +81,9 @@ my $stop_segment_name = $node_primary->safe_psql('postgres', # Stop backup and get backup_label, the last segment is archived. my $backup_label = - $psql->query_safe("select labelfile from pg_backup_stop()"); + $psql->query_oneval("select labelfile from pg_backup_stop()"); -$psql->quit; +$psql->close; # Rather than writing out backup_label, try to recover the backup without # backup_label to demonstrate that recovery will not work correctly without it, diff --git a/src/test/recovery/t/046_checkpoint_logical_slot.pl b/src/test/recovery/t/046_checkpoint_logical_slot.pl index 66761bf56c1..3a46f20fc28 100644 --- a/src/test/recovery/t/046_checkpoint_logical_slot.pl +++ b/src/test/recovery/t/046_checkpoint_logical_slot.pl @@ -8,6 +8,7 @@ use strict; use warnings FATAL => 'all'; use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Session; use PostgreSQL::Test::Utils; use Test::More; @@ -52,13 +53,10 @@ $node->safe_psql('postgres', $node->safe_psql('postgres', q{checkpoint}); # Generate some transactions to get RUNNING_XACTS. -my $xacts = $node->background_psql('postgres'); -$xacts->query_until( - qr/run_xacts/, - q(\echo run_xacts -SELECT 1 \watch 0.1 -\q -)); +for (my $i = 0; $i < 10; $i++) +{ + $node->safe_psql('postgres', 'SELECT 1'); +} $node->advance_wal(20); @@ -72,16 +70,11 @@ $node->advance_wal(20); # removing old WAL segments. note('starting checkpoint'); -my $checkpoint = $node->background_psql('postgres'); -$checkpoint->query_safe( +$node->safe_psql('postgres', q(select injection_points_attach('checkpoint-before-old-wal-removal','wait')) ); -$checkpoint->query_until( - qr/starting_checkpoint/, - q(\echo starting_checkpoint -checkpoint; -\q -)); +my $checkpoint = PostgreSQL::Test::Session->new(node => $node); +$checkpoint->do_async(q(CHECKPOINT;)); # Wait until the checkpoint stops right before removing WAL segments. note('waiting for injection_point'); @@ -90,17 +83,21 @@ note('injection_point is reached'); # Try to advance the logical slot, but make it stop when it moves to the next # WAL segment (this has to happen in the background, too). -my $logical = $node->background_psql('postgres'); -$logical->query_safe( +# We need to call pg_logical_slot_get_changes repeatedly until the slot +# advances to the next segment and hits the injection point. +my $logical = PostgreSQL::Test::Session->new(node => $node); +$logical->do( q{select injection_points_attach('logical-replication-slot-advance-segment','wait');} ); -$logical->query_until( - qr/get_changes/, - q( -\echo get_changes -select count(*) from pg_logical_slot_get_changes('slot_logical', null, null) \watch 1 -\q -)); +$logical->do_async( + q{DO $$ + BEGIN + LOOP + PERFORM count(*) FROM pg_logical_slot_get_changes('slot_logical', null, null); + PERFORM pg_sleep(0.1); + END LOOP; + END $$;} +); # Wait until the slot's restart_lsn points to the next WAL segment. note('waiting for injection_point'); @@ -138,12 +135,8 @@ eval { }; is($@, '', "Logical slot still valid"); -# If we send \q with $->quit the command can be sent to the -# session already closed. So \q is in initial script, here we only finish -# IPC::Run -$xacts->{run}->finish; -$checkpoint->{run}->finish; -$logical->{run}->finish; +# Sessions were terminated by the server crash and will be cleaned up +# automatically when they go out of scope. # Verify that the synchronized slots won't be invalidated immediately after # synchronization in the presence of a concurrent checkpoint. @@ -185,15 +178,11 @@ $primary->wait_for_replay_catchup($standby); # checkpoint stops right before invalidating replication slots. note('starting checkpoint'); -$checkpoint = $standby->background_psql('postgres'); -$checkpoint->query_safe( +$standby->safe_psql('postgres', q(select injection_points_attach('restartpoint-before-slot-invalidation','wait')) ); -$checkpoint->query_until( - qr/starting_checkpoint/, - q(\echo starting_checkpoint -checkpoint; -)); +$checkpoint = PostgreSQL::Test::Session->new(node => $standby); +$checkpoint->do_async(q(CHECKPOINT;)); # Wait until the checkpoint stops right before invalidating slots note('waiting for injection_point'); @@ -216,7 +205,8 @@ $standby->safe_psql( q{select injection_points_wakeup('restartpoint-before-slot-invalidation'); select injection_points_detach('restartpoint-before-slot-invalidation')}); -$checkpoint->quit; +$checkpoint->wait_for_completion; +$checkpoint->close; # Confirm that the slot is not invalidated is( $standby->safe_psql( diff --git a/src/test/recovery/t/047_checkpoint_physical_slot.pl b/src/test/recovery/t/047_checkpoint_physical_slot.pl index 4334145abe1..43193c219ba 100644 --- a/src/test/recovery/t/047_checkpoint_physical_slot.pl +++ b/src/test/recovery/t/047_checkpoint_physical_slot.pl @@ -8,6 +8,7 @@ use strict; use warnings FATAL => 'all'; use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Session; use PostgreSQL::Test::Utils; use Test::More; @@ -69,16 +70,11 @@ note("restart lsn before checkpoint: $restart_lsn_init"); # removing old WAL segments. note('starting checkpoint'); -my $checkpoint = $node->background_psql('postgres'); -$checkpoint->query_safe( +my $checkpoint = PostgreSQL::Test::Session->new(node => $node); +$checkpoint->do( q{select injection_points_attach('checkpoint-before-old-wal-removal','wait')} ); -$checkpoint->query_until( - qr/starting_checkpoint/, - q(\echo starting_checkpoint -checkpoint; -\q -)); +$checkpoint->do_async('checkpoint'); # Wait until the checkpoint stops right before removing WAL segments. note('waiting for injection_point'); @@ -104,7 +100,11 @@ my $restart_lsn_old = $node->safe_psql('postgres', chomp($restart_lsn_old); note("restart lsn before stop: $restart_lsn_old"); -# Abruptly stop the server. +$checkpoint->wait_for_completion(); +$checkpoint->close(); + +# Abruptly stop the server (1 second should be enough for the checkpoint +# to finish; it would be better). $node->stop('immediate'); $node->start; diff --git a/src/test/recovery/t/048_vacuum_horizon_floor.pl b/src/test/recovery/t/048_vacuum_horizon_floor.pl index 52acb5561d6..a487be04dc8 100644 --- a/src/test/recovery/t/048_vacuum_horizon_floor.pl +++ b/src/test/recovery/t/048_vacuum_horizon_floor.pl @@ -45,12 +45,10 @@ my $orig_conninfo = $node_primary->connstr(); my $table1 = "vac_horizon_floor_table"; # Long-running Primary Session A -my $psql_primaryA = - $node_primary->background_psql($test_db, on_error_stop => 1); +my $session_primaryA = PostgreSQL::Test::Session->new(node => $node_primary, dbname => $test_db); # Long-running Primary Session B -my $psql_primaryB = - $node_primary->background_psql($test_db, on_error_stop => 1); +my $session_primaryB = PostgreSQL::Test::Session->new(node => $node_primary, dbname => $test_db); # Our test relies on two rounds of index vacuuming for reasons elaborated # later. To trigger two rounds of index vacuuming, we must fill up the @@ -123,7 +121,7 @@ $node_replica->poll_query_until( # Now insert and update a tuple which will be visible to the vacuum on the # primary but which will have xmax newer than the oldest xmin on the standby # that was recently disconnected. -my $res = $psql_primaryA->query_safe( +my $res = $session_primaryA->query( qq[ INSERT INTO $table1 VALUES (99); UPDATE $table1 SET col1 = 100 WHERE col1 = 99; @@ -132,7 +130,7 @@ my $res = $psql_primaryA->query_safe( ); # Make sure the UPDATE finished -like($res, qr/^after_update$/m, "UPDATE occurred on primary session A"); +like($res->{psqlout}, qr/^after_update$/m, "UPDATE occurred on primary session A"); # Open a cursor on the primary whose pin will keep VACUUM from getting a # cleanup lock on the first page of the relation. We want VACUUM to be able to @@ -145,7 +143,7 @@ my $primary_cursor1 = "vac_horizon_floor_cursor1"; # The first value inserted into the table was a 7, so FETCH FORWARD should # return a 7. That's how we know the cursor has a pin. # Disable index scans so the cursor pins heap pages and not index pages. -$res = $psql_primaryB->query_safe( +$res = $session_primaryB->query( qq[ BEGIN; SET enable_bitmapscan = off; @@ -156,11 +154,11 @@ $res = $psql_primaryB->query_safe( ] ); -is($res, 7, qq[Cursor query returned $res. Expected value 7.]); +is($res->{psqlout}, 7, qq[Cursor query returned $res->{psqlout}. Expected value 7.]); # Get the PID of the session which will run the VACUUM FREEZE so that we can # use it to filter pg_stat_activity later. -my $vacuum_pid = $psql_primaryA->query_safe("SELECT pg_backend_pid();"); +my $vacuum_pid = $session_primaryA->query_oneval("SELECT pg_backend_pid();"); # Now start a VACUUM FREEZE on the primary. It will call vacuum_get_cutoffs() # and establish values of OldestXmin and GlobalVisState which are newer than @@ -176,14 +174,8 @@ my $vacuum_pid = $psql_primaryA->query_safe("SELECT pg_backend_pid();"); # pages of the heap must be processed in order by a single worker to ensure # test stability (PARALLEL 0 shouldn't be necessary but guards against the # possibility of parallel heap vacuuming). -$psql_primaryA->{stdin} .= qq[ - SET maintenance_io_concurrency = 0; - VACUUM (VERBOSE, FREEZE, PARALLEL 0) $table1; - \\echo VACUUM - ]; - -# Make sure the VACUUM command makes it to the server. -$psql_primaryA->{run}->pump_nb(); +$session_primaryA->do('SET maintenance_io_concurrency = 0;'); +$session_primaryA->do_async("VACUUM (VERBOSE, FREEZE, PARALLEL 0) $table1;"); # Make sure that the VACUUM has already called vacuum_get_cutoffs() and is # just waiting on the lock to start vacuuming. We don't want the standby to @@ -229,7 +221,7 @@ $node_primary->poll_query_until( # expect that a round of index vacuuming has happened and that the vacuum is # now waiting for the cursor to release its pin on the last page of the # relation. -$res = $psql_primaryB->query_safe("FETCH $primary_cursor1"); +$res = $session_primaryB->query_oneval("FETCH $primary_cursor1"); is($res, 7, qq[Cursor query returned $res from second fetch. Expected value 7.]); @@ -243,13 +235,7 @@ $node_primary->poll_query_until( ], 't'); # Commit the transaction with the open cursor so that the VACUUM can finish. -$psql_primaryB->query_until( - qr/^commit$/m, - qq[ - COMMIT; - \\echo commit - ] -); +$session_primaryB->do('COMMIT'); # VACUUM proceeds with pruning and does a visibility check on each tuple. In # older versions of Postgres, pruning found our final dead tuple @@ -281,8 +267,8 @@ $node_primary->safe_psql($test_db, "INSERT INTO $table1 VALUES (1);"); $node_primary->wait_for_catchup($node_replica, 'replay', $primary_lsn); ## Shut down psqls -$psql_primaryA->quit; -$psql_primaryB->quit; +$session_primaryA->close; +$session_primaryB->close; $node_replica->stop(); $node_primary->stop(); diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl index bc216064714..9911e5bfa04 100644 --- a/src/test/recovery/t/049_wait_for_lsn.pl +++ b/src/test/recovery/t/049_wait_for_lsn.pl @@ -5,6 +5,7 @@ use strict; use warnings FATAL => 'all'; use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Session; use PostgreSQL::Test::Utils; use Test::More; @@ -221,20 +222,15 @@ ok($output eq "timeout", "WAIT FOR returns correct status after timeout"); my $subxact_lsn = $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn() + 10000000000"); my $subxact_appname = 'wait_for_lsn_subxact_cleanup'; -my $subxact_session = - $node_primary->background_psql('postgres', on_error_stop => 0); -$subxact_session->query_until( - qr/start/, qq[ - SET application_name = '$subxact_appname'; - BEGIN; - SAVEPOINT wait_cleanup; - \\echo start - WAIT FOR LSN '${subxact_lsn}' WITH (MODE 'primary_flush'); - ROLLBACK TO wait_cleanup; - WAIT FOR LSN '${subxact_lsn}' - WITH (MODE 'primary_flush', timeout '10ms', no_throw); - COMMIT; -]); +my $subxact_session = PostgreSQL::Test::Session->new(node => $node_primary); +# Send the setup statements individually so the first WAIT FOR LSN can be +# issued asynchronously: it blocks (the target LSN is unreachable) and will +# be canceled below. +$subxact_session->do("SET application_name = '$subxact_appname'"); +$subxact_session->do("BEGIN"); +$subxact_session->do("SAVEPOINT wait_cleanup"); +$subxact_session->do_async( + "WAIT FOR LSN '${subxact_lsn}' WITH (MODE 'primary_flush')"); $node_primary->poll_query_until( 'postgres', "SELECT count(*) = 1 FROM pg_stat_activity @@ -248,18 +244,30 @@ my $subxact_cancelled = $node_primary->safe_psql( AND wait_event = 'WaitForWalFlush'" ); is($subxact_cancelled, 't', "canceled WAIT FOR LSN in subtransaction"); -$subxact_session->quit; -chomp($subxact_session->{stdout}); + +# The cancel interrupts the blocking WAIT FOR LSN, leaving the transaction +# in an aborted state. +my $subxact_cancel_res = $subxact_session->get_async_result(); like( - $subxact_session->{stderr}, + $subxact_cancel_res->{error_message}, qr/canceling statement due to user request/, "query cancel interrupted WAIT FOR LSN in subtransaction"); -is($subxact_session->{stdout}, + +# Roll back to the savepoint so a second WAIT FOR LSN can register again in +# the same backend; with no_throw it returns 'timeout' rather than erroring. +$subxact_session->do("ROLLBACK TO wait_cleanup"); +my $subxact_timeout = $subxact_session->query_oneval( + "WAIT FOR LSN '${subxact_lsn}' + WITH (MODE 'primary_flush', timeout '10ms', no_throw)"); +is($subxact_timeout, "timeout", "second WAIT FOR LSN timed out after savepoint rollback"); -unlike( - $subxact_session->{stderr}, - qr/server closed the connection unexpectedly/, + +# The backend survived the cancel without disconnecting: the connection is +# still usable. +is($subxact_session->query_oneval('SELECT 1'), '1', "WAIT FOR LSN after savepoint rollback did not disconnect"); +$subxact_session->do("COMMIT"); +$subxact_session->close; # 5. Check mode validation: standby modes error on primary, primary mode errors # on standby, and primary_flush works on primary. Also check that WAIT FOR @@ -467,10 +475,8 @@ for (my $i = 0; $i < 5; $i++) my $lsn = $node_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()"); - $psql_sessions[$i] = $node_standby->background_psql('postgres'); - $psql_sessions[$i]->query_until( - qr/start/, qq[ - \\echo start + $psql_sessions[$i] = PostgreSQL::Test::Session->new(node => $node_standby); + $psql_sessions[$i]->do_async(qq[ WAIT FOR LSN '${lsn}'; SELECT log_count(${i}); ]); @@ -481,7 +487,8 @@ $node_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume();"); for (my $i = 0; $i < 5; $i++) { $node_standby->wait_for_log("count ${i}", $log_offset); - $psql_sessions[$i]->quit; + $psql_sessions[$i]->wait_for_completion; + $psql_sessions[$i]->close; } ok(1, 'multiple standby_replay waiters reported consistent data'); @@ -505,10 +512,8 @@ for (my $i = 0; $i < 5; $i++) my @write_sessions; for (my $i = 0; $i < 5; $i++) { - $write_sessions[$i] = $node_standby->background_psql('postgres'); - $write_sessions[$i]->query_until( - qr/start/, qq[ - \\echo start + $write_sessions[$i] = PostgreSQL::Test::Session->new(node => $node_standby); + $write_sessions[$i]->do_async(qq[ WAIT FOR LSN '$write_lsns[$i]' WITH (MODE 'standby_write', timeout '1d'); SELECT log_wait_done('write_done', $i); ]); @@ -527,7 +532,8 @@ resume_walreceiver($node_standby); for (my $i = 0; $i < 5; $i++) { $node_standby->wait_for_log("write_done $i", $write_log_offset); - $write_sessions[$i]->quit; + $write_sessions[$i]->wait_for_completion; + $write_sessions[$i]->close; } # Verify on standby that WAL was written up to the target LSN @@ -557,10 +563,8 @@ for (my $i = 0; $i < 5; $i++) my @flush_sessions; for (my $i = 0; $i < 5; $i++) { - $flush_sessions[$i] = $node_standby->background_psql('postgres'); - $flush_sessions[$i]->query_until( - qr/start/, qq[ - \\echo start + $flush_sessions[$i] = PostgreSQL::Test::Session->new(node => $node_standby); + $flush_sessions[$i]->do_async(qq[ WAIT FOR LSN '$flush_lsns[$i]' WITH (MODE 'standby_flush', timeout '1d'); SELECT log_wait_done('flush_done', $i); ]); @@ -579,7 +583,8 @@ resume_walreceiver($node_standby); for (my $i = 0; $i < 5; $i++) { $node_standby->wait_for_log("flush_done $i", $flush_log_offset); - $flush_sessions[$i]->quit; + $flush_sessions[$i]->wait_for_completion; + $flush_sessions[$i]->close; } # Verify on standby that WAL was flushed up to the target LSN @@ -615,10 +620,8 @@ my @mixed_sessions; my @mixed_modes = ('standby_replay', 'standby_write', 'standby_flush'); for (my $i = 0; $i < 6; $i++) { - $mixed_sessions[$i] = $node_standby->background_psql('postgres'); - $mixed_sessions[$i]->query_until( - qr/start/, qq[ - \\echo start + $mixed_sessions[$i] = PostgreSQL::Test::Session->new(node => $node_standby); + $mixed_sessions[$i]->do_async(qq[ WAIT FOR LSN '${mixed_target_lsn}' WITH (MODE '$mixed_modes[$i % 3]', timeout '1d'); SELECT log_wait_done('mixed_done', $i); ]); @@ -642,7 +645,8 @@ resume_walreceiver($node_standby); for (my $i = 0; $i < 6; $i++) { $node_standby->wait_for_log("mixed_done $i", $mixed_log_offset); - $mixed_sessions[$i]->quit; + $mixed_sessions[$i]->wait_for_completion; + $mixed_sessions[$i]->close; } # Verify all modes reached the target LSN @@ -675,10 +679,8 @@ my $primary_flush_log_offset = -s $node_primary->logfile; my @primary_flush_sessions; for (my $i = 0; $i < 5; $i++) { - $primary_flush_sessions[$i] = $node_primary->background_psql('postgres'); - $primary_flush_sessions[$i]->query_until( - qr/start/, qq[ - \\echo start + $primary_flush_sessions[$i] = PostgreSQL::Test::Session->new(node => $node_primary); + $primary_flush_sessions[$i]->do_async(qq[ WAIT FOR LSN '$primary_flush_lsns[$i]' WITH (MODE 'primary_flush', timeout '1d'); SELECT log_wait_done('primary_flush_done', $i); ]); @@ -689,7 +691,8 @@ for (my $i = 0; $i < 5; $i++) { $node_primary->wait_for_log("primary_flush_done $i", $primary_flush_log_offset); - $primary_flush_sessions[$i]->quit; + $primary_flush_sessions[$i]->wait_for_completion; + $primary_flush_sessions[$i]->close; } # Verify on primary that WAL was flushed up to the target LSN @@ -717,10 +720,8 @@ my @wait_modes = ('standby_replay', 'standby_write', 'standby_flush'); my @wait_sessions; for (my $i = 0; $i < 3; $i++) { - $wait_sessions[$i] = $node_standby->background_psql('postgres'); - $wait_sessions[$i]->query_until( - qr/start/, qq[ - \\echo start + $wait_sessions[$i] = PostgreSQL::Test::Session->new(node => $node_standby); + $wait_sessions[$i]->do_async(qq[ WAIT FOR LSN '${lsn4}' WITH (MODE '$wait_modes[$i]'); ]); } @@ -758,12 +759,7 @@ ok($output eq "not in recovery", $node_standby->stop; $node_primary->stop; -# If we send \q with $session->quit the command can be sent to the session -# already closed. So \q is in initial script, here we only finish IPC::Run. -for (my $i = 0; $i < 3; $i++) -{ - $wait_sessions[$i]->{run}->finish; -} +# Sessions will be cleaned up automatically when they go out of scope. # 9. Archive-only standby tests: verify standby_write/standby_flush work # without a walreceiver. These exercises the replay-position floor in @@ -862,18 +858,14 @@ $arc_primary->poll_query_until('postgres', # Start background waiters. With replay paused, target > replay, so they # will sleep on WaitLatch. They can only be woken by the replay-loop # WaitLSNWakeup calls. -my $arc_write_session = $arc_standby->background_psql('postgres'); -$arc_write_session->query_until( - qr/start/, qq[ - \\echo start +my $arc_write_session = PostgreSQL::Test::Session->new(node => $arc_standby); +$arc_write_session->do_async(qq[ WAIT FOR LSN '${arc_target_lsn2}' WITH (MODE 'standby_write', timeout '1d', no_throw); ]); -my $arc_flush_session = $arc_standby->background_psql('postgres'); -$arc_flush_session->query_until( - qr/start/, qq[ - \\echo start +my $arc_flush_session = PostgreSQL::Test::Session->new(node => $arc_standby); +$arc_flush_session->do_async(qq[ WAIT FOR LSN '${arc_target_lsn2}' WITH (MODE 'standby_flush', timeout '1d', no_throw); ]); @@ -887,15 +879,15 @@ $arc_standby->poll_query_until('postgres', # STANDBY_FLUSH waiters as it replays past arc_target_lsn2. $arc_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume()"); -$arc_write_session->quit; -$arc_flush_session->quit; -chomp($arc_write_session->{stdout}); -chomp($arc_flush_session->{stdout}); +my $arc_write_out = $arc_write_session->get_async_result(); +my $arc_flush_out = $arc_flush_session->get_async_result(); +$arc_write_session->close; +$arc_flush_session->close; -is($arc_write_session->{stdout}, +is($arc_write_out->{psqlout}, 'success', "standby_write waiter woken by replay on archive-only standby"); -is($arc_flush_session->{stdout}, +is($arc_flush_out->{psqlout}, 'success', "standby_flush waiter woken by replay on archive-only standby"); @@ -1035,10 +1027,8 @@ check_wait_for_lsn_fencepost($rcv_standby, 'standby_flush', $flush_lsn, $rcv_primary->safe_psql('postgres', "INSERT INTO rcv_test VALUES (generate_series(200, 210))"); -my $boundary_session = $rcv_standby->background_psql('postgres'); -$boundary_session->query_until( - qr/start/, qq[ - \\echo start +my $boundary_session = PostgreSQL::Test::Session->new(node => $rcv_standby); +$boundary_session->do_async(qq[ WAIT FOR LSN '${replay_lsn_plus}' WITH (MODE 'standby_replay', timeout '1d', no_throw); ]); @@ -1049,9 +1039,9 @@ $rcv_standby->poll_query_until('postgres', $rcv_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume()"); resume_walreceiver($rcv_standby); -$boundary_session->quit; -chomp($boundary_session->{stdout}); -is($boundary_session->{stdout}, +my $boundary_out = $boundary_session->get_async_result(); +$boundary_session->close; +is($boundary_out->{psqlout}, 'success', "standby_replay: waiter at current + 1 wakes when replay advances"); @@ -1101,10 +1091,8 @@ $tl_standby2->poll_query_until('postgres', "SELECT pg_get_wal_replay_pause_state() = 'paused'") or die "Timed out waiting for tl_standby2 replay to pause"; -my $tl_session = $tl_standby2->background_psql('postgres'); -$tl_session->query_until( - qr/start/, qq[ - \\echo start +my $tl_session = PostgreSQL::Test::Session->new(node => $tl_standby2); +$tl_session->do_async(qq[ WAIT FOR LSN '${tl_target}' WITH (MODE 'standby_replay', timeout '1d', no_throw); ]); @@ -1126,9 +1114,9 @@ $tl_standby2->poll_query_until('postgres', "SELECT received_tli > 1 FROM pg_stat_wal_receiver") or die "tl_standby2 did not follow upstream timeline switch"; -$tl_session->quit; -chomp($tl_session->{stdout}); -is($tl_session->{stdout}, 'success', +my $tl_out = $tl_session->get_async_result(); +$tl_session->close; +is($tl_out->{psqlout}, 'success', "WAIT FOR LSN survives upstream promotion and timeline switch on cascade standby" ); diff --git a/src/test/recovery/t/050_redo_segment_missing.pl b/src/test/recovery/t/050_redo_segment_missing.pl index e07ff0c72fe..b492db44684 100644 --- a/src/test/recovery/t/050_redo_segment_missing.pl +++ b/src/test/recovery/t/050_redo_segment_missing.pl @@ -7,6 +7,7 @@ use strict; use warnings FATAL => 'all'; use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Session; use PostgreSQL::Test::Utils; use Test::More; @@ -44,15 +45,11 @@ $node->safe_psql('postgres', $node->safe_psql('postgres', q{select injection_points_attach('create-checkpoint-run', 'wait')}); -# Start a psql session to run the checkpoint in the background and make +# Start a session to run the checkpoint in the background and make # the test wait on the injection point so the checkpoint stops just after # it starts. -my $checkpoint = $node->background_psql('postgres'); -$checkpoint->query_until( - qr/starting_checkpoint/, - q(\echo starting_checkpoint -checkpoint; -)); +my $checkpoint = PostgreSQL::Test::Session->new(node => $node); +$checkpoint->do_async(q(CHECKPOINT;)); # Wait for the initial point to finish, the checkpointer is still # outside its critical section. Then release to reach the second @@ -76,7 +73,8 @@ $node->safe_psql('postgres', q{select injection_points_wakeup('create-checkpoint-run')}); $node->wait_for_log(qr/checkpoint complete/, $log_offset); -$checkpoint->quit; +$checkpoint->wait_for_completion; +$checkpoint->close; # Retrieve the WAL file names for the redo record and checkpoint record. my $redo_lsn = $node->safe_psql('postgres', diff --git a/src/test/recovery/t/051_effective_wal_level.pl b/src/test/recovery/t/051_effective_wal_level.pl index c862073c34e..5c68949f80c 100644 --- a/src/test/recovery/t/051_effective_wal_level.pl +++ b/src/test/recovery/t/051_effective_wal_level.pl @@ -6,6 +6,7 @@ use strict; use warnings FATAL => 'all'; use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Session; use PostgreSQL::Test::Utils; use Test::More; @@ -382,20 +383,15 @@ if ( $ENV{enable_injection_points} eq 'yes' test_wal_level($primary, "replica|replica", "effective_wal_level got decreased to 'replica' on primary"); - # Start a psql session to test the case where the activation process is + # Start a session to test the case where the activation process is # interrupted. - my $psql_create_slot = $primary->background_psql('postgres'); + my $psql_create_slot = PostgreSQL::Test::Session->new(node => $primary); - # Start the logical decoding activation process upon creating the logical - # slot, but it will wait due to the injection point. - $psql_create_slot->query_until( - qr/create_slot_canceled/, - q(\echo create_slot_canceled -select injection_points_set_local(); -select injection_points_attach('logical-decoding-activation', 'wait'); -select pg_create_logical_replication_slot('slot_canceled', 'pgoutput'); -\q -)); + # Set up the injection point in this session (using set_local so it only + # affects this session), then start the slot creation which will block. + $psql_create_slot->do(q{select injection_points_set_local()}); + $psql_create_slot->do(q{select injection_points_attach('logical-decoding-activation', 'wait')}); + $psql_create_slot->do_async(q{select pg_create_logical_replication_slot('slot_canceled', 'pgoutput')}); $primary->wait_for_event('client backend', 'logical-decoding-activation'); note("injection_point 'logical-decoding-activation' is reached"); @@ -412,6 +408,9 @@ select pg_cancel_backend(pid) from pg_stat_activity where query ~ 'slot_canceled $primary->wait_for_log("aborting logical decoding activation process"); test_wal_level($primary, "replica|replica", "the activation process aborted"); + + # Clean up the session (the async query was cancelled, so we just close) + $psql_create_slot->close; } $primary->stop; diff --git a/src/test/subscription/t/015_stream.pl b/src/test/subscription/t/015_stream.pl index ac96bc3f009..41765faea34 100644 --- a/src/test/subscription/t/015_stream.pl +++ b/src/test/subscription/t/015_stream.pl @@ -5,6 +5,7 @@ use strict; use warnings FATAL => 'all'; use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Session; use PostgreSQL::Test::Utils; use Test::More; @@ -30,18 +31,17 @@ sub test_streaming # Interleave a pair of transactions, each exceeding the 64kB limit. my $offset = 0; - my $h = $node_publisher->background_psql('postgres', on_error_stop => 0); + my $h = PostgreSQL::Test::Session->new(node=>$node_publisher); # Check the subscriber log from now on. $offset = -s $node_subscriber->logfile; - $h->query_safe( - q{ - BEGIN; - INSERT INTO test_tab SELECT i, sha256(i::text::bytea) FROM generate_series(3, 5000) s(i); - UPDATE test_tab SET b = sha256(b) WHERE mod(a,2) = 0; - DELETE FROM test_tab WHERE mod(a,3) = 0; - }); + $h->do( + 'BEGIN', + 'INSERT INTO test_tab SELECT i, sha256(i::text::bytea) FROM generate_series(3, 5000) s(i)', + 'UPDATE test_tab SET b = sha256(b) WHERE mod(a,2) = 0', + 'DELETE FROM test_tab WHERE mod(a,3) = 0', + ); $node_publisher->safe_psql( 'postgres', q{ @@ -51,9 +51,9 @@ sub test_streaming COMMIT; }); - $h->query_safe('COMMIT'); + $h->do('COMMIT'); # errors make the next test fail, so ignore them here - $h->quit; + $h->close; $node_publisher->wait_for_catchup($appname); @@ -211,14 +211,14 @@ $node_subscriber->reload; $node_subscriber->safe_psql('postgres', q{SELECT 1}); # Interleave a pair of transactions, each exceeding the 64kB limit. -my $h = $node_publisher->background_psql('postgres', on_error_stop => 0); +my $h = PostgreSQL::Test::Session->new(node => $node_publisher); # Confirm if a deadlock between the leader apply worker and the parallel apply # worker can be detected. my $offset = -s $node_subscriber->logfile; -$h->query_safe( +$h->do( q{ BEGIN; INSERT INTO test_tab_2 SELECT i FROM generate_series(1, 5000) s(i); @@ -232,8 +232,8 @@ $node_subscriber->wait_for_log( $node_publisher->safe_psql('postgres', "INSERT INTO test_tab_2 values(1)"); -$h->query_safe('COMMIT'); -$h->quit; +$h->do('COMMIT'); +$h->close; $node_subscriber->wait_for_log(qr/ERROR: ( [A-Z0-9]+:)? deadlock detected/, $offset); @@ -260,7 +260,8 @@ $node_subscriber->safe_psql('postgres', # Check the subscriber log from now on. $offset = -s $node_subscriber->logfile; -$h->query_safe( +$h->reconnect; +$h->do( q{ BEGIN; INSERT INTO test_tab_2 SELECT i FROM generate_series(1, 5000) s(i); @@ -275,8 +276,8 @@ $node_subscriber->wait_for_log( $node_publisher->safe_psql('postgres', "INSERT INTO test_tab_2 SELECT i FROM generate_series(1, 5000) s(i)"); -$h->query_safe('COMMIT'); -$h->quit; +$h->do('COMMIT'); +$h->close; $node_subscriber->wait_for_log(qr/ERROR: ( [A-Z0-9]+:)? deadlock detected/, $offset); diff --git a/src/test/subscription/t/035_conflicts.pl b/src/test/subscription/t/035_conflicts.pl index f23fe6af2a5..8058beeb680 100644 --- a/src/test/subscription/t/035_conflicts.pl +++ b/src/test/subscription/t/035_conflicts.pl @@ -4,6 +4,7 @@ use strict; use warnings FATAL => 'all'; use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Session; use PostgreSQL::Test::Utils; use Test::More; @@ -455,17 +456,14 @@ if ($injection_points_supported != 0) # Start a background session on the publisher node to perform an update and # pause at the injection point. - my $pub_session = $node_B->background_psql('postgres'); - $pub_session->query_until( - qr/starting_bg_psql/, - q{ - \echo starting_bg_psql - BEGIN; - UPDATE tab SET b = 2 WHERE a = 1; - PREPARE TRANSACTION 'txn_with_later_commit_ts'; - COMMIT PREPARED 'txn_with_later_commit_ts'; - } + my $pub_session = PostgreSQL::Test::Session->new(node => $node_B); + $pub_session->do( + q{BEGIN}, + q{UPDATE tab SET b = 2 WHERE a = 1}, + q{PREPARE TRANSACTION 'txn_with_later_commit_ts'} ); + # COMMIT PREPARED will block on the injection point + $pub_session->do_async(q{COMMIT PREPARED 'txn_with_later_commit_ts'}); # Wait until the backend enters the injection point $node_B->wait_for_event('client backend', @@ -516,8 +514,9 @@ if ($injection_points_supported != 0) SELECT injection_points_detach('commit-after-delay-checkpoint');" ); - # Close the background session on the publisher node - ok($pub_session->quit, "close publisher session"); + # Wait for the async query to complete and close the background session + $pub_session->wait_for_completion; + $pub_session->close; # Confirm that the transaction committed $result = $node_B->safe_psql('postgres', 'SELECT * FROM tab WHERE a = 1'); diff --git a/src/test/subscription/t/038_walsnd_shutdown_timeout.pl b/src/test/subscription/t/038_walsnd_shutdown_timeout.pl index f4ed5d97852..48caf8e3da4 100644 --- a/src/test/subscription/t/038_walsnd_shutdown_timeout.pl +++ b/src/test/subscription/t/038_walsnd_shutdown_timeout.pl @@ -8,6 +8,7 @@ use strict; use warnings FATAL => 'all'; use PostgreSQL::Test::Cluster; use PostgreSQL::Test::Utils; +use PostgreSQL::Test::Session; use Test::More; use Time::HiRes qw(usleep); @@ -45,7 +46,7 @@ $node_subscriber->wait_for_subscription_sync($node_publisher, 'test_sub'); # Start a background session on the subscriber to run a transaction later # that will block the logical apply worker on a lock. -my $sub_session = $node_subscriber->background_psql('postgres'); +my $sub_session = PostgreSQL::Test::Session->new(node => $node_subscriber); # Test that when the logical apply worker is blocked on a lock and replication # is stalled, shutting down the publisher causes the logical walsender to exit @@ -53,7 +54,7 @@ my $sub_session = $node_subscriber->background_psql('postgres'); # Cause the logical apply worker to block on a lock by running conflicting # transactions on the publisher and subscriber. -$sub_session->query_safe("BEGIN; INSERT INTO test_tab VALUES (0);"); +$sub_session->do("BEGIN; INSERT INTO test_tab VALUES (0);"); $node_publisher->safe_psql('postgres', "INSERT INTO test_tab VALUES (0);"); my $log_offset = -s $node_publisher->logfile; @@ -65,7 +66,7 @@ ok( $node_publisher->log_contains( $log_offset), "walsender exits due to wal_sender_shutdown_timeout"); -$sub_session->query_safe("ABORT;"); +$sub_session->do("ABORT;"); $node_publisher->start; $node_publisher->wait_for_catchup('test_sub'); @@ -79,7 +80,7 @@ $node_publisher->wait_for_catchup('test_sub'); # Run a transaction on the subscriber that blocks the logical apply worker # on a lock. -$sub_session->query_safe("BEGIN; LOCK TABLE test_tab IN EXCLUSIVE MODE;"); +$sub_session->do("BEGIN; LOCK TABLE test_tab IN EXCLUSIVE MODE;"); # Generate enough data to fill the logical walsender's output buffer. $node_publisher->safe_psql('postgres', @@ -117,7 +118,7 @@ ok( $node_publisher->log_contains( "walsender with full output buffer exits due to wal_sender_shutdown_timeout" ); -$sub_session->query_safe("ABORT;"); +$sub_session->do("ABORT;"); # The next test depends on Perl's `kill`, which apparently is not # portable to Windows. (It would be nice to use Test::More's `subtest`, @@ -167,7 +168,7 @@ $node_standby->start; # Cause the logical apply worker to block on a lock by running conflicting # transactions on the publisher and subscriber, stalling logical replication. $node_publisher->wait_for_catchup('test_sub'); -$sub_session->query_safe("BEGIN; LOCK TABLE test_tab IN EXCLUSIVE MODE;"); +$sub_session->do("BEGIN; LOCK TABLE test_tab IN EXCLUSIVE MODE;"); $node_publisher->safe_psql('postgres', "INSERT INTO test_tab VALUES (-1); "); # Cause the standby's walreceiver to be blocked with SIGSTOP signal, @@ -193,7 +194,7 @@ ok( $node_publisher->log_contains( ); kill 'CONT', $receiverpid; -$sub_session->quit; +$sub_session->close; $node_subscriber->stop('fast'); $node_standby->stop('fast'); -- 2.43.0