Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
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)