2013-02-27 20:38 keltezéssel, Boszormenyi Zoltan írta:
2013-02-27 20:06 keltezéssel, Stephen Frost írta:
Zoltan,
* Boszormenyi Zoltan (z...@cybertec.at) wrote:
If we get rid of the per-statement variant, there is no need for that either.
For my 2c, I didn't see Tom's comments as saying that we shouldn't have
that capability, just that the implementation was ugly. :)
But I am happy to drop it. ;-)
That said, perhaps we should just drop it for now, get the lock_timeout
piece solid, and then come back to the question about lock_timeout_stmt.
OK, let's do it this way.
Dropped the per-statement lock timeout for now. The patch is
now obviously simpler and shorter. I renamed
enable/disable_multiple_timeouts() to simply enable/disable_timeouts()
since the List* argument implies more than one of them and
you need to type less.
The comments and the documentation needs another review,
to make sure I left no traces of the per-statements variant.
I can't see any but I stared at this patch for so long that I can't
be sure anymore.
Best regards,
Zoltán Böszörményi
--
----------------------------------
Zoltán Böszörményi
Cybertec Schönig & Schönig GmbH
Gröhrmühlgasse 26
A-2700 Wiener Neustadt, Austria
Web: http://www.postgresql-support.de
http://www.postgresql.at/
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 175d1d5..c124927 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -5078,7 +5078,10 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
milliseconds, starting from the time the command arrives at the server
from the client. If <varname>log_min_error_statement</> is set to
<literal>ERROR</> or lower, the statement that timed out will also be
- logged. A value of zero (the default) turns this off.
+ logged. The timeout may happen any time, i.e. while waiting for locks
+ on database objects or in case of a large result set, during data
+ retrieval from the server after all locks were successfully acquired.
+ A value of zero (the default) turns this off.
</para>
<para>
@@ -5089,6 +5092,33 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
</listitem>
</varlistentry>
+ <varlistentry id="guc-lock-timeout" xreflabel="lock_timeout">
+ <term><varname>lock_timeout</varname> (<type>integer</type>)</term>
+ <indexterm>
+ <primary><varname>lock_timeout</> configuration parameter</primary>
+ </indexterm>
+ <listitem>
+ <para>
+ Abort any statement which waits longer than the specified number of
+ milliseconds while attempting to acquire a lock on rows, pages,
+ tables, indices or other objects. The timeout applies to each of
+ the locks individually.
+ </para>
+ <para>
+ As opposed to <varname>statement_timeout</>, this timeout (and the error)
+ may only occur while waiting for locks. If <varname>log_min_error_statement</>
+ is set to <literal>ERROR</> or lower, the statement that timed out will
+ also be logged. A value of zero (the default) turns this off.
+ </para>
+
+ <para>
+ Setting <varname>lock_timeout</> in
+ <filename>postgresql.conf</> is not recommended because it
+ affects all sessions.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-vacuum-freeze-table-age" xreflabel="vacuum_freeze_table_age">
<term><varname>vacuum_freeze_table_age</varname> (<type>integer</type>)</term>
<indexterm>
diff --git a/doc/src/sgml/ref/lock.sgml b/doc/src/sgml/ref/lock.sgml
index 05acbc4..3de08d5 100644
--- a/doc/src/sgml/ref/lock.sgml
+++ b/doc/src/sgml/ref/lock.sgml
@@ -39,10 +39,12 @@ LOCK [ TABLE ] [ ONLY ] <replaceable class="PARAMETER">name</replaceable> [ * ]
<literal>NOWAIT</literal> is specified, <command>LOCK
TABLE</command> does not wait to acquire the desired lock: if it
cannot be acquired immediately, the command is aborted and an
- error is emitted. Once obtained, the lock is held for the
- remainder of the current transaction. (There is no <command>UNLOCK
- TABLE</command> command; locks are always released at transaction
- end.)
+ error is emitted. If <varname>lock_timeout</varname> is set and
+ the lock cannot be acquired under the specified timeout,
+ the command is aborted and an error is emitted. Once obtained,
+ the lock is held for the remainder of the current transaction.
+ (There is no <command>UNLOCK TABLE</command> command; locks are
+ always released at transaction end.)
</para>
<para>
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 0f9d527..0640898 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -1309,6 +1309,14 @@ FOR KEY SHARE [ OF <replaceable class="parameter">table_name</replaceable> [, ..
</para>
<para>
+ If <literal>NOWAIT</> option is not specified and either
+ <varname>statement_timeout</varname> or <varname>lock_timeout</varname>
+ is set and the lock or statement needs to wait more than the specified
+ timeout, the command reports an error after timing out, rather than
+ waiting until the lock becomes available.
+ </para>
+
+ <para>
If specific tables are named in a locking clause,
then only rows coming from those tables are locked; any other
tables used in the <command>SELECT</command> are simply read as
diff --git a/src/backend/port/posix_sema.c b/src/backend/port/posix_sema.c
index 061fd2d..6010460 100644
--- a/src/backend/port/posix_sema.c
+++ b/src/backend/port/posix_sema.c
@@ -313,3 +313,42 @@ PGSemaphoreTryLock(PGSemaphore sema)
return true;
}
+
+/*
+ * PGSemaphoreTimedLock
+ *
+ * Lock a semaphore (decrement count), blocking if count would be < 0
+ * until a timeout triggers. Returns true if the lock was acquired.
+ */
+bool
+PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK,
+ TimeoutCondition condition)
+{
+ int errStatus;
+ int saved_errno;
+ bool timeout;
+
+ /*
+ * See notes in sysv_sema.c's implementation of PGSemaphoreLock. Just as
+ * that code does for semop(), we handle both the case where sem_wait()
+ * returns errno == EINTR after a signal, and the case where it just keeps
+ * waiting. If EINTR came from a SIGALRM timeout source we were watching for,
+ * the loop exits.
+ */
+ do
+ {
+ ImmediateInterruptOK = interruptOK;
+ CHECK_FOR_INTERRUPTS();
+ errStatus = sem_wait(PG_SEM_REF(sema));
+ saved_errno = errno;
+ ImmediateInterruptOK = false;
+ timeout = condition();
+ errno = saved_errno;
+ } while (errStatus < 0 && errno == EINTR && !timeout);
+
+ if (timeout)
+ return false;
+ if (errStatus < 0)
+ elog(FATAL, "sem_wait failed: %m");
+ return true;
+}
diff --git a/src/backend/port/sysv_sema.c b/src/backend/port/sysv_sema.c
index 65f922e..faadc27 100644
--- a/src/backend/port/sysv_sema.c
+++ b/src/backend/port/sysv_sema.c
@@ -492,3 +492,46 @@ PGSemaphoreTryLock(PGSemaphore sema)
return true;
}
+
+/*
+ * PGSemaphoreTimedLock
+ *
+ * Lock a semaphore (decrement count), blocking if count would be < 0
+ * until a timeout triggers. Returns true if the lock was acquired.
+ */
+bool
+PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK,
+ TimeoutCondition condition)
+{
+ int errStatus;
+ int saved_errno;
+ bool timeout;
+ struct sembuf sops;
+
+ sops.sem_op = -1; /* decrement */
+ sops.sem_flg = 0;
+ sops.sem_num = sema->semNum;
+
+ /*
+ * Note: if errStatus is -1 and errno == EINTR then it means we returned
+ * from the operation prematurely because we were sent a signal. So we
+ * try and lock the semaphore again unless the signal came from a SIGALRM
+ * timeout source we were watching for.
+ */
+ do
+ {
+ ImmediateInterruptOK = interruptOK;
+ CHECK_FOR_INTERRUPTS();
+ errStatus = semop(sema->semId, &sops, 1);
+ saved_errno = errno;
+ ImmediateInterruptOK = false;
+ timeout = condition();
+ errno = saved_errno;
+ } while (errStatus < 0 && errno == EINTR && !timeout);
+
+ if (timeout)
+ return false;
+ if (errStatus < 0)
+ elog(FATAL, "semop(id=%d) failed: %m", sema->semId);
+ return true;
+}
diff --git a/src/backend/port/win32_sema.c b/src/backend/port/win32_sema.c
index dc5054b..318c393 100644
--- a/src/backend/port/win32_sema.c
+++ b/src/backend/port/win32_sema.c
@@ -209,3 +209,71 @@ PGSemaphoreTryLock(PGSemaphore sema)
/* keep compiler quiet */
return false;
}
+
+/*
+ * PGSemaphoreTimedLock
+ *
+ * Lock a semaphore (decrement count), blocking if count would be < 0
+ * until a timeout triggers. Serve the interrupt if interruptOK is true.
+ */
+bool
+PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK,
+ TimeoutCondition condition)
+{
+ DWORD ret;
+ HANDLE wh[2];
+ int saved_errno;
+ bool timeout;
+
+ /*
+ * Note: pgwin32_signal_event should be first to ensure that it will be
+ * reported when multiple events are set. We want to guarantee that
+ * pending signals are serviced.
+ */
+ wh[0] = pgwin32_signal_event;
+ wh[1] = *sema;
+
+ /*
+ * As in other implementations of PGSemaphoreLock, we need to check for
+ * cancel/die interrupts each time through the loop. But here, there is
+ * no hidden magic about whether the syscall will internally service a
+ * signal --- we do that ourselves.
+ *
+ * If EINTR came from a SIGALRM timeout source we were watching for,
+ * the loop exits.
+ */
+ do
+ {
+ ImmediateInterruptOK = interruptOK;
+ CHECK_FOR_INTERRUPTS();
+
+ ret = WaitForMultipleObjectsEx(2, wh, FALSE, INFINITE, TRUE);
+
+ if (ret == WAIT_OBJECT_0)
+ {
+ /* Signal event is set - we have a signal to deliver */
+ pgwin32_dispatch_queued_signals();
+ errno = EINTR;
+ }
+ else if (ret == WAIT_OBJECT_0 + 1)
+ {
+ /* We got it! */
+ errno = 0;
+ }
+ else
+ /* Otherwise we are in trouble */
+ errno = EIDRM;
+
+ saved_errno = errno;
+ ImmediateInterruptOK = false;
+ timeout = condition();
+ errno = saved_errno;
+ } while (errno == EINTR && !timeout);
+
+ if (timeout)
+ return false;
+ if (errno != 0)
+ ereport(FATAL,
+ (errmsg("could not lock semaphore: error code %lu", GetLastError())));
+ return true;
+}
diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c
index a903f12..44ae5ca 100644
--- a/src/backend/storage/ipc/standby.c
+++ b/src/backend/storage/ipc/standby.c
@@ -46,6 +46,10 @@ static void LogCurrentRunningXacts(RunningTransactions CurrRunningXacts);
static void LogAccessExclusiveLocks(int nlocks, xl_standby_lock *locks);
+static TimeoutParams standby_deadlock = { STANDBY_DEADLOCK_TIMEOUT, TMPARAM_AFTER };
+static TimeoutParams standby_timeout = { STANDBY_TIMEOUT, TMPARAM_AT };
+
+
/*
* InitRecoveryTransactionEnvironment
* Initialize tracking of in-progress transactions in master
@@ -424,12 +428,16 @@ ResolveRecoveryConflictWithBufferPin(void)
}
else
{
+ List *timeouts;
/*
* Wake up at ltime, and check for deadlocks as well if we will be
* waiting longer than deadlock_timeout
*/
- enable_timeout_after(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout);
- enable_timeout_at(STANDBY_TIMEOUT, ltime);
+ standby_deadlock.delay_ms = DeadlockTimeout;
+ standby_timeout.fin_time = ltime;
+ timeouts = list_make2(&standby_deadlock, &standby_timeout);
+ enable_timeouts(timeouts);
+ list_free(timeouts);
}
/* Wait to be signaled by UnpinBuffer() */
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index 2c1c652..26756a8 100644
--- a/src/backend/storage/lmgr/lock.c
+++ b/src/backend/storage/lmgr/lock.c
@@ -1568,6 +1568,7 @@ WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner)
LOCKMETHODID lockmethodid = LOCALLOCK_LOCKMETHOD(*locallock);
LockMethod lockMethodTable = LockMethods[lockmethodid];
char *volatile new_status = NULL;
+ int wait_status;
LOCK_PRINT("WaitOnLock: sleeping on lock",
locallock->lock, locallock->tag.mode);
@@ -1593,12 +1594,13 @@ WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner)
/*
* NOTE: Think not to put any shared-state cleanup after the call to
* ProcSleep, in either the normal or failure path. The lock state must
- * be fully set by the lock grantor, or by CheckDeadLock if we give up
- * waiting for the lock. This is necessary because of the possibility
- * that a cancel/die interrupt will interrupt ProcSleep after someone else
- * grants us the lock, but before we've noticed it. Hence, after granting,
- * the locktable state must fully reflect the fact that we own the lock;
- * we can't do additional work on return.
+ * be fully set by the lock grantor, or by CheckDeadLock or by ProcSleep
+ * itself in case a timeout is detected or if we give up waiting for the lock.
+ * This is necessary because of the possibility that a cancel/die interrupt
+ * will interrupt ProcSleep after someone else grants us the lock, but
+ * before we've noticed it. Hence, after granting, the locktable state must
+ * fully reflect the fact that we own the lock; we can't do additional work
+ * on return.
*
* We can and do use a PG_TRY block to try to clean up after failure, but
* this still has a major limitation: elog(FATAL) can occur while waiting
@@ -1609,8 +1611,10 @@ WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner)
*/
PG_TRY();
{
- if (ProcSleep(locallock, lockMethodTable) != STATUS_OK)
+ wait_status = ProcSleep(locallock, lockMethodTable);
+ switch (wait_status)
{
+ case STATUS_ERROR:
/*
* We failed as a result of a deadlock, see CheckDeadLock(). Quit
* now.
@@ -1626,6 +1630,20 @@ WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner)
*/
DeadLockReport();
/* not reached */
+ break;
+
+ case STATUS_WAITING:
+ /*
+ * We failed because of a timeout, see ProcSleep(). Quit now.
+ */
+ ereport(ERROR,
+ (errcode(ERRCODE_QUERY_CANCELED),
+ errmsg("canceling statement due to lock timeout")));
+ /* not reached */
+ break;
+
+ default:
+ break;
}
}
PG_CATCH();
@@ -1655,7 +1673,11 @@ WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner)
pfree(new_status);
}
- LOCK_PRINT("WaitOnLock: wakeup on lock",
+ if (wait_status == STATUS_OK)
+ LOCK_PRINT("WaitOnLock: wakeup on lock",
+ locallock->lock, locallock->tag.mode);
+ else if (wait_status == STATUS_WAITING)
+ LOCK_PRINT("WaitOnLock: timeout on lock",
locallock->lock, locallock->tag.mode);
}
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 2e012fa..6fc9121 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -55,8 +55,12 @@
/* GUC variables */
int DeadlockTimeout = 1000;
int StatementTimeout = 0;
+int LockTimeout = 0;
bool log_lock_waits = false;
+static TimeoutParams deadlock_params = { DEADLOCK_TIMEOUT, TMPARAM_AFTER };
+static TimeoutParams lock_params = { LOCK_TIMEOUT, TMPARAM_AFTER };
+
/* Pointer to this process's PGPROC and PGXACT structs, if any */
PGPROC *MyProc = NULL;
PGXACT *MyPgXact = NULL;
@@ -665,6 +669,7 @@ void
LockErrorCleanup(void)
{
LWLockId partitionLock;
+ List *timeouts;
AbortStrongLockAcquire();
@@ -672,8 +677,13 @@ LockErrorCleanup(void)
if (lockAwaited == NULL)
return;
- /* Turn off the deadlock timer, if it's still running (see ProcSleep) */
- disable_timeout(DEADLOCK_TIMEOUT, false);
+ /*
+ * Turn off the deadlock and lock timeout timers,
+ * if they are still running (see ProcSleep).
+ */
+ timeouts = list_make2(&deadlock_params, &lock_params);
+ disable_timeouts(timeouts);
+ list_free(timeouts);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
@@ -906,6 +916,16 @@ ProcQueueInit(PROC_QUEUE *queue)
/*
+ * LockTimeoutIndicator -- passed to PGSemaphoreTimedLock()
+ */
+static bool
+LockTimeoutIndicator(void)
+{
+ return get_timeout_indicator(LOCK_TIMEOUT);
+}
+
+
+/*
* ProcSleep -- put a process to sleep on the specified lock
*
* Caller must have set MyProc->heldLocks to reflect locks already held
@@ -914,7 +934,10 @@ ProcQueueInit(PROC_QUEUE *queue)
* The lock table's partition lock must be held at entry, and will be held
* at exit.
*
- * Result: STATUS_OK if we acquired the lock, STATUS_ERROR if not (deadlock).
+ * Result:
+ * STATUS_OK if we acquired the lock
+ * STATUS_WAITING if not because of a timeout
+ * STATUS_ERROR if not (deadlock).
*
* ASSUME: that no one will fiddle with the queue until after
* we release the partition lock.
@@ -936,6 +959,8 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable)
LOCKMASK myHeldLocks = MyProc->heldLocks;
bool early_deadlock = false;
bool allow_autovacuum_cancel = true;
+ List *timeouts;
+ bool timeout_detected = false;
int myWaitStatus;
PGPROC *proc;
int i;
@@ -1065,19 +1090,38 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable)
deadlock_state = DS_NOT_YET_CHECKED;
/*
- * Set timer so we can wake up after awhile and check for a deadlock. If a
- * deadlock is detected, the handler releases the process's semaphore and
- * sets MyProc->waitStatus = STATUS_ERROR, allowing us to know that we
- * must report failure rather than success.
+ * Set timers so:
+ * a) we can wake up after awhile and check for a deadlock. If a
+ * deadlock is detected, the handler releases the process's semaphore and
+ * sets MyProc->waitStatus = STATUS_ERROR, allowing us to know that we
+ * must report failure rather than success.
+ * b) detect lock timeout.
*
* By delaying the check until we've waited for a bit, we can avoid
* running the rather expensive deadlock-check code in most cases.
+ *
+ * If both timeout sources are set, then enable them all once.
*/
- enable_timeout_after(DEADLOCK_TIMEOUT, DeadlockTimeout);
+ deadlock_params.delay_ms = DeadlockTimeout;
+ timeouts = list_make1(&deadlock_params);
+ if (LockTimeout > 0)
+ {
+ lock_params.delay_ms = LockTimeout;
+ timeouts = lappend(timeouts, &lock_params);
+ }
+
+ if (enable_timeouts(timeouts))
+ {
+ list_free(timeouts);
+ LWLockAcquire(partitionLock, LW_EXCLUSIVE);
+ RemoveFromWaitQueue(MyProc, hashcode);
+
+ return (LockTimeoutIndicator() ? STATUS_WAITING : STATUS_ERROR);
+ }
/*
- * If someone wakes us between LWLockRelease and PGSemaphoreLock,
- * PGSemaphoreLock will not block. The wakeup is "saved" by the semaphore
+ * If someone wakes us between LWLockRelease and PGSemaphoreTimedLock,
+ * PGSemaphoreTimedLock will not block. The wakeup is "saved" by the semaphore
* implementation. While this is normally good, there are cases where a
* saved wakeup might be leftover from a previous operation (for example,
* we aborted ProcWaitForSignal just before someone did ProcSendSignal).
@@ -1094,7 +1138,11 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable)
*/
do
{
- PGSemaphoreLock(&MyProc->sem, true);
+ timeout_detected = !PGSemaphoreTimedLock(&MyProc->sem, true,
+ LockTimeoutIndicator);
+
+ if (timeout_detected)
+ break;
/*
* waitStatus could change from STATUS_WAITING to something else
@@ -1240,9 +1288,10 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable)
} while (myWaitStatus == STATUS_WAITING);
/*
- * Disable the timer, if it's still running
+ * Disable the timers, if they are still running
*/
- disable_timeout(DEADLOCK_TIMEOUT, false);
+ disable_timeouts(timeouts);
+ list_free(timeouts);
/*
* Re-acquire the lock table's partition lock. We have to do this to hold
@@ -1252,6 +1301,15 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable)
LWLockAcquire(partitionLock, LW_EXCLUSIVE);
/*
+ * If we're in timeout, so:
+ * 1. we're not waiting anymore and
+ * 2. we're not the one that the lock will be granted to,
+ * remove ourselves from the wait queue.
+ */
+ if (timeout_detected)
+ RemoveFromWaitQueue(MyProc, hashcode);
+
+ /*
* We no longer want LockErrorCleanup to do anything.
*/
lockAwaited = NULL;
@@ -1265,8 +1323,10 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable)
/*
* We don't have to do anything else, because the awaker did all the
* necessary update of the lock table and MyProc.
+ * RemoveFromWaitQueue() have set MyProc->waitStatus = STATUS_ERROR,
+ * we need to distinguish this case.
*/
- return MyProc->waitStatus;
+ return (timeout_detected ? STATUS_WAITING : MyProc->waitStatus);
}
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 8427006..aa8a312 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -66,6 +66,7 @@ static void PerformAuthentication(Port *port);
static void CheckMyDatabase(const char *name, bool am_superuser);
static void InitCommunication(void);
static void ShutdownPostgres(int code, Datum arg);
+static void LockTimeoutHandler(void);
static void StatementTimeoutHandler(void);
static bool ThereIsAtLeastOneRole(void);
static void process_startup_options(Port *port, bool am_superuser);
@@ -535,6 +536,7 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username,
{
RegisterTimeout(DEADLOCK_TIMEOUT, CheckDeadLock);
RegisterTimeout(STATEMENT_TIMEOUT, StatementTimeoutHandler);
+ RegisterTimeout(LOCK_TIMEOUT, LockTimeoutHandler);
}
/*
@@ -1040,6 +1042,16 @@ ShutdownPostgres(int code, Datum arg)
/*
+ * LOCK_TIMEOUT handler: nothing to do
+ */
+static void
+LockTimeoutHandler(void)
+{
+ return;
+}
+
+
+/*
* STATEMENT_TIMEOUT handler: trigger a query-cancel interrupt.
*/
static void
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 98149fc..03b1e17 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -1862,6 +1862,17 @@ static struct config_int ConfigureNamesInt[] =
},
{
+ {"lock_timeout", PGC_USERSET, CLIENT_CONN_STATEMENT,
+ gettext_noop("Sets the maximum allowed timeout for any lock taken by a statement."),
+ gettext_noop("A value of 0 turns off the timeout."),
+ GUC_UNIT_MS
+ },
+ &LockTimeout,
+ 0, 0, INT_MAX,
+ NULL, NULL, NULL
+ },
+
+ {
{"vacuum_freeze_min_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
gettext_noop("Minimum age at which VACUUM should freeze a table row."),
NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 62aea2f..01a99cf 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -532,6 +532,7 @@
#------------------------------------------------------------------------------
#deadlock_timeout = 1s
+#lock_timeout = 0
#max_locks_per_transaction = 64 # min 10
# (change requires restart)
# Note: Each lock table slot uses ~270 bytes of shared memory, and there are
diff --git a/src/backend/utils/misc/timeout.c b/src/backend/utils/misc/timeout.c
index 944c9a7..725638b 100644
--- a/src/backend/utils/misc/timeout.c
+++ b/src/backend/utils/misc/timeout.c
@@ -17,6 +17,7 @@
#include <sys/time.h>
#include "libpq/pqsignal.h"
+#include "nodes/pg_list.h"
#include "storage/proc.h"
#include "utils/timeout.h"
#include "utils/timestamp.h"
@@ -153,6 +154,30 @@ schedule_alarm(TimestampTz now)
}
}
+/*
+ * Disable alarm
+ *
+ * If num_active_timeouts is zero, we don't have to call setitimer. There
+ * should not be any pending interrupt, and even if there is, the worst
+ * possible case is that the signal handler fires during schedule_alarm.
+ * (If it fires at any point before insert_timeout has incremented
+ * num_active_timeouts, it will do nothing.) In that case we could end up
+ * scheduling a useless interrupt ... but when the interrupt does happen,
+ * the signal handler will do nothing, so it's all good.
+ */
+static void
+disable_alarm(void)
+{
+ struct itimerval timeval;
+
+ if (num_active_timeouts > 0)
+ {
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ if (setitimer(ITIMER_REAL, &timeval, NULL) != 0)
+ elog(FATAL, "could not disable SIGALRM timer: %m");
+ }
+}
+
/*****************************************************************************
* Signal handler
@@ -289,7 +314,6 @@ RegisterTimeout(TimeoutId id, timeout_handler handler)
static void
enable_timeout(TimeoutId id, TimestampTz now, TimestampTz fin_time)
{
- struct itimerval timeval;
int i;
/* Assert request is sane */
@@ -297,26 +321,6 @@ enable_timeout(TimeoutId id, TimestampTz now, TimestampTz fin_time)
Assert(all_timeouts[id].timeout_handler != NULL);
/*
- * Disable the timer if it is active; this avoids getting interrupted by
- * the signal handler and thereby possibly getting confused. We will
- * re-enable the interrupt below.
- *
- * If num_active_timeouts is zero, we don't have to call setitimer. There
- * should not be any pending interrupt, and even if there is, the worst
- * possible case is that the signal handler fires during schedule_alarm.
- * (If it fires at any point before insert_timeout has incremented
- * num_active_timeouts, it will do nothing.) In that case we could end up
- * scheduling a useless interrupt ... but when the interrupt does happen,
- * the signal handler will do nothing, so it's all good.
- */
- if (num_active_timeouts > 0)
- {
- MemSet(&timeval, 0, sizeof(struct itimerval));
- if (setitimer(ITIMER_REAL, &timeval, NULL) != 0)
- elog(FATAL, "could not disable SIGALRM timer: %m");
- }
-
- /*
* If this timeout was already active, momentarily disable it. We
* interpret the call as a directive to reschedule the timeout.
*/
@@ -345,19 +349,17 @@ enable_timeout(TimeoutId id, TimestampTz now, TimestampTz fin_time)
all_timeouts[id].start_time = now;
all_timeouts[id].fin_time = fin_time;
insert_timeout(id, i);
-
- /*
- * Set the timer.
- */
- schedule_alarm(now);
}
/*
* Enable the specified timeout to fire after the specified delay.
*
* Delay is given in milliseconds.
+ *
+ * Returns true if the timeout is to trigger in the past and indicate
+ * that the timeout has triggered. Leave previously set timeouts scheduled.
*/
-void
+bool
enable_timeout_after(TimeoutId id, int delay_ms)
{
TimestampTz now;
@@ -366,20 +368,145 @@ enable_timeout_after(TimeoutId id, int delay_ms)
now = GetCurrentTimestamp();
fin_time = TimestampTzPlusMilliseconds(now, delay_ms);
+ if (fin_time < now)
+ {
+ all_timeouts[id].indicator = true;
+ return true;
+ }
+
+ /*
+ * Disable the timer if it is active; this avoids getting interrupted by
+ * the signal handler and thereby possibly getting confused. We will
+ * re-enable the interrupt below.
+ */
+ disable_alarm();
+
enable_timeout(id, now, fin_time);
+
+ /*
+ * Set the timer.
+ */
+ schedule_alarm(now);
+
+ return false;
}
/*
* Enable the specified timeout to fire at the specified time.
+ * Return true if the specified time is in the past and indicate
+ * the timeout has triggered. Leave previously set timeouts scheduled.
*
* This is provided to support cases where there's a reason to calculate
* the timeout by reference to some point other than "now". If there isn't,
* use enable_timeout_after(), to avoid calling GetCurrentTimestamp() twice.
*/
-void
+bool
enable_timeout_at(TimeoutId id, TimestampTz fin_time)
{
- enable_timeout(id, GetCurrentTimestamp(), fin_time);
+ TimestampTz now = GetCurrentTimestamp();
+
+ if (fin_time < now)
+ {
+ all_timeouts[id].indicator = true;
+ return true;
+ }
+
+ /*
+ * Disable the timer if it is active; this avoids getting interrupted by
+ * the signal handler and thereby possibly getting confused. We will
+ * re-enable the interrupt below.
+ */
+ disable_alarm();
+
+ enable_timeout(id, now, fin_time);
+
+ /*
+ * Set the timer.
+ */
+ schedule_alarm(now);
+
+ return false;
+}
+
+/*
+ * Enable multiple timeouts at once.
+ *
+ * It works like calling enable_timeout_after() or enable_timeout_at()
+ * separately. This is provided to minimize the number of setitimer() calls.
+ *
+ * Returns true if any of the timeouts would have triggered in the past.
+ * In this case the new timeouts won't get scheduled.
+ * Returns false if the new timeouts were scheduled.
+ */
+bool
+enable_timeouts(List *timeouts)
+{
+ TimestampTz now;
+ ListCell *cell;
+
+ if (list_length(timeouts) == 0)
+ return false;
+
+ now = GetCurrentTimestamp();
+
+ /*
+ * Disable the timer if it is active; this avoids getting interrupted by
+ * the signal handler and thereby possibly getting confused. We will
+ * re-enable the interrupt below.
+ */
+ disable_alarm();
+
+ foreach(cell, timeouts)
+ {
+ TimeoutParams *to_ptr = (TimeoutParams *)lfirst(cell);
+ TimeoutId id = to_ptr->id;
+
+ switch (to_ptr->type)
+ {
+ case TMPARAM_AFTER:
+ Assert(to_ptr->delay_ms > 0);
+
+ to_ptr->fin_time = TimestampTzPlusMilliseconds(now, to_ptr->delay_ms);
+
+ if (to_ptr->fin_time < now)
+ {
+ all_timeouts[id].indicator = true;
+ schedule_alarm(now);
+ return true;
+ }
+ break;
+
+ case TMPARAM_AT:
+ /* fin_time must be set */
+ Assert(to_ptr->fin_time > 0);
+
+ if (to_ptr->fin_time < now)
+ {
+ all_timeouts[id].indicator = true;
+ schedule_alarm(now);
+ return true;
+ }
+
+ break;
+ default:
+ elog(ERROR, "internal error: unknown timeout type");
+ break;
+ }
+ }
+
+ foreach(cell, timeouts)
+ {
+ TimeoutParams *to_ptr = (TimeoutParams *)lfirst(cell);
+
+ enable_timeout(to_ptr->id, now, to_ptr->fin_time);
+ }
+
+ /*
+ * Set the timer.
+ */
+ schedule_alarm(now);
+
+ return false;
}
/*
@@ -394,29 +521,13 @@ enable_timeout_at(TimeoutId id, TimestampTz fin_time)
void
disable_timeout(TimeoutId id, bool keep_indicator)
{
- struct itimerval timeval;
int i;
/* Assert request is sane */
Assert(all_timeouts_initialized);
Assert(all_timeouts[id].timeout_handler != NULL);
- /*
- * Disable the timer if it is active; this avoids getting interrupted by
- * the signal handler and thereby possibly getting confused. We will
- * re-enable the interrupt if necessary below.
- *
- * If num_active_timeouts is zero, we don't have to call setitimer. There
- * should not be any pending interrupt, and even if there is, the signal
- * handler will not do anything. In this situation the only thing we
- * really have to do is reset the timeout's indicator.
- */
- if (num_active_timeouts > 0)
- {
- MemSet(&timeval, 0, sizeof(struct itimerval));
- if (setitimer(ITIMER_REAL, &timeval, NULL) != 0)
- elog(FATAL, "could not disable SIGALRM timer: %m");
- }
+ disable_alarm();
/* Find the timeout and remove it from the active list. */
i = find_active_timeout(id);
@@ -427,9 +538,52 @@ disable_timeout(TimeoutId id, bool keep_indicator)
if (!keep_indicator)
all_timeouts[id].indicator = false;
- /* Now re-enable the timer, if necessary. */
- if (num_active_timeouts > 0)
- schedule_alarm(GetCurrentTimestamp());
+ schedule_alarm(GetCurrentTimestamp());
+}
+
+/*
+ * Cancel the specified timeouts at once.
+ *
+ * The timeouts' I've-been-fired indicator are reset,
+ * unless ->keep_indicator is true.
+ *
+ * After cancelling the specified timeouts, any other active timeout
+ * remains in force. It's not an error to disable a timeout that is
+ * not enabled.
+ */
+void
+disable_timeouts(List *timeouts)
+{
+ ListCell *cell;
+ int idx;
+
+ if (list_length(timeouts) == 0)
+ return;
+
+ Assert(all_timeouts_initialized);
+ foreach(cell, timeouts)
+ {
+ TimeoutParams *to_ptr = (TimeoutParams *)lfirst(cell);
+
+ Assert(all_timeouts[to_ptr->id].timeout_handler != NULL);
+ }
+
+ disable_alarm();
+
+ foreach(cell, timeouts)
+ {
+ TimeoutParams *to_ptr = (TimeoutParams *)lfirst(cell);
+ TimeoutId id = to_ptr->id;
+
+ idx = find_active_timeout(id);
+ if (idx >= 0)
+ remove_timeout_index(idx);
+
+ if (!to_ptr->keep_indicator)
+ all_timeouts[id].indicator = false;
+ }
+
+ schedule_alarm(GetCurrentTimestamp());
}
/*
diff --git a/src/include/storage/pg_sema.h b/src/include/storage/pg_sema.h
index 82163cb..113d97c 100644
--- a/src/include/storage/pg_sema.h
+++ b/src/include/storage/pg_sema.h
@@ -61,6 +61,7 @@ typedef HANDLE PGSemaphoreData;
typedef PGSemaphoreData *PGSemaphore;
+typedef bool (*TimeoutCondition)(void);
/* Module initialization (called during postmaster start or shmem reinit) */
extern void PGReserveSemaphores(int maxSemas, int port);
@@ -80,4 +81,8 @@ extern void PGSemaphoreUnlock(PGSemaphore sema);
/* Lock a semaphore only if able to do so without blocking */
extern bool PGSemaphoreTryLock(PGSemaphore sema);
+/* Lock a semaphore (decrement count), blocking if count would be < 0 with timeout */
+extern bool PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK,
+ TimeoutCondition condition);
+
#endif /* PG_SEMA_H */
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index d571c35..b4a2140 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -222,6 +222,7 @@ extern PGPROC *PreparedXactProcs;
/* configurable options */
extern int DeadlockTimeout;
extern int StatementTimeout;
+extern int LockTimeout;
extern bool log_lock_waits;
diff --git a/src/include/utils/timeout.h b/src/include/utils/timeout.h
index dd58c0e..dbffc2f 100644
--- a/src/include/utils/timeout.h
+++ b/src/include/utils/timeout.h
@@ -26,6 +26,7 @@ typedef enum TimeoutId
STARTUP_PACKET_TIMEOUT,
DEADLOCK_TIMEOUT,
STATEMENT_TIMEOUT,
+ LOCK_TIMEOUT,
STANDBY_DEADLOCK_TIMEOUT,
STANDBY_TIMEOUT,
/* First user-definable timeout reason */
@@ -34,6 +35,22 @@ typedef enum TimeoutId
MAX_TIMEOUTS = 16
} TimeoutId;
+typedef enum TimeoutType {
+ TMPARAM_AFTER,
+ TMPARAM_AT
+} TimeoutType;
+
+/*
+ * Structure for setting multiple timeouts at once
+ */
+typedef struct {
+ TimeoutId id;
+ TimeoutType type;
+ int delay_ms;
+ TimestampTz fin_time;
+ bool keep_indicator;
+} TimeoutParams;
+
/* callback function signature */
typedef void (*timeout_handler) (void);
@@ -42,9 +59,11 @@ extern void InitializeTimeouts(void);
extern TimeoutId RegisterTimeout(TimeoutId id, timeout_handler handler);
/* timeout operation */
-extern void enable_timeout_after(TimeoutId id, int delay_ms);
-extern void enable_timeout_at(TimeoutId id, TimestampTz fin_time);
+extern bool enable_timeout_after(TimeoutId id, int delay_ms);
+extern bool enable_timeout_at(TimeoutId id, TimestampTz fin_time);
+extern bool enable_timeouts(List *timeouts);
extern void disable_timeout(TimeoutId id, bool keep_indicator);
+extern void disable_timeouts(List *timeouts);
extern void disable_all_timeouts(bool keep_indicators);
/* accessors */
diff --git a/src/test/regress/expected/prepared_xacts.out b/src/test/regress/expected/prepared_xacts.out
index c0b0864..009a6ca 100644
--- a/src/test/regress/expected/prepared_xacts.out
+++ b/src/test/regress/expected/prepared_xacts.out
@@ -198,6 +198,11 @@ set statement_timeout to 2000;
SELECT * FROM pxtest3;
ERROR: canceling statement due to statement timeout
reset statement_timeout;
+-- pxtest3 should be locked because of the pending DROP
+set lock_timeout to 2000;
+SELECT * FROM pxtest3;
+ERROR: canceling statement due to lock timeout
+reset lock_timeout;
-- Disconnect, we will continue testing in a different backend
\c -
-- There should still be two prepared transactions
@@ -213,6 +218,11 @@ set statement_timeout to 2000;
SELECT * FROM pxtest3;
ERROR: canceling statement due to statement timeout
reset statement_timeout;
+-- pxtest3 should be locked because of the pending DROP
+set lock_timeout to 2000;
+SELECT * FROM pxtest3;
+ERROR: canceling statement due to lock timeout
+reset lock_timeout;
-- Commit table creation
COMMIT PREPARED 'regress-one';
\d pxtest2
diff --git a/src/test/regress/expected/prepared_xacts_1.out b/src/test/regress/expected/prepared_xacts_1.out
index 898f278..ec9ddcb 100644
--- a/src/test/regress/expected/prepared_xacts_1.out
+++ b/src/test/regress/expected/prepared_xacts_1.out
@@ -205,6 +205,14 @@ SELECT * FROM pxtest3;
(0 rows)
reset statement_timeout;
+-- pxtest3 should be locked because of the pending DROP
+set lock_timeout to 2000;
+SELECT * FROM pxtest3;
+ fff
+-----
+(0 rows)
+
+reset lock_timeout;
-- Disconnect, we will continue testing in a different backend
\c -
-- There should still be two prepared transactions
@@ -221,6 +229,14 @@ SELECT * FROM pxtest3;
(0 rows)
reset statement_timeout;
+-- pxtest3 should be locked because of the pending DROP
+set lock_timeout to 2000;
+SELECT * FROM pxtest3;
+ fff
+-----
+(0 rows)
+
+reset lock_timeout;
-- Commit table creation
COMMIT PREPARED 'regress-one';
ERROR: prepared transaction with identifier "regress-one" does not exist
diff --git a/src/test/regress/sql/prepared_xacts.sql b/src/test/regress/sql/prepared_xacts.sql
index 7902152..872beed 100644
--- a/src/test/regress/sql/prepared_xacts.sql
+++ b/src/test/regress/sql/prepared_xacts.sql
@@ -126,6 +126,11 @@ set statement_timeout to 2000;
SELECT * FROM pxtest3;
reset statement_timeout;
+-- pxtest3 should be locked because of the pending DROP
+set lock_timeout to 2000;
+SELECT * FROM pxtest3;
+reset lock_timeout;
+
-- Disconnect, we will continue testing in a different backend
\c -
@@ -137,6 +142,11 @@ set statement_timeout to 2000;
SELECT * FROM pxtest3;
reset statement_timeout;
+-- pxtest3 should be locked because of the pending DROP
+set lock_timeout to 2000;
+SELECT * FROM pxtest3;
+reset lock_timeout;
+
-- Commit table creation
COMMIT PREPARED 'regress-one';
\d pxtest2
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers