BUG #19896: transaction_timeout misbehaves after ROLLBACK TO SAVEPOINT and after errors

From: PG Bug reporting form <noreply(at)postgresql(dot)org>
To: pgsql-bugs(at)lists(dot)postgresql(dot)org
Cc: nickevanoorschot(at)gmail(dot)com
Subject: BUG #19896: transaction_timeout misbehaves after ROLLBACK TO SAVEPOINT and after errors
Date: 2026-11-12 12:41:55
Message-ID: 19896-555a806fe6352359@postgresql.org
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-bugs

The following bug has been logged on the website:

Bug reference: 19896
Logged by: Nicke van Oorschot
Email address: nickevanoorschot(at)gmail(dot)com
PostgreSQL version: 18.6
Operating system: Ubuntu 22.04.5 LTS
Description:

PostgreSQL versions: 18.6; 20devel (development snapshot
postgresql-snapshot.tar.bz2 of
2026-11-12 04:22 UTC, master at debfb3c8a8c93e7bcc3638523b7ffc0846cc2db8).
First observed
on 18.1 ("PostgreSQL 18.1 on x86_64-pc-linux-musl, compiled by gcc (Alpine
15.2.0) 15.2.0, 64-bit").

Documentation of transaction_timeout: "Terminate any session that spans
longer than the
specified amount of time in a transaction." The cases below differ from
that. Each script was
run with "psql -X -f <file>" against a freshly initialised cluster with
default settings.
Output below is from 18.6; 20devel gives the same output apart from the
LOCATION line numbers.

Case 1: a timeout set inside a rolled-back savepoint terminates a later
transaction

--- case1.sql
\set VERBOSITY verbose
-- Transaction 1 sets transaction_timeout inside a savepoint and rolls the
savepoint back.
BEGIN;
SELECT now() AS transaction_1_start;
SAVEPOINT s;
SET LOCAL transaction_timeout = '2s';
ROLLBACK TO SAVEPOINT s;
SHOW transaction_timeout;
COMMIT;
-- Transaction 2 sets a 10 s transaction_timeout and runs for 3 s.
BEGIN;
SELECT now() AS transaction_2_start;
SET LOCAL transaction_timeout = '10s';
SELECT pg_sleep(3);
COMMIT;
SELECT 'transaction 2 committed' AS result;
---

Output:
BEGIN
transaction_1_start
-------------------------------
2026-11-12 11:33:03.520214+00
(1 row)

SAVEPOINT
SET
ROLLBACK
transaction_timeout
---------------------
0
(1 row)

COMMIT
BEGIN
transaction_2_start
-------------------------------
2026-11-12 11:33:03.520757+00
(1 row)

SET
psql:case1.sql:14: FATAL: 25P04: terminating connection due to transaction
timeout
LOCATION: ProcessInterrupts, postgres.c:3498
psql:case1.sql:14: server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
psql:case1.sql:14: error: connection to server was lost

Server log:
2026-11-12 11:33:05.520 UTC [297561] FATAL: terminating connection due to
transaction timeout
2026-11-12 11:33:05.520 UTC [297561] STATEMENT: SELECT pg_sleep(3);

The connection is terminated 2 s after transaction 1 started, while
transaction 2 has run for
less than 2 s with a 10 s transaction_timeout.

Expected: transaction 2 completes and "transaction 2 committed" is returned.
Transaction 1
ended with transaction_timeout = 0, and transaction 2 stays within its 10 s.

Case 2: after an error and ROLLBACK TO SAVEPOINT, the transaction is not
terminated

--- case2.sql
\set VERBOSITY verbose
-- The transaction sets a 2 s transaction_timeout, recovers from an error
with ROLLBACK TO SAVEPOINT, and runs for 3 s.
BEGIN;
SET LOCAL transaction_timeout = '2s';
SAVEPOINT s;
SELECT 1/0;
ROLLBACK TO SAVEPOINT s;
SHOW transaction_timeout;
SELECT pg_sleep(3);
COMMIT;
SELECT 'transaction committed after 3 s' AS result;
---

Output:
BEGIN
SET
SAVEPOINT
psql:case2.sql:6: ERROR: 22012: division by zero
LOCATION: int4div, int.c:870
ROLLBACK
transaction_timeout
---------------------
2s
(1 row)

pg_sleep
----------

(1 row)

COMMIT
result
---------------------------------
transaction committed after 3 s
(1 row)

Expected: the connection is terminated 2 s into the transaction, during
pg_sleep(3), and the
transaction does not commit.

The same script with "SELECT 1;" instead of "SELECT 1/0;" is terminated as
expected:
psql:case2_control.sql:8: FATAL: 25P04: terminating connection due to
transaction timeout

Setting the timeout for the session ("SET transaction_timeout = '2s';"
before BEGIN, no SET
LOCAL) gives the same result as case 2: the transaction commits after 3 s.

Case 3: an aborted transaction is not terminated

--- case3.sql
\set VERBOSITY verbose
-- The transaction sets a 2 s transaction_timeout, fails, and stays open
(aborted) for 3 s.
BEGIN;
SET LOCAL transaction_timeout = '2s';
SELECT 1/0;
\! sleep 3
ROLLBACK;
SELECT 'connection still open after 3 s' AS result;
---

Output:
BEGIN
SET
psql:case3.sql:5: ERROR: 22012: division by zero
LOCATION: int4div, int.c:870
ROLLBACK
result
---------------------------------
connection still open after 3 s
(1 row)

Expected: the connection is terminated 2 s into the transaction, while it is
open in the
aborted state.

Environment of the 18.6 and 20devel runs:
- Built from the source tarballs with
./configure --without-readline --without-zlib --without-icu
--without-openssl; make; make install
(no other deviation from the installation instructions)
- Cluster: initdb -A trust -U postgres, no changes to postgresql.conf
- Server started with: pg_ctl -o "-p <port> -k <socket dir> -c
listen_addresses=''" start
- psql from the same build, run with -X (no .psqlrc)
- OS: Ubuntu 22.04.5 LTS, Linux 7.0.0-43-generic x86_64, glibc 2.35
- Compiler: gcc (Ubuntu 11.4.0-1ubuntu1~22.04.3) 11.4.0
- CPU: AMD Ryzen 9 7940HS (16 threads), 53 GB RAM

Browse pgsql-bugs by date

  From Date Subject
Next Message Daniel Gustafsson 2026-11-12 12:44:57 Re: BUG #19714: pgcrypto pgp_sym_encrypt accepts nonnumeric s2k-mode as mode 0
Previous Message shveta malik 2026-11-12 12:16:16 Re: BUG #19864: pg_get_publication_tables() gets cache lookup error after concurrent DROP PUBLICATION