Skip to content

Commit d43837d

Browse files
committed
Add lock_timeout configuration parameter.
This GUC allows limiting the time spent waiting to acquire any one heavyweight lock. In support of this, improve the recently-added timeout infrastructure to permit efficiently enabling or disabling multiple timeouts at once. That reduces the performance hit from turning on lock_timeout, though it's still not zero. Zoltán Böszörményi, reviewed by Tom Lane, Stephen Frost, and Hari Babu
1 parent d2bef5f commit d43837d

File tree

16 files changed

+511
-116
lines changed

16 files changed

+511
-116
lines changed

doc/src/sgml/config.sgml

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5077,7 +5077,7 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
50775077
</indexterm>
50785078
<listitem>
50795079
<para>
5080-
Abort any statement that takes over the specified number of
5080+
Abort any statement that takes more than the specified number of
50815081
milliseconds, starting from the time the command arrives at the server
50825082
from the client. If <varname>log_min_error_statement</> is set to
50835083
<literal>ERROR</> or lower, the statement that timed out will also be
@@ -5086,8 +5086,42 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
50865086

50875087
<para>
50885088
Setting <varname>statement_timeout</> in
5089-
<filename>postgresql.conf</> is not recommended because it
5090-
affects all sessions.
5089+
<filename>postgresql.conf</> is not recommended because it would
5090+
affect all sessions.
5091+
</para>
5092+
</listitem>
5093+
</varlistentry>
5094+
5095+
<varlistentry id="guc-lock-timeout" xreflabel="lock_timeout">
5096+
<term><varname>lock_timeout</varname> (<type>integer</type>)</term>
5097+
<indexterm>
5098+
<primary><varname>lock_timeout</> configuration parameter</primary>
5099+
</indexterm>
5100+
<listitem>
5101+
<para>
5102+
Abort any statement that waits longer than the specified number of
5103+
milliseconds while attempting to acquire a lock on a table, index,
5104+
row, or other database object. The time limit applies separately to
5105+
each lock acquisition attempt. The limit applies both to explicit
5106+
locking requests (such as <command>LOCK TABLE</>, or <command>SELECT
5107+
FOR UPDATE</> without <literal>NOWAIT</>) and to implicitly-acquired
5108+
locks. If <varname>log_min_error_statement</> is set to
5109+
<literal>ERROR</> or lower, the statement that timed out will be
5110+
logged. A value of zero (the default) turns this off.
5111+
</para>
5112+
5113+
<para>
5114+
Unlike <varname>statement_timeout</>, this timeout can only occur
5115+
while waiting for locks. Note that if <varname>statement_timeout</>
5116+
is nonzero, it is rather pointless to set <varname>lock_timeout</> to
5117+
the same or larger value, since the statement timeout would always
5118+
trigger first.
5119+
</para>
5120+
5121+
<para>
5122+
Setting <varname>lock_timeout</> in
5123+
<filename>postgresql.conf</> is not recommended because it would
5124+
affect all sessions.
50915125
</para>
50925126
</listitem>
50935127
</varlistentry>

src/backend/postmaster/autovacuum.c

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -547,10 +547,11 @@ AutoVacLauncherMain(int argc, char *argv[])
547547
SetConfigOption("zero_damaged_pages", "false", PGC_SUSET, PGC_S_OVERRIDE);
548548

549549
/*
550-
* Force statement_timeout to zero to avoid a timeout setting from
551-
* preventing regular maintenance from being executed.
550+
* Force statement_timeout and lock_timeout to zero to avoid letting these
551+
* settings prevent regular maintenance from being executed.
552552
*/
553553
SetConfigOption("statement_timeout", "0", PGC_SUSET, PGC_S_OVERRIDE);
554+
SetConfigOption("lock_timeout", "0", PGC_SUSET, PGC_S_OVERRIDE);
554555

555556
/*
556557
* Force default_transaction_isolation to READ COMMITTED. We don't want
@@ -1573,10 +1574,11 @@ AutoVacWorkerMain(int argc, char *argv[])
15731574
SetConfigOption("zero_damaged_pages", "false", PGC_SUSET, PGC_S_OVERRIDE);
15741575

15751576
/*
1576-
* Force statement_timeout to zero to avoid a timeout setting from
1577-
* preventing regular maintenance from being executed.
1577+
* Force statement_timeout and lock_timeout to zero to avoid letting these
1578+
* settings prevent regular maintenance from being executed.
15781579
*/
15791580
SetConfigOption("statement_timeout", "0", PGC_SUSET, PGC_S_OVERRIDE);
1581+
SetConfigOption("lock_timeout", "0", PGC_SUSET, PGC_S_OVERRIDE);
15801582

15811583
/*
15821584
* Force default_transaction_isolation to READ COMMITTED. We don't want

src/backend/storage/ipc/standby.c

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -428,8 +428,15 @@ ResolveRecoveryConflictWithBufferPin(void)
428428
* Wake up at ltime, and check for deadlocks as well if we will be
429429
* waiting longer than deadlock_timeout
430430
*/
431-
enable_timeout_after(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout);
432-
enable_timeout_at(STANDBY_TIMEOUT, ltime);
431+
EnableTimeoutParams timeouts[2];
432+
433+
timeouts[0].id = STANDBY_TIMEOUT;
434+
timeouts[0].type = TMPARAM_AT;
435+
timeouts[0].fin_time = ltime;
436+
timeouts[1].id = STANDBY_DEADLOCK_TIMEOUT;
437+
timeouts[1].type = TMPARAM_AFTER;
438+
timeouts[1].delay_ms = DeadlockTimeout;
439+
enable_timeouts(timeouts, 2);
433440
}
434441

435442
/* Wait to be signaled by UnpinBuffer() */

src/backend/storage/lmgr/proc.c

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
/* GUC variables */
5656
int DeadlockTimeout = 1000;
5757
int StatementTimeout = 0;
58+
int LockTimeout = 0;
5859
bool log_lock_waits = false;
5960

6061
/* Pointer to this process's PGPROC and PGXACT structs, if any */
@@ -665,15 +666,27 @@ void
665666
LockErrorCleanup(void)
666667
{
667668
LWLockId partitionLock;
669+
DisableTimeoutParams timeouts[2];
668670

669671
AbortStrongLockAcquire();
670672

671673
/* Nothing to do if we weren't waiting for a lock */
672674
if (lockAwaited == NULL)
673675
return;
674676

675-
/* Turn off the deadlock timer, if it's still running (see ProcSleep) */
676-
disable_timeout(DEADLOCK_TIMEOUT, false);
677+
/*
678+
* Turn off the deadlock and lock timeout timers, if they are still
679+
* running (see ProcSleep). Note we must preserve the LOCK_TIMEOUT
680+
* indicator flag, since this function is executed before
681+
* ProcessInterrupts when responding to SIGINT; else we'd lose the
682+
* knowledge that the SIGINT came from a lock timeout and not an external
683+
* source.
684+
*/
685+
timeouts[0].id = DEADLOCK_TIMEOUT;
686+
timeouts[0].keep_indicator = false;
687+
timeouts[1].id = LOCK_TIMEOUT;
688+
timeouts[1].keep_indicator = true;
689+
disable_timeouts(timeouts, 2);
677690

678691
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
679692
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
@@ -1072,8 +1085,24 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable)
10721085
*
10731086
* By delaying the check until we've waited for a bit, we can avoid
10741087
* running the rather expensive deadlock-check code in most cases.
1088+
*
1089+
* If LockTimeout is set, also enable the timeout for that. We can save a
1090+
* few cycles by enabling both timeout sources in one call.
10751091
*/
1076-
enable_timeout_after(DEADLOCK_TIMEOUT, DeadlockTimeout);
1092+
if (LockTimeout > 0)
1093+
{
1094+
EnableTimeoutParams timeouts[2];
1095+
1096+
timeouts[0].id = DEADLOCK_TIMEOUT;
1097+
timeouts[0].type = TMPARAM_AFTER;
1098+
timeouts[0].delay_ms = DeadlockTimeout;
1099+
timeouts[1].id = LOCK_TIMEOUT;
1100+
timeouts[1].type = TMPARAM_AFTER;
1101+
timeouts[1].delay_ms = LockTimeout;
1102+
enable_timeouts(timeouts, 2);
1103+
}
1104+
else
1105+
enable_timeout_after(DEADLOCK_TIMEOUT, DeadlockTimeout);
10771106

10781107
/*
10791108
* If someone wakes us between LWLockRelease and PGSemaphoreLock,
@@ -1240,9 +1269,20 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable)
12401269
} while (myWaitStatus == STATUS_WAITING);
12411270

12421271
/*
1243-
* Disable the timer, if it's still running
1272+
* Disable the timers, if they are still running
12441273
*/
1245-
disable_timeout(DEADLOCK_TIMEOUT, false);
1274+
if (LockTimeout > 0)
1275+
{
1276+
DisableTimeoutParams timeouts[2];
1277+
1278+
timeouts[0].id = DEADLOCK_TIMEOUT;
1279+
timeouts[0].keep_indicator = false;
1280+
timeouts[1].id = LOCK_TIMEOUT;
1281+
timeouts[1].keep_indicator = false;
1282+
disable_timeouts(timeouts, 2);
1283+
}
1284+
else
1285+
disable_timeout(DEADLOCK_TIMEOUT, false);
12461286

12471287
/*
12481288
* Re-acquire the lock table's partition lock. We have to do this to hold

src/backend/tcop/postgres.c

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2883,7 +2883,22 @@ ProcessInterrupts(void)
28832883
(errcode(ERRCODE_QUERY_CANCELED),
28842884
errmsg("canceling authentication due to timeout")));
28852885
}
2886-
if (get_timeout_indicator(STATEMENT_TIMEOUT))
2886+
2887+
/*
2888+
* If LOCK_TIMEOUT and STATEMENT_TIMEOUT indicators are both set, we
2889+
* prefer to report the former; but be sure to clear both.
2890+
*/
2891+
if (get_timeout_indicator(LOCK_TIMEOUT, true))
2892+
{
2893+
ImmediateInterruptOK = false; /* not idle anymore */
2894+
(void) get_timeout_indicator(STATEMENT_TIMEOUT, true);
2895+
DisableNotifyInterrupt();
2896+
DisableCatchupInterrupt();
2897+
ereport(ERROR,
2898+
(errcode(ERRCODE_QUERY_CANCELED),
2899+
errmsg("canceling statement due to lock timeout")));
2900+
}
2901+
if (get_timeout_indicator(STATEMENT_TIMEOUT, true))
28872902
{
28882903
ImmediateInterruptOK = false; /* not idle anymore */
28892904
DisableNotifyInterrupt();

src/backend/utils/init/postinit.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ static void CheckMyDatabase(const char *name, bool am_superuser);
6767
static void InitCommunication(void);
6868
static void ShutdownPostgres(int code, Datum arg);
6969
static void StatementTimeoutHandler(void);
70+
static void LockTimeoutHandler(void);
7071
static bool ThereIsAtLeastOneRole(void);
7172
static void process_startup_options(Port *port, bool am_superuser);
7273
static void process_settings(Oid databaseid, Oid roleid);
@@ -535,6 +536,7 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username,
535536
{
536537
RegisterTimeout(DEADLOCK_TIMEOUT, CheckDeadLock);
537538
RegisterTimeout(STATEMENT_TIMEOUT, StatementTimeoutHandler);
539+
RegisterTimeout(LOCK_TIMEOUT, LockTimeoutHandler);
538540
}
539541

540542
/*
@@ -1052,6 +1054,22 @@ StatementTimeoutHandler(void)
10521054
kill(MyProcPid, SIGINT);
10531055
}
10541056

1057+
/*
1058+
* LOCK_TIMEOUT handler: trigger a query-cancel interrupt.
1059+
*
1060+
* This is identical to StatementTimeoutHandler, but since it's so short,
1061+
* we might as well keep the two functions separate for clarity.
1062+
*/
1063+
static void
1064+
LockTimeoutHandler(void)
1065+
{
1066+
#ifdef HAVE_SETSID
1067+
/* try to signal whole process group */
1068+
kill(-MyProcPid, SIGINT);
1069+
#endif
1070+
kill(MyProcPid, SIGINT);
1071+
}
1072+
10551073

10561074
/*
10571075
* Returns true if at least one role is defined in this database cluster.

src/backend/utils/misc/guc.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1861,6 +1861,17 @@ static struct config_int ConfigureNamesInt[] =
18611861
NULL, NULL, NULL
18621862
},
18631863

1864+
{
1865+
{"lock_timeout", PGC_USERSET, CLIENT_CONN_STATEMENT,
1866+
gettext_noop("Sets the maximum allowed duration of any wait for a lock."),
1867+
gettext_noop("A value of 0 turns off the timeout."),
1868+
GUC_UNIT_MS
1869+
},
1870+
&LockTimeout,
1871+
0, 0, INT_MAX,
1872+
NULL, NULL, NULL
1873+
},
1874+
18641875
{
18651876
{"vacuum_freeze_min_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
18661877
gettext_noop("Minimum age at which VACUUM should freeze a table row."),

src/backend/utils/misc/postgresql.conf.sample

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,7 @@
489489
#default_transaction_deferrable = off
490490
#session_replication_role = 'origin'
491491
#statement_timeout = 0 # in milliseconds, 0 is disabled
492+
#lock_timeout = 0 # in milliseconds, 0 is disabled
492493
#vacuum_freeze_min_age = 50000000
493494
#vacuum_freeze_table_age = 150000000
494495
#bytea_output = 'hex' # hex, escape

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy