Skip to content

25P03 — idle_in_transaction_session_timeout

PostgreSQL SQLSTATE 25P03: diagnosing and recovering from server termination of a session idle inside an open transaction.

25P03 — idle_in_transaction_session_timeout

At a glance

25P03 is a FATAL session-termination condition. It fires when a session is waiting for the next client query inside an open transaction longer than the effective idle_in_transaction_session_timeout. The selected case uses 300 ms as a harness trigger, not as a production recommendation.

Field Value
SQLSTATE 25P03
Condition idle_in_transaction_session_timeout
Status active
Known present by 9.6.0
Locked snapshots 9.6.24, 10.23, 11.22, 12.22, 13.23, 14.24, 15.19, 16.15, 17.11, 18.6, 19beta3
Macros ERRCODE_IDLE_IN_TRANSACTION_SESSION_TIMEOUT
Aliases

Meaning

This timeout protects the server from sessions that keep an open transaction while waiting for the client; it can retain locks and delay cleanup. It applies while the session is idle in the transaction, including the idle in transaction and idle in transaction (aborted) states, not while an active statement is running. statement_timeout cancels a statement, while transaction_timeout covers the total transaction lifetime on versions that support it. The selected run covers the non-aborted INTRANS case only.

Diagnosis

Before the session disappears, capture its effective SHOW idle_in_transaction_session_timeout (or the matching pg_settings row) on that timed session itself. A control connection’s SHOW or pg_settings describes the observer backend and cannot establish another session’s effective SET value. From the control connection, inspect pg_stat_activity fields such as pid, usename, application_name, client_addr, state, xact_start, state_change, query_start, and query for state IN ('idle in transaction', 'idle in transaction (aborted)'). If the state is already aborted, preserve the earlier root error and 25P02 as separate diagnostics; the selected run is the non-aborted INTRANS path. These identify the pool or application path and how long it has been idle; permissions can limit what an observer sees. Correlate the FATAL record by backend PID, SQLSTATE, error_severity = FATAL, and exact message. The selected psycopg/libpq stack surfaced 25P03 in the driver diagnostic and CSV; PG18 also supplied JSON. The original connection is closed, so its transaction status cannot be repaired with ROLLBACK.

Response

Treat the session as gone: discard it from the pool and reconnect before retrying idempotent work. Prevent recurrence by committing or rolling back before returning a connection to the pool, and by fixing the application path that leaves a transaction open. Do not lower this timeout to suppress the error; a shorter value makes termination more likely. Choose an effective value above legitimate idle periods, or set it to 0 only when the deployment deliberately accepts the lock and cleanup risk. When the server closes this session, it rolls back the open incomplete transaction before exiting; the dead connection cannot accept a later ROLLBACK. A known uncommitted transaction on this terminated session cannot be recovered through that connection. If a separate network failure occurred after a COMMIT may have been sent, reconcile the business result from a fresh connection before retrying; that uncertainty is not implied by every 25P03 FATAL.

FATAL message

The server source emits terminating connection due to idle-in-transaction timeout at FATAL severity; the original connection is terminated.

Observed diagnostics

18.6 (Homebrew) / latest: FATAL SQLSTATE 25P03; primary terminating connection due to idle-in-transaction timeout; backend PID 26447; original connection closed True; collector CSV 25P03; collector JSON 25P03; fresh probe 1. 10.21 (Debian 10.21-1.pgdg90+1) / pg10: FATAL SQLSTATE 25P03; primary terminating connection due to idle-in-transaction timeout; backend PID 81; original connection closed True; collector CSV 25P03; collector JSON not applicable; fresh probe 1.

Representative case

The SQL excerpt below is not a paste-all script: run the setting, PID, and BEGIN on a test connection, then stop sending it queries while a separate observer or log collector waits for FATAL; after the old connection is terminated, open a fresh owner connection for the final probe. The runner reads these statements from the shared registry; complete assertions, collector correlation, environment, and cleanup are in case export.

-- set_timeout
SET idle_in_transaction_session_timeout = '300ms';
-- backend_pid
SELECT pg_backend_pid();
-- begin
BEGIN;
-- probe
SELECT 1;

The SQLSTATE, diagnostics, states, and repair assertions for this excerpt come from the shared registry (SHA-256 95fd079c8bca77c6e3ffc398e404a218e4f810e5fa36e30127cbc6470a6d6eb2); structured evidence.

Authored evidence IDs: identity, timeout-path, runtime. Selected runtime records: runtime.25P03-batch2c-latest-20260909.latest, runtime.25P03-batch2c-pg10-20260909.pg10.

Versions and limits

The selected 300 ms termination case passes on PostgreSQL 18.6 and 10.21. PG18 has CSV and JSON collector records; PG10 has CSV only under its configured logging target. Both fresh owner connections run SELECT 1 successfully. The timeout value is deployment-specific; the case does not prescribe it.

25P04 transaction timeout, 25006 read-only SQL transaction, 57014 query canceled.

Sources