From 8a36bd692b9fb2ec6a514ad42f94bce06d8148f0 Mon Sep 17 00:00:00 2001 From: Andrey Borodin Date: Fri, 13 Nov 2026 11:37:15 +0500 Subject: [PATCH v1 2/4] Fix transaction timeout cleanup during GUC restoration An aborting subtransaction is not in IsTransactionState(), so the assign hook failed to disable a timer when GUC restoration reset the timeout to zero. Transaction end then skipped disabling it too, allowing the timer to affect the next transaction. Restoring a positive value after disabling the timer in a savepoint also failed to re-enable it. Allow the assign hook to update the parent's timer during subtransaction abort, but do not start a timer during top-level transaction cleanup. Check actual timer activity when disabling it at transaction end. Back-patch to v17, where transaction_timeout was introduced. Reported-by: Nicke van Oorschot Discussion: https://postgr.es/m/19896-555a806fe6352359@postgresql.org --- src/backend/access/transam/xact.c | 6 +++--- src/backend/tcop/postgres.c | 18 ++++++++++-------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index 1227e3fbe24..4599aad5a90 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -2410,7 +2410,7 @@ CommitTransaction(void) s->parallelChildXact = false; /* should be false already */ /* Disable transaction timeout */ - if (TransactionTimeout > 0) + if (get_timeout_active(TRANSACTION_TIMEOUT)) disable_timeout(TRANSACTION_TIMEOUT, false); if (!is_parallel_worker) @@ -2694,7 +2694,7 @@ PrepareTransaction(void) s->state = TRANS_PREPARE; /* Disable transaction timeout */ - if (TransactionTimeout > 0) + if (get_timeout_active(TRANSACTION_TIMEOUT)) disable_timeout(TRANSACTION_TIMEOUT, false); prepared_at = GetCurrentTimestamp(); @@ -2877,7 +2877,7 @@ AbortTransaction(void) HOLD_INTERRUPTS(); /* Disable transaction timeout */ - if (TransactionTimeout > 0) + if (get_timeout_active(TRANSACTION_TIMEOUT)) disable_timeout(TRANSACTION_TIMEOUT, false); /* Make sure we have a valid memory context and resource owner */ diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index d6761aeea9f..e3bceac8379 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -3769,17 +3769,19 @@ check_log_stats(bool *newval, void **extra, GucSource source) void assign_transaction_timeout(int newval, void *extra) { - if (IsTransactionState()) + /* + * GUC restoration runs after the current subtransaction has entered + * TRANS_ABORT, but its parent's transaction timeout must still be + * updated. Do not enable a timer while restoring GUCs at top-level + * transaction end. + */ + if (newval > 0 && (IsTransactionState() || IsSubTransaction())) { - /* - * If transaction_timeout GUC has changed within the transaction block - * enable or disable the timer correspondingly. - */ - if (newval > 0 && !get_timeout_active(TRANSACTION_TIMEOUT)) + if (!get_timeout_active(TRANSACTION_TIMEOUT)) enable_timeout_after(TRANSACTION_TIMEOUT, newval); - else if (newval <= 0 && get_timeout_active(TRANSACTION_TIMEOUT)) - disable_timeout(TRANSACTION_TIMEOUT, false); } + else if (newval <= 0 && get_timeout_active(TRANSACTION_TIMEOUT)) + disable_timeout(TRANSACTION_TIMEOUT, false); } /* -- 2.50.1 (Apple Git-155)