On Sun, Aug 23, 2026, Andrey Borodin <[email protected]> wrote: > The attached patch makes isolationtester treat PQconsumeInput() failure with > CONNECTION_BAD as completion of the step.
Thanks Andrey. CF 6992 hit the same symptom in slot_creation_error on Windows. Attached are three follow-ups on top of your v1. There is another case with a notice blocker: try_complete_step() can return while the step is still blocked, losing the saved connection error. The next attempt then fails with "invalid socket". The fix keeps the error in IsoConnInfo until the step completes. The other patches add the alternate slot_creation_error output and extend wait_cleanup to cover this case. With the extended test and the final ErrorResponse suppressed, v1 fails before release3/detach3; the fixed version completes both. The suppression is test-only and leaves the server's FATAL in the log. It does not force Windows transport loss. The Windows run below passed core regression/isolation, test_decoding, injection_points isolation and subscription TAP (one ICU-dependent skip), with your v1 plus these patches. The workflow includes setup, the missing ErrorResponse test and old-driver controls: https://github.com/NikolayS/postgres/actions/runs/35770093308 https://github.com/NikolayS/postgres/blob/8039ee47313f7ac860d26bd6fd9892c0ac296fee/.github/workflows/cf6992-windows-validation.yml AI found the blocker case and prepared these follow-up patches. I have not manually reviewed these patches. Nik
From 30c1a14b115299ee10a43a3497f4de763ddf8e4e Mon Sep 17 00:00:00 2001 From: Nik Samokhvalov <[email protected]> Date: Tue, 22 Sep 2026 08:21:30 -0700 Subject: [PATCH] test_decoding: accept lost FATAL message on backend termination With isolationtester able to finish a step after connection loss, accept the Windows case where the final ErrorResponse is not received. Keep all subsequent checks, including slot cleanup, in the expected output. --- .../expected/slot_creation_error_1.out | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 contrib/test_decoding/expected/slot_creation_error_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..2922022ce32 --- /dev/null +++ b/contrib/test_decoding/expected/slot_creation_error_1.out @@ -0,0 +1,113 @@ +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'); + <waiting ...> +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'; + <waiting ...> +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'); + <waiting ...> +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'); + <waiting ...> +step s1_terminate_s2: + SELECT pg_terminate_backend(pid) + FROM pg_stat_activity + WHERE application_name = 'isolation/slot_creation_error/s2'; + <waiting ...> +step s2_init: <... completed> +server closed the connection unexpectedly + This probably means the server terminated abnormally + before or while processing the request. + +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) + -- 2.50.1 (Apple Git-155)
From 0dda85366844bb48157980992f47d19a68152537 Mon Sep 17 00:00:00 2001 From: Nik Samokhvalov <[email protected]> Date: Tue, 22 Sep 2026 11:32:09 -0700 Subject: [PATCH] isolationtester: test connection loss with an active blocker Wait for the terminated backend to exit before releasing the notice blocker. This makes the missing-error case exercise retaining a connection error across scheduler retries. --- .../expected/wait_cleanup.out | 19 ++++++++++--------- .../expected/wait_cleanup_1.out | 17 +++++++++-------- .../injection_points/specs/wait_cleanup.spec | 16 ++++++++++------ 3 files changed, 29 insertions(+), 23 deletions(-) diff --git a/src/test/modules/injection_points/expected/wait_cleanup.out b/src/test/modules/injection_points/expected/wait_cleanup.out index c5be17428fc..9c60ecfb2d4 100644 --- a/src/test/modules/injection_points/expected/wait_cleanup.out +++ b/src/test/modules/injection_points/expected/wait_cleanup.out @@ -41,7 +41,7 @@ injection_points_detach (1 row) -starting permutation: wait1 terminate3 noop3 wait2 wakeup3 noop2 detach3 +starting permutation: wait1 terminate3 noop3 release3 wait2 wakeup3 noop2 detach3 injection_points_attach ----------------------- @@ -49,22 +49,23 @@ injection_points_attach step wait1: SELECT injection_points_run('injection-points-wait'); <waiting ...> step terminate3: - SELECT pg_terminate_backend(pid) FROM pg_stat_activity + SELECT pg_terminate_backend(pid, 180000) FROM pg_stat_activity WHERE wait_event = 'injection-points-wait'; - <waiting ...> -step wait1: <... completed> -FATAL: terminating connection due to administrator command -server closed the connection unexpectedly - This probably means the server terminated abnormally - before or while processing the request. -step terminate3: <... completed> pg_terminate_backend -------------------- t (1 row) step noop3: +s3: NOTICE: release wait1 +step release3: DO $$BEGIN RAISE NOTICE 'release wait1'; END$$; +step wait1: <... completed> +FATAL: terminating connection due to administrator command +server closed the connection unexpectedly + This probably means the server terminated abnormally + before or while processing the request. + step wait2: SELECT injection_points_run('injection-points-wait'); <waiting ...> step wakeup3: SELECT injection_points_wakeup('injection-points-wait'); injection_points_wakeup diff --git a/src/test/modules/injection_points/expected/wait_cleanup_1.out b/src/test/modules/injection_points/expected/wait_cleanup_1.out index 516a428b364..71ae751cbee 100644 --- a/src/test/modules/injection_points/expected/wait_cleanup_1.out +++ b/src/test/modules/injection_points/expected/wait_cleanup_1.out @@ -41,7 +41,7 @@ injection_points_detach (1 row) -starting permutation: wait1 terminate3 noop3 wait2 wakeup3 noop2 detach3 +starting permutation: wait1 terminate3 noop3 release3 wait2 wakeup3 noop2 detach3 injection_points_attach ----------------------- @@ -49,21 +49,22 @@ injection_points_attach step wait1: SELECT injection_points_run('injection-points-wait'); <waiting ...> step terminate3: - SELECT pg_terminate_backend(pid) FROM pg_stat_activity + SELECT pg_terminate_backend(pid, 180000) FROM pg_stat_activity WHERE wait_event = 'injection-points-wait'; - <waiting ...> -step wait1: <... completed> -server closed the connection unexpectedly - This probably means the server terminated abnormally - before or while processing the request. -step terminate3: <... completed> pg_terminate_backend -------------------- t (1 row) step noop3: +s3: NOTICE: release wait1 +step release3: DO $$BEGIN RAISE NOTICE 'release wait1'; END$$; +step wait1: <... completed> +server closed the connection unexpectedly + This probably means the server terminated abnormally + before or while processing the request. + step wait2: SELECT injection_points_run('injection-points-wait'); <waiting ...> step wakeup3: SELECT injection_points_wakeup('injection-points-wait'); injection_points_wakeup diff --git a/src/test/modules/injection_points/specs/wait_cleanup.spec b/src/test/modules/injection_points/specs/wait_cleanup.spec index ed7d21c4de4..fa482f2422c 100644 --- a/src/test/modules/injection_points/specs/wait_cleanup.spec +++ b/src/test/modules/injection_points/specs/wait_cleanup.spec @@ -26,9 +26,9 @@ session s2 step wait2 { SELECT injection_points_run('injection-points-wait'); } step noop2 { } -# Control session. The blocker annotations on cancel3/terminate3, -# together with noop3, make the tester wait until wait1 has fully -# completed before starting wait2. Otherwise, wait2 could register a +# Control session. The blocker on cancel3 and the notice from release3 +# make the tester wait until wait1 has fully completed before starting +# wait2. Otherwise, wait2 could register a # new waiter slot while s1 still owns the previous one. session s3 step cancel3 { @@ -36,15 +36,19 @@ step cancel3 { WHERE wait_event = 'injection-points-wait'; } step terminate3 { - SELECT pg_terminate_backend(pid) FROM pg_stat_activity + SELECT pg_terminate_backend(pid, 180000) FROM pg_stat_activity WHERE wait_event = 'injection-points-wait'; } step wakeup3 { SELECT injection_points_wakeup('injection-points-wait'); } step detach3 { SELECT injection_points_detach('injection-points-wait'); } +step release3 { DO $$BEGIN RAISE NOTICE 'release wait1'; END$$; } step noop3 { } permutation wait1 cancel3(wait1) noop3 wait2 wakeup3 noop2 detach3 # The terminate permutation has to stay last: s1's connection is dead -# afterwards, and the tester never reconnects a session. -permutation wait1 terminate3(wait1) noop3 wait2 wakeup3 noop2 detach3 +# afterwards, and the tester never reconnects a session. Delay reporting +# wait1 until release3 sends its notice, even if the connection is already +# closed, to exercise retaining the connection error across step retries. +# terminate3 waits for backend exit before the notice blocker is released. +permutation wait1(release3 notices 1) terminate3 noop3 release3 wait2 wakeup3 noop2 detach3 -- 2.50.1 (Apple Git-155)
From ce461649028aba48133acd7a8270c4fb1145fdd0 Mon Sep 17 00:00:00 2001 From: Nik Samokhvalov <[email protected]> Date: Tue, 22 Sep 2026 08:25:16 -0700 Subject: [PATCH] isolationtester: retain connection errors across blocked steps Keep the saved error with the active connection until the step can be reported. A blocked step may be retried after its socket has closed. Drain all complete buffered results before reporting the saved connection error. --- src/test/isolation/isolationtester.c | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 31e7341a67a..b7e39c48672 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -33,6 +33,8 @@ typedef struct IsoConnInfo const char *sessionname; /* Active step on this connection, or NULL if idle. */ PermutationStep *active_step; + /* Connection error retained while an active step has blockers. */ + char *connection_error; /* Number of NOTICE messages received from connection. */ int total_notices; } IsoConnInfo; @@ -828,7 +830,6 @@ try_complete_step(TestSpec *testspec, PermutationStep *pstep, int flags) PGresult *res; PGnotify *notify; bool canceled = false; - char *connection_error = NULL; /* * If the step is annotated with (*), then on the first call, force it to @@ -852,7 +853,7 @@ try_complete_step(TestSpec *testspec, PermutationStep *pstep, int flags) } } - if (sock < 0) + if (sock < 0 && !iconn->connection_error) { fprintf(stderr, "invalid socket: %s", PQerrorMessage(conn)); exit(1); @@ -861,7 +862,7 @@ try_complete_step(TestSpec *testspec, PermutationStep *pstep, int flags) gettimeofday(&start_time, NULL); FD_ZERO(&read_set); - while (PQisBusy(conn)) + while (!iconn->connection_error && PQisBusy(conn)) { FD_SET(sock, &read_set); timeout.tv_sec = 0; @@ -922,7 +923,7 @@ try_complete_step(TestSpec *testspec, PermutationStep *pstep, int flags) * Save the error before PQgetResult() adds another complaint * about attempting to read from the dead socket. */ - connection_error = pg_strdup(PQerrorMessage(conn)); + iconn->connection_error = pg_strdup(PQerrorMessage(conn)); break; } if (!PQisBusy(conn)) @@ -1001,7 +1002,7 @@ try_complete_step(TestSpec *testspec, PermutationStep *pstep, int flags) * Save the error before PQgetResult() adds another complaint about * attempting to read from the dead socket. */ - connection_error = pg_strdup(PQerrorMessage(conn)); + iconn->connection_error = pg_strdup(PQerrorMessage(conn)); break; } } @@ -1049,7 +1050,7 @@ try_complete_step(TestSpec *testspec, PermutationStep *pstep, int flags) if (sev && msg) printf("%s: %s\n", sev, msg); - else if (!connection_error) + else if (!iconn->connection_error) printf("%s\n", PQresultErrorMessage(res)); } break; @@ -1058,16 +1059,13 @@ try_complete_step(TestSpec *testspec, PermutationStep *pstep, int flags) PQresStatus(PQresultStatus(res))); } PQclear(res); - - /* The connection is dead, so don't ask libpq for another result. */ - if (connection_error) - break; } - if (connection_error) + if (iconn->connection_error) { - printf("%s\n", connection_error); - pg_free(connection_error); + printf("%s\n", iconn->connection_error); + pg_free(iconn->connection_error); + iconn->connection_error = NULL; } /* Report any available NOTIFY messages, too */ -- 2.50.1 (Apple Git-155)
