From d29eccc7c73d6e1c804c5673eb02d4c0ce841b03 Mon Sep 17 00:00:00 2001 From: Zsolt Parragi Date: Tue, 29 Sep 2026 00:11:58 +0000 Subject: [PATCH v2] isolationtester: Report lost connection as step result isolationtester exits when PQconsumeInput() fails. On Windows the FATAL of a terminated backend can get lost, and the connection error then aborts the permutation, skipping teardown. libpq already treats a dead connection as not busy, so let the step complete with the connection error instead. Add alternative outputs for the tests that terminate a backend. --- .../expected/slot_creation_error_1.out | 114 +++++++++++++++++ .../expected/temp-schema-cleanup_1.out | 119 ++++++++++++++++++ src/test/isolation/isolationtester.c | 7 +- .../expected/wait_cleanup_1.out | 87 +++++++++++++ 4 files changed, 324 insertions(+), 3 deletions(-) create mode 100644 contrib/test_decoding/expected/slot_creation_error_1.out create mode 100644 src/test/isolation/expected/temp-schema-cleanup_1.out create mode 100644 src/test/modules/injection_points/expected/wait_cleanup_1.out diff --git a/contrib/test_decoding/expected/slot_creation_error_1.out b/contrib/test_decoding/expected/slot_creation_error_1.out new file mode 100644 index 00000000000..041cc547fb3 --- /dev/null +++ b/contrib/test_decoding/expected/slot_creation_error_1.out @@ -0,0 +1,114 @@ +Parsed test spec with 2 sessions + +starting permutation: s1_b s1_xid s2_init s1_view_slot s1_cancel_s2 s1_view_slot s1_c +step s1_b: BEGIN; +step s1_xid: SELECT 'xid' FROM txid_current(); +?column? +-------- +xid +(1 row) + +step s2_init: + SELECT 'init' FROM pg_create_logical_replication_slot('slot_creation_error', 'test_decoding'); + +step s1_view_slot: + SELECT slot_name, slot_type, active FROM pg_replication_slots WHERE slot_name = 'slot_creation_error' + +slot_name |slot_type|active +-------------------+---------+------ +slot_creation_error|logical |t +(1 row) + +step s1_cancel_s2: + SELECT pg_cancel_backend(pid) + FROM pg_stat_activity + WHERE application_name = 'isolation/slot_creation_error/s2'; + +step s2_init: <... completed> +ERROR: canceling statement due to user request +step s1_cancel_s2: <... completed> +pg_cancel_backend +----------------- +t +(1 row) + +step s1_view_slot: + SELECT slot_name, slot_type, active FROM pg_replication_slots WHERE slot_name = 'slot_creation_error' + +slot_name|slot_type|active +---------+---------+------ +(0 rows) + +step s1_c: COMMIT; + +starting permutation: s1_b s1_xid s2_init s1_c s1_view_slot s1_drop_slot +step s1_b: BEGIN; +step s1_xid: SELECT 'xid' FROM txid_current(); +?column? +-------- +xid +(1 row) + +step s2_init: + SELECT 'init' FROM pg_create_logical_replication_slot('slot_creation_error', 'test_decoding'); + +step s1_c: COMMIT; +step s2_init: <... completed> +?column? +-------- +init +(1 row) + +step s1_view_slot: + SELECT slot_name, slot_type, active FROM pg_replication_slots WHERE slot_name = 'slot_creation_error' + +slot_name |slot_type|active +-------------------+---------+------ +slot_creation_error|logical |f +(1 row) + +step s1_drop_slot: + SELECT pg_drop_replication_slot('slot_creation_error'); + +pg_drop_replication_slot +------------------------ + +(1 row) + + +starting permutation: s1_b s1_xid s2_init s1_terminate_s2 s1_c s1_view_slot +step s1_b: BEGIN; +step s1_xid: SELECT 'xid' FROM txid_current(); +?column? +-------- +xid +(1 row) + +step s2_init: + SELECT 'init' FROM pg_create_logical_replication_slot('slot_creation_error', 'test_decoding'); + +step s1_terminate_s2: + SELECT pg_terminate_backend(pid) + FROM pg_stat_activity + WHERE application_name = 'isolation/slot_creation_error/s2'; + +step s2_init: <... completed> +server closed the connection unexpectedly + This probably means the server terminated abnormally + before or while processing the request. +invalid socket + +step s1_terminate_s2: <... completed> +pg_terminate_backend +-------------------- +t +(1 row) + +step s1_c: COMMIT; +step s1_view_slot: + SELECT slot_name, slot_type, active FROM pg_replication_slots WHERE slot_name = 'slot_creation_error' + +slot_name|slot_type|active +---------+---------+------ +(0 rows) + diff --git a/src/test/isolation/expected/temp-schema-cleanup_1.out b/src/test/isolation/expected/temp-schema-cleanup_1.out new file mode 100644 index 00000000000..5ac3054e6eb --- /dev/null +++ b/src/test/isolation/expected/temp-schema-cleanup_1.out @@ -0,0 +1,119 @@ +Parsed test spec with 2 sessions + +starting permutation: s1_create_temp_objects s1_discard_temp s2_check_schema +step s1_create_temp_objects: + + -- create function large enough to be toasted, to ensure we correctly clean those up, a prior bug + -- https://postgr.es/m/CAOFAq3BU5Mf2TTvu8D9n_ZOoFAeQswuzk7yziAb7xuw_qyw5gw%40mail.gmail.com + SELECT exec(format($outer$ + CREATE OR REPLACE FUNCTION pg_temp.long() RETURNS text LANGUAGE sql AS $body$ SELECT %L; $body$$outer$, + (SELECT string_agg(g.i::text||':'||random()::text, '|') FROM generate_series(1, 100) g(i)))); + + -- The above bug requires function removal to happen after a catalog + -- invalidation. dependency.c sorts objects in descending oid order so + -- that newer objects are deleted before older objects, so create a + -- table after. + CREATE TEMPORARY TABLE invalidate_catalog_cache(); + + -- test non-temp function is dropped when depending on temp table + CREATE TEMPORARY TABLE just_give_me_a_type(id serial primary key); + + CREATE FUNCTION uses_a_temp_type(just_give_me_a_type) RETURNS int LANGUAGE sql AS $$SELECT 1;$$; + +exec +---- + +(1 row) + +s1: NOTICE: function "uses_a_temp_type" will be effectively temporary +DETAIL: It depends on temporary type just_give_me_a_type. +step s1_discard_temp: + DISCARD TEMP; + +step s2_check_schema: + SELECT oid::regclass FROM pg_class WHERE relnamespace = (SELECT oid FROM s1_temp_schema); + SELECT oid::regproc FROM pg_proc WHERE pronamespace = (SELECT oid FROM s1_temp_schema); + SELECT oid::regproc FROM pg_type WHERE typnamespace = (SELECT oid FROM s1_temp_schema); + +oid +--- +(0 rows) + +oid +--- +(0 rows) + +oid +--- +(0 rows) + + +starting permutation: s1_advisory s2_advisory s1_create_temp_objects s1_exit s2_check_schema +step s1_advisory: + SELECT pg_advisory_lock('pg_namespace'::regclass::int8); + +pg_advisory_lock +---------------- + +(1 row) + +step s2_advisory: + SELECT pg_advisory_lock('pg_namespace'::regclass::int8); + +step s1_create_temp_objects: + + -- create function large enough to be toasted, to ensure we correctly clean those up, a prior bug + -- https://postgr.es/m/CAOFAq3BU5Mf2TTvu8D9n_ZOoFAeQswuzk7yziAb7xuw_qyw5gw%40mail.gmail.com + SELECT exec(format($outer$ + CREATE OR REPLACE FUNCTION pg_temp.long() RETURNS text LANGUAGE sql AS $body$ SELECT %L; $body$$outer$, + (SELECT string_agg(g.i::text||':'||random()::text, '|') FROM generate_series(1, 100) g(i)))); + + -- The above bug requires function removal to happen after a catalog + -- invalidation. dependency.c sorts objects in descending oid order so + -- that newer objects are deleted before older objects, so create a + -- table after. + CREATE TEMPORARY TABLE invalidate_catalog_cache(); + + -- test non-temp function is dropped when depending on temp table + CREATE TEMPORARY TABLE just_give_me_a_type(id serial primary key); + + CREATE FUNCTION uses_a_temp_type(just_give_me_a_type) RETURNS int LANGUAGE sql AS $$SELECT 1;$$; + +exec +---- + +(1 row) + +s1: NOTICE: function "uses_a_temp_type" will be effectively temporary +DETAIL: It depends on temporary type just_give_me_a_type. +step s1_exit: + SELECT pg_terminate_backend(pg_backend_pid()); + +server closed the connection unexpectedly + This probably means the server terminated abnormally + before or while processing the request. +invalid socket + +step s2_advisory: <... completed> +pg_advisory_lock +---------------- + +(1 row) + +step s2_check_schema: + SELECT oid::regclass FROM pg_class WHERE relnamespace = (SELECT oid FROM s1_temp_schema); + SELECT oid::regproc FROM pg_proc WHERE pronamespace = (SELECT oid FROM s1_temp_schema); + SELECT oid::regproc FROM pg_type WHERE typnamespace = (SELECT oid FROM s1_temp_schema); + +oid +--- +(0 rows) + +oid +--- +(0 rows) + +oid +--- +(0 rows) + diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index e64dc020b18..66241a633c5 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -851,7 +851,7 @@ try_complete_step(TestSpec *testspec, PermutationStep *pstep, int flags) } } - if (sock < 0) + if (sock < 0 && PQstatus(conn) != CONNECTION_BAD) { fprintf(stderr, "invalid socket: %s", PQerrorMessage(conn)); exit(1); @@ -908,7 +908,7 @@ try_complete_step(TestSpec *testspec, PermutationStep *pstep, int flags) * returns false, we might as well go examine the * available result. */ - if (!PQconsumeInput(conn)) + if (!PQconsumeInput(conn) && PQstatus(conn) != CONNECTION_BAD) { fprintf(stderr, "PQconsumeInput failed: %s\n", PQerrorMessage(conn)); @@ -977,7 +977,8 @@ try_complete_step(TestSpec *testspec, PermutationStep *pstep, int flags) exit(1); } } - else if (!PQconsumeInput(conn)) /* select(): data available */ + else if (!PQconsumeInput(conn) && + PQstatus(conn) != CONNECTION_BAD) /* select(): data available */ { fprintf(stderr, "PQconsumeInput failed: %s\n", PQerrorMessage(conn)); diff --git a/src/test/modules/injection_points/expected/wait_cleanup_1.out b/src/test/modules/injection_points/expected/wait_cleanup_1.out new file mode 100644 index 00000000000..10da6bd4fcd --- /dev/null +++ b/src/test/modules/injection_points/expected/wait_cleanup_1.out @@ -0,0 +1,87 @@ +Parsed test spec with 3 sessions + +starting permutation: wait1 cancel3 noop3 wait2 wakeup3 noop2 detach3 +injection_points_attach +----------------------- + +(1 row) + +step wait1: SELECT injection_points_run('injection-points-wait'); +step cancel3: + SELECT pg_cancel_backend(pid) FROM pg_stat_activity + WHERE wait_event = 'injection-points-wait'; + +step wait1: <... completed> +ERROR: canceling statement due to user request +step cancel3: <... completed> +pg_cancel_backend +----------------- +t +(1 row) + +step noop3: +step wait2: SELECT injection_points_run('injection-points-wait'); +step wakeup3: SELECT injection_points_wakeup('injection-points-wait'); +injection_points_wakeup +----------------------- + +(1 row) + +step wait2: <... completed> +injection_points_run +-------------------- + +(1 row) + +step noop2: +step detach3: SELECT injection_points_detach('injection-points-wait'); +injection_points_detach +----------------------- + +(1 row) + + +starting permutation: wait1 terminate3 noop3 wait2 wakeup3 noop2 detach3 +injection_points_attach +----------------------- + +(1 row) + +step wait1: SELECT injection_points_run('injection-points-wait'); +step terminate3: + SELECT pg_terminate_backend(pid) FROM pg_stat_activity + WHERE wait_event = 'injection-points-wait'; + +step wait1: <... completed> +server closed the connection unexpectedly + This probably means the server terminated abnormally + before or while processing the request. +invalid socket + +step terminate3: <... completed> +pg_terminate_backend +-------------------- +t +(1 row) + +step noop3: +step wait2: SELECT injection_points_run('injection-points-wait'); +step wakeup3: SELECT injection_points_wakeup('injection-points-wait'); +injection_points_wakeup +----------------------- + +(1 row) + +step wait2: <... completed> +injection_points_run +-------------------- + +(1 row) + +step noop2: +step detach3: SELECT injection_points_detach('injection-points-wait'); +injection_points_detach +----------------------- + +(1 row) + -- 2.55.0