diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 68811f1f21721bd3c8c20fef131c3195f5d4bb72..b185c1b5eb69fba2654b65a8b80bfe74f03ff600 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -2909,6 +2909,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
@@ -2929,17 +2932,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 3b3f220e6e6802c55f6df6ca1f5d14c4d41f5fbd..7171a7c59ce63ad59b7b76a4a018e98dfb759934 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 f64921e2d676fa62713e1353235cbed9113eadf7..260c0139609eda6aea53a1cf65a0adfffa673198 100644
--- a/src/include/utils/timeout.h
+++ b/src/include/utils/timeout.h
@@ -82,5 +82,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 */