diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index caecf6c002c..f78321d7540 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -2920,6 +2920,9 @@ ProcessInterrupts(void) if (QueryCancelPending) { + bool lock_timeout_occurred; + bool stmt_timeout_occurred; + /* * Don't allow query cancel interrupts while reading input from the * client, because we might lose sync in the FE/BE protocol. (Die @@ -2940,17 +2943,29 @@ ProcessInterrupts(void) /* * If LOCK_TIMEOUT and STATEMENT_TIMEOUT indicators are both set, we - * prefer to report the former; but be sure to clear both. + * need to clear both, so always fetch both. */ - if (get_timeout_indicator(LOCK_TIMEOUT, true)) + lock_timeout_occurred = get_timeout_indicator(LOCK_TIMEOUT, true); + stmt_timeout_occurred = get_timeout_indicator(STATEMENT_TIMEOUT, true); + + /* + * If both were set, we want to report whichever timeout completed + * earlier; this ensures consistent behavior if the machine is slow + * enough that the second timeout triggers before we get here. A tie + * is arbitrarily broken in favor of reporting a lock timeout. + */ + if (lock_timeout_occurred && stmt_timeout_occurred && + get_timeout_finish_time(STATEMENT_TIMEOUT) < get_timeout_finish_time(LOCK_TIMEOUT)) + lock_timeout_occurred = false; /* report stmt timeout */ + + if (lock_timeout_occurred) { - (void) get_timeout_indicator(STATEMENT_TIMEOUT, true); LockErrorCleanup(); ereport(ERROR, (errcode(ERRCODE_LOCK_NOT_AVAILABLE), errmsg("canceling statement due to lock timeout"))); } - if (get_timeout_indicator(STATEMENT_TIMEOUT, true)) + if (stmt_timeout_occurred) { LockErrorCleanup(); ereport(ERROR, diff --git a/src/backend/utils/misc/timeout.c b/src/backend/utils/misc/timeout.c index a7ec665f6c5..1574488a2ee 100644 --- a/src/backend/utils/misc/timeout.c +++ b/src/backend/utils/misc/timeout.c @@ -34,7 +34,7 @@ typedef struct timeout_params timeout_handler_proc timeout_handler; TimestampTz start_time; /* time that timeout was last activated */ - TimestampTz fin_time; /* if active, time it is due to fire */ + TimestampTz fin_time; /* time it is, or was last, due to fire */ } timeout_params; /* @@ -654,3 +654,17 @@ get_timeout_start_time(TimeoutId id) { return all_timeouts[id].start_time; } + +/* + * Return the time when the timeout is, or most recently was, due to fire + * + * Note: will return 0 if timeout has never been activated in this process. + * However, we do *not* reset the fin_time when a timeout occurs, so as + * not to create a race condition if SIGALRM fires just as some code is + * about to fetch the value. + */ +TimestampTz +get_timeout_finish_time(TimeoutId id) +{ + return all_timeouts[id].fin_time; +} diff --git a/src/include/utils/timeout.h b/src/include/utils/timeout.h index 30581a1ba4f..148d91efe83 100644 --- a/src/include/utils/timeout.h +++ b/src/include/utils/timeout.h @@ -80,5 +80,6 @@ extern void disable_all_timeouts(bool keep_indicators); /* accessors */ extern bool get_timeout_indicator(TimeoutId id, bool reset_indicator); extern TimestampTz get_timeout_start_time(TimeoutId id); +extern TimestampTz get_timeout_finish_time(TimeoutId id); #endif /* TIMEOUT_H */