2012-07-17 06:32 keltezéssel, Alvaro Herrera írta:
Excerpts from Tom Lane's message of vie jul 13 18:23:31 -0400 2012:
Boszormenyi Zoltan <z...@cybertec.at> writes:
Try SET deadlock_timeout = 0;
Actually, when I try that I get

ERROR:  0 is outside the valid range for parameter "deadlock_timeout" (1 .. 
2147483647)

So I don't see any bug here.
I committed this patch without changing this.  If this needs patched,
please speak up.  I also considered adding a comment on
enable_timeout_after about calling it with a delay of 0, but refrained;
if anybody thinks it's necessary, suggestions are welcome.

Thanks for committing this part.

Attached is the revised (and a lot leaner, more generic) lock timeout patch,
which introduces new functionality for the timeout registration framework.
The new functionality is called "extra timeouts", better naming is welcome.
Instead of only the previously defined (deadlock and statement) timeouts,
the "extra" timeouts can also be activated from within ProcSleep() in a linked
way.

The lock timeout is a special case of this functionality. To show this, this 
patch
is split into two again to make reviewing easier.

This way, the timeout framework is really usable for external modules, as
envisioned by you guys

Also, rewriting statement and deadlock timeouts using this functionality
and unifying the two registration interfaces may be possible later. But
messing up proven and working code is not in the scope of this patch or
my job at this point.

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 -durpN postgresql/src/backend/port/posix_sema.c postgresql.1/src/backend/port/posix_sema.c
--- postgresql/src/backend/port/posix_sema.c	2012-04-16 19:57:22.438915489 +0200
+++ postgresql.1/src/backend/port/posix_sema.c	2012-07-22 21:34:50.475375677 +0200
@@ -24,6 +24,7 @@
 #include "miscadmin.h"
 #include "storage/ipc.h"
 #include "storage/pg_sema.h"
+#include "utils/timeout.h"
 
 
 #ifdef USE_NAMED_POSIX_SEMAPHORES
@@ -313,3 +314,31 @@ PGSemaphoreTryLock(PGSemaphore sema)
 
 	return true;
 }
+
+/*
+ * PGSemaphoreTimedLock
+ *
+ * Lock a semaphore (decrement count), blocking if count would be < 0
+ * Return if lock_timeout expired
+ */
+bool
+PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+{
+	int			errStatus;
+	bool			timeout;
+
+	do
+	{
+		ImmediateInterruptOK = interruptOK;
+		CHECK_FOR_INTERRUPTS();
+		errStatus = sem_wait(PG_SEM_REF(sema));
+		ImmediateInterruptOK = false;
+		timeout = ExtraTimeoutCondition();
+	} while (errStatus < 0 && errno == EINTR && !timeout);
+
+	if (timeout)
+		return false;
+	if (errStatus < 0)
+		elog(FATAL, "sem_wait failed: %m");
+	return true;
+}
diff -durpN postgresql/src/backend/port/sysv_sema.c postgresql.1/src/backend/port/sysv_sema.c
--- postgresql/src/backend/port/sysv_sema.c	2012-05-14 08:20:56.284830580 +0200
+++ postgresql.1/src/backend/port/sysv_sema.c	2012-07-22 21:34:50.476375683 +0200
@@ -27,6 +27,7 @@
 #include "miscadmin.h"
 #include "storage/ipc.h"
 #include "storage/pg_sema.h"
+#include "utils/timeout.h"
 
 
 #ifndef HAVE_UNION_SEMUN
@@ -492,3 +493,36 @@ PGSemaphoreTryLock(PGSemaphore sema)
 
 	return true;
 }
+
+/*
+ * PGSemaphoreTimedLock
+ *
+ * Lock a semaphore (decrement count), blocking if count would be < 0
+ * Return if lock_timeout expired
+ */
+bool
+PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+{
+	int			errStatus;
+	bool			timeout;
+	struct sembuf sops;
+
+	sops.sem_op = -1;			/* decrement */
+	sops.sem_flg = 0;
+	sops.sem_num = sema->semNum;
+
+	do
+	{
+		ImmediateInterruptOK = interruptOK;
+		CHECK_FOR_INTERRUPTS();
+		errStatus = semop(sema->semId, &sops, 1);
+		ImmediateInterruptOK = false;
+		timeout = ExtraTimeoutCondition();
+	} 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 -durpN postgresql/src/backend/port/win32_sema.c postgresql.1/src/backend/port/win32_sema.c
--- postgresql/src/backend/port/win32_sema.c	2012-06-11 06:22:48.137921483 +0200
+++ postgresql.1/src/backend/port/win32_sema.c	2012-07-22 21:34:50.476375683 +0200
@@ -16,6 +16,7 @@
 #include "miscadmin.h"
 #include "storage/ipc.h"
 #include "storage/pg_sema.h"
+#include "utils/timeout.h"
 
 static HANDLE *mySemSet;		/* IDs of sema sets acquired so far */
 static int	numSems;			/* number of sema sets acquired so far */
@@ -209,3 +210,65 @@ PGSemaphoreTryLock(PGSemaphore sema)
 	/* keep compiler quiet */
 	return false;
 }
+
+/*
+ * PGSemaphoreTimedLock
+ *
+ * Lock a semaphore (decrement count), blocking if count would be < 0.
+ * Serve the interrupt if interruptOK is true.
+ * Return if lock_timeout expired.
+ */
+bool
+PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+{
+	DWORD		ret;
+	HANDLE		wh[2];
+	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.
+	 */
+	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! */
+			return;
+		}
+		else
+			/* Otherwise we are in trouble */
+			errno = EIDRM;
+
+		ImmediateInterruptOK = false;
+		timeout = ExtraTimeoutCondition();
+	} while (errno == EINTR && !timeout);
+
+	if (timeout)
+		return false;
+	if (errno != 0)
+		ereport(FATAL,
+				(errmsg("could not lock semaphore: error code %d", (int) GetLastError())));
+	return true;
+}
diff -durpN postgresql/src/backend/storage/lmgr/proc.c postgresql.1/src/backend/storage/lmgr/proc.c
--- postgresql/src/backend/storage/lmgr/proc.c	2012-07-22 16:48:48.520857718 +0200
+++ postgresql.1/src/backend/storage/lmgr/proc.c	2012-07-22 21:34:50.478375695 +0200
@@ -640,8 +640,12 @@ LockErrorCleanup(void)
 	if (lockAwaited == NULL)
 		return;
 
-	/* Turn off the deadlock timer, if it's still running (see ProcSleep) */
+	/*
+	 * Turn off the deadlock timer and any other extra timers,
+	 * if they are still running (see ProcSleep)
+	 */
 	disable_timeout(DEADLOCK_TIMEOUT, false);
+	DisableExtraTimeouts();
 
 	/* Unlink myself from the wait queue, if on it (might not be anymore!) */
 	partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
@@ -1039,6 +1043,11 @@ ProcSleep(LOCALLOCK *locallock, LockMeth
 	enable_timeout_after(DEADLOCK_TIMEOUT, DeadlockTimeout);
 
 	/*
+	 * Set the timer for other registered timeouts
+	 */
+	EnableExtraTimeouts();
+
+	/*
 	 * If someone wakes us between LWLockRelease and PGSemaphoreLock,
 	 * PGSemaphoreLock will not block.	The wakeup is "saved" by the semaphore
 	 * implementation.	While this is normally good, there are cases where a
@@ -1057,7 +1066,8 @@ ProcSleep(LOCALLOCK *locallock, LockMeth
 	 */
 	do
 	{
-		PGSemaphoreLock(&MyProc->sem, true);
+		if (!PGSemaphoreTimedLock(&MyProc->sem, true))
+			ReportExtraTimeoutError();
 
 		/*
 		 * waitStatus could change from STATUS_WAITING to something else
@@ -1186,9 +1196,10 @@ ProcSleep(LOCALLOCK *locallock, LockMeth
 	} 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);
+	DisableExtraTimeouts();
 
 	/*
 	 * Re-acquire the lock table's partition lock.  We have to do this to hold
diff -durpN postgresql/src/backend/utils/misc/timeout.c postgresql.1/src/backend/utils/misc/timeout.c
--- postgresql/src/backend/utils/misc/timeout.c	2012-07-22 16:48:48.535857816 +0200
+++ postgresql.1/src/backend/utils/misc/timeout.c	2012-07-22 21:34:50.479375701 +0200
@@ -44,6 +44,11 @@ static timeout_params all_timeouts[MAX_T
 static bool all_timeouts_initialized = false;
 
 /*
+ * Linked set of timeout functions
+ */
+static ExtraTimeoutFunctions *CurrentExtraTimeoutFunctions = NULL;
+
+/*
  * List of active timeouts ordered by their fin_time and priority.
  * This list is subject to change by the interrupt handler, so it's volatile.
  */
@@ -284,6 +289,104 @@ RegisterTimeout(TimeoutId id, timeout_ha
 }
 
 /*
+ * Register a set of functions for an extra timeout
+ */
+void
+RegisterExtraTimeout(ExtraTimeoutFunctions *functions)
+{
+	Assert(functions != NULL);
+	Assert(functions->enable != NULL);
+	Assert(functions->disable != NULL);
+	Assert(functions->condition != NULL);
+	Assert(functions->reporter != NULL);
+
+	functions->next = CurrentExtraTimeoutFunctions;
+	CurrentExtraTimeoutFunctions = functions;
+}
+
+/*
+ * Enable registered extra timeouts
+ */
+void
+EnableExtraTimeouts(void)
+{
+	ExtraTimeoutFunctions *ptr = CurrentExtraTimeoutFunctions;
+
+	while (ptr)
+	{
+		ptr->enable();
+		ptr = ptr->next;
+	}
+}
+
+/*
+ * Disable registered extra timeouts
+ */
+void
+DisableExtraTimeouts(void)
+{
+	ExtraTimeoutFunctions *ptr = CurrentExtraTimeoutFunctions;
+
+	while (ptr)
+	{
+		ptr->disable();
+		ptr = ptr->next;
+	}
+}
+
+/*
+ * Call the timeout indicator functions in the order of registration
+ */
+static bool
+DoExtraTimeoutCondition(ExtraTimeoutFunctions *ptr)
+{
+	if (ptr)
+	{
+		if (ptr->next)
+			if (DoExtraTimeoutCondition(ptr->next))
+				return true;
+		if (ptr->condition())
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Call the timeout indicator and return if any one of them is set
+ */
+bool
+ExtraTimeoutCondition(void)
+{
+	return DoExtraTimeoutCondition(CurrentExtraTimeoutFunctions);
+}
+
+/*
+ * Call the timeout error reporter functions in the order of registration
+ */
+static void
+DoReportExtraTimeoutError(ExtraTimeoutFunctions *ptr)
+{
+	if (ptr)
+	{
+		if (ptr->next)
+			DoReportExtraTimeoutError(ptr->next);
+
+		/* This may not return because of calling ereport() */
+		ptr->reporter();
+	}
+}
+
+/*
+ * Call the timeout error reporter if set
+ */
+void
+ReportExtraTimeoutError(void)
+{
+	DoReportExtraTimeoutError(CurrentExtraTimeoutFunctions);
+}
+
+/*
  * Enable the specified timeout reason
  */
 static void
diff -durpN postgresql/src/include/storage/pg_sema.h postgresql.1/src/include/storage/pg_sema.h
--- postgresql/src/include/storage/pg_sema.h	2012-04-16 19:57:22.672918205 +0200
+++ postgresql.1/src/include/storage/pg_sema.h	2012-07-22 21:34:50.479375701 +0200
@@ -20,6 +20,8 @@
 #ifndef PG_SEMA_H
 #define PG_SEMA_H
 
+#include "utils/timeout.h"
+
 /*
  * PGSemaphoreData and pointer type PGSemaphore are the data structure
  * representing an individual semaphore.  The contents of PGSemaphoreData
@@ -80,4 +82,10 @@ extern void PGSemaphoreUnlock(PGSemaphor
 /* Lock a semaphore only if able to do so without blocking */
 extern bool PGSemaphoreTryLock(PGSemaphore sema);
 
+/*
+ * Lock a semaphore (decrement count), blocking for at most
+ * "lock_timeout" milliseconds if count would be < 0
+ */
+extern bool PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK);
+
 #endif   /* PG_SEMA_H */
diff -durpN postgresql/src/include/utils/timeout.h postgresql.1/src/include/utils/timeout.h
--- postgresql/src/include/utils/timeout.h	2012-07-22 16:48:48.549857907 +0200
+++ postgresql.1/src/include/utils/timeout.h	2012-07-22 21:34:50.480375707 +0200
@@ -37,6 +37,18 @@ typedef enum TimeoutId
 /* callback function signature */
 typedef void (*timeout_handler) (void);
 
+/* callback function signatures for extra timeouts */
+typedef void (*TimeoutCallback)(void);
+typedef bool (*TimeoutCondition)(void);
+
+typedef struct ExtraTimeoutFunctions {
+	struct ExtraTimeoutFunctions *next;
+	TimeoutCallback		enable;
+	TimeoutCallback		disable;
+	TimeoutCondition	condition;
+	TimeoutCallback		reporter;
+} ExtraTimeoutFunctions;
+
 /* timeout setup */
 extern void InitializeTimeouts(void);
 extern TimeoutId RegisterTimeout(TimeoutId id, timeout_handler handler);
@@ -51,4 +63,11 @@ extern void disable_all_timeouts(bool ke
 extern bool get_timeout_indicator(TimeoutId id);
 extern TimestampTz get_timeout_start_time(TimeoutId id);
 
+/* register and handle extra timeouts */
+extern void RegisterExtraTimeout(ExtraTimeoutFunctions *functions);
+extern void EnableExtraTimeouts(void);
+extern void DisableExtraTimeouts(void);
+extern bool ExtraTimeoutCondition(void);
+extern void ReportExtraTimeoutError(void);
+
 #endif   /* TIMEOUT_H */
diff -durpN postgresql.1/doc/src/sgml/config.sgml postgresql.2/doc/src/sgml/config.sgml
--- postgresql.1/doc/src/sgml/config.sgml	2012-07-05 06:51:30.540155082 +0200
+++ postgresql.2/doc/src/sgml/config.sgml	2012-07-22 21:39:00.856924933 +0200
@@ -4953,7 +4953,10 @@ COPY postgres_log FROM '/full/path/to/lo
         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>
@@ -4964,6 +4967,60 @@ COPY postgres_log FROM '/full/path/to/lo
       </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 that tries to acquire a heavy-weight lock on rows,
+        pages, tables, indices or other objects and the lock(s) has to wait
+        more than the specified number of milliseconds. 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-lock-timeout-option" xreflabel="lock_timeout_option">
+      <term><varname>lock_timeout_option</varname> (<type>enum</type>)</term>
+      <indexterm>
+       <primary><varname>lock_timeout_option</> configuration parameter</primary>
+      </indexterm>
+      <listitem>
+       <para>
+        Control the behaviour of <varname>lock_timeout</>. Possible values are
+        'per_lock' and 'per_statement'.
+       </para>
+
+       <para>
+        With 'per_lock' in effect and if the statement involves more than one
+        lock, the timeout applies to every one of them individually, starting
+        from the time the server attempts to lock an object. This makes the
+        statement possibly wait for up to N * <varname>lock_timeout</> time in
+        the worst case where N is the number of locks attempted. This is the
+        default.
+       </para>
+
+       <para>
+        With 'per_statement' in effect, <varname>lock_timeout</> behaves like
+        <varname>statement_timeout</>: the specified timeout applies to all
+        the locks in a cumulative way, starting from the time the command
+        arrives at the server from the client.
+       </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 -durpN postgresql.1/doc/src/sgml/ref/lock.sgml postgresql.2/doc/src/sgml/ref/lock.sgml
--- postgresql.1/doc/src/sgml/ref/lock.sgml	2012-04-16 19:57:22.229913063 +0200
+++ postgresql.2/doc/src/sgml/ref/lock.sgml	2012-07-22 21:39:00.857924939 +0200
@@ -39,8 +39,11 @@ LOCK [ TABLE ] [ ONLY ] <replaceable cla
    <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
+   error is emitted. If <varname>lock_timeout</varname> is set to a value
+   higher than 0, and the lock cannot be acquired under the specified
+   timeout value in milliseconds, 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>
diff -durpN postgresql.1/doc/src/sgml/ref/select.sgml postgresql.2/doc/src/sgml/ref/select.sgml
--- postgresql.1/doc/src/sgml/ref/select.sgml	2012-04-16 19:57:22.233913109 +0200
+++ postgresql.2/doc/src/sgml/ref/select.sgml	2012-07-22 21:39:00.859924951 +0200
@@ -1199,6 +1199,14 @@ FOR SHARE [ OF <replaceable class="param
    </para>
 
    <para>
+    If <literal>NOWAIT</> option is not specified and <varname>lock_timeout</varname>
+    is set to a value higher than 0, and the lock or statement (depending on
+    <varname>lock_timeout_option</varname>) needs to wait more than
+    the specified value in milliseconds, the command reports an error after
+    timing out, rather than waiting indefinitely.
+   </para>
+
+   <para>
     If specific tables are named in <literal>FOR UPDATE</literal>
     or <literal>FOR SHARE</literal>,
     then only rows coming from those tables are locked; any other
diff -durpN postgresql.1/src/backend/storage/lmgr/proc.c postgresql.2/src/backend/storage/lmgr/proc.c
--- postgresql.1/src/backend/storage/lmgr/proc.c	2012-07-22 21:34:50.478375695 +0200
+++ postgresql.2/src/backend/storage/lmgr/proc.c	2012-07-22 21:39:00.860924957 +0200
@@ -55,6 +55,8 @@
 /* GUC variables */
 int			DeadlockTimeout = 1000;
 int			StatementTimeout = 0;
+int			LockTimeout = 0;
+int			LockTimeoutOption = LOCK_TIMEOUT_PER_LOCK;
 bool		log_lock_waits = false;
 
 /* Pointer to this process's PGPROC and PGXACT structs, if any */
diff -durpN postgresql.1/src/backend/utils/init/postinit.c postgresql.2/src/backend/utils/init/postinit.c
--- postgresql.1/src/backend/utils/init/postinit.c	2012-07-22 16:48:48.525857751 +0200
+++ postgresql.2/src/backend/utils/init/postinit.c	2012-07-22 21:39:00.861924963 +0200
@@ -56,6 +56,7 @@
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 #include "utils/timeout.h"
+#include "utils/timestamp.h"
 #include "utils/tqual.h"
 
 
@@ -66,10 +67,21 @@ static void CheckMyDatabase(const char *
 static void InitCommunication(void);
 static void ShutdownPostgres(int code, Datum arg);
 static void StatementTimeoutHandler(void);
+static void LockTimeoutHandler(void);
+static void EnableLockTimeout(void);
+static void DisableLockTimeout(void);
+static bool LockTimeoutCondition(void);
+static void LockTimeoutError(void);
 static bool ThereIsAtLeastOneRole(void);
 static void process_startup_options(Port *port, bool am_superuser);
 static void process_settings(Oid databaseid, Oid roleid);
 
+static ExtraTimeoutFunctions LockTimeoutFunctions = {
+	NULL,
+	EnableLockTimeout, DisableLockTimeout,
+	LockTimeoutCondition, LockTimeoutError
+};
+
 
 /*** InitPostgres support ***/
 
@@ -501,6 +513,8 @@ InitPostgres(const char *in_dbname, Oid
 	if (!bootstrap)
 	{
 		RegisterTimeout(DEADLOCK_TIMEOUT, CheckDeadLock);
+		RegisterTimeout(LOCK_TIMEOUT, LockTimeoutHandler);
+		RegisterExtraTimeout(&LockTimeoutFunctions);
 		RegisterTimeout(STATEMENT_TIMEOUT, StatementTimeoutHandler);
 	}
 
@@ -983,6 +997,81 @@ ShutdownPostgres(int code, Datum arg)
 }
 
 
+/*
+ * LOCK_TIMEOUT handler: disable all subsequent timeouts
+ *
+ * This ensures that the error coming from lock timeout is reported
+ * instead of triggering the query-cancel interrupt (and reporting
+ * the statement timeout error) in case they trigger at the same time.
+ */
+static void
+LockTimeoutHandler(void)
+{
+	disable_all_timeouts(true);
+}
+
+
+/*
+ * Enable LOCK_TIMEOUT
+ */
+static void
+EnableLockTimeout(void)
+{
+	if (LockTimeout > 0)
+	{
+		switch (LockTimeoutOption)
+		{
+			case LOCK_TIMEOUT_PER_LOCK:
+				enable_timeout_after(LOCK_TIMEOUT, LockTimeout);
+				break;
+			case LOCK_TIMEOUT_PER_STMT:
+				enable_timeout_at(LOCK_TIMEOUT,
+							TimestampTzPlusMilliseconds(
+									GetCurrentStatementStartTimestamp(),
+									LockTimeout));
+				break;
+			default:
+				elog(ERROR, "unhandled lock_timeout_option value: \"%s\"",
+							GetConfigOptionByName("lock_timeout_option", NULL));
+				break;
+		}
+	}
+}
+
+
+/*
+ * Disable LOCK_TIMEOUT
+ */
+static void
+DisableLockTimeout(void)
+{
+	disable_timeout(LOCK_TIMEOUT, false);
+}
+
+
+/*
+ * Indicator for LOCK_TIMEOUT.
+ */
+static bool
+LockTimeoutCondition(void)
+{
+	return get_timeout_indicator(LOCK_TIMEOUT);
+}
+
+
+/*
+ * Error reporting for LOCK_TIMEOUT
+ */
+static void
+LockTimeoutError(void)
+{
+	if (get_timeout_indicator(LOCK_TIMEOUT))
+		ereport(ERROR,
+				(errcode(ERRCODE_QUERY_CANCELED),
+				 errmsg("canceling statement due to lock timeout")));
+}
+
+
 /*
  * STATEMENT_TIMEOUT handler: trigger a query-cancel interrupt.
  */
diff -durpN postgresql.1/src/backend/utils/misc/guc.c postgresql.2/src/backend/utils/misc/guc.c
--- postgresql.1/src/backend/utils/misc/guc.c	2012-07-03 13:50:11.725151648 +0200
+++ postgresql.2/src/backend/utils/misc/guc.c	2012-07-22 21:39:00.865924989 +0200
@@ -389,6 +389,17 @@ static const struct config_enum_entry sy
 };
 
 /*
+ * Control behaviour of lock_timeout:
+ * - timeout applied per lock from the time the lock is attempted to be taken
+ * - timeout applied per statement from the time the statements has started
+ */
+static const struct config_enum_entry lock_timeout_options[] = {
+	{"per_lock", LOCK_TIMEOUT_PER_LOCK, false},
+	{"per_statement", LOCK_TIMEOUT_PER_STMT, false},
+	{NULL, 0, false}
+};
+
+/*
  * Options for enum values stored in other modules
  */
 extern const struct config_enum_entry wal_level_options[];
@@ -1861,6 +1872,17 @@ static struct config_int ConfigureNamesI
 	},
 
 	{
+		{"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
@@ -3141,6 +3163,16 @@ static struct config_enum ConfigureNames
 		NULL, NULL, NULL
 	},
 
+	{
+		{"lock_timeout_option", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("Sets the lock_timeout behaviour."),
+			NULL
+		},
+		&LockTimeoutOption,
+		LOCK_TIMEOUT_PER_LOCK, lock_timeout_options,
+		NULL, NULL, NULL
+	},
+
 	{
 		{"log_error_verbosity", PGC_SUSET, LOGGING_WHAT,
 			gettext_noop("Sets the verbosity of logged messages."),
diff -durpN postgresql.1/src/backend/utils/misc/postgresql.conf.sample postgresql.2/src/backend/utils/misc/postgresql.conf.sample
--- postgresql.1/src/backend/utils/misc/postgresql.conf.sample	2012-05-14 08:20:56.298830662 +0200
+++ postgresql.2/src/backend/utils/misc/postgresql.conf.sample	2012-07-22 21:39:00.866924995 +0200
@@ -528,6 +528,11 @@
 #------------------------------------------------------------------------------
 
 #deadlock_timeout = 1s
+#lock_timeout = 0			# timeout value for heavy-weight locks
+					# taken by statements. 0 disables timeout
+					# unit in milliseconds, default is 0
+#lock_timeout_option = 'per_lock'	# behaviour of lock_timeout. possible
+					# values are: 'per_lock' or 'per_statement'
 #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 -durpN postgresql.1/src/include/storage/proc.h postgresql.2/src/include/storage/proc.h
--- postgresql.1/src/include/storage/proc.h	2012-07-22 16:48:48.548857900 +0200
+++ postgresql.2/src/include/storage/proc.h	2012-07-22 21:39:00.867925001 +0200
@@ -219,8 +219,15 @@ extern PGPROC *PreparedXactProcs;
 /* configurable options */
 extern int	DeadlockTimeout;
 extern int	StatementTimeout;
+extern int	LockTimeout;
+extern int	LockTimeoutOption;
 extern bool log_lock_waits;
 
+typedef enum LockTimeoutOptions {
+	LOCK_TIMEOUT_PER_LOCK,
+	LOCK_TIMEOUT_PER_STMT
+} LockTimeoutOptions;
+
 
 /*
  * Function Prototypes
diff -durpN postgresql.1/src/include/utils/timeout.h postgresql.2/src/include/utils/timeout.h
--- postgresql.1/src/include/utils/timeout.h	2012-07-22 21:34:50.480375707 +0200
+++ postgresql.2/src/include/utils/timeout.h	2012-07-22 21:39:00.867925001 +0200
@@ -25,6 +25,7 @@ typedef enum TimeoutId
 	/* Predefined timeout reasons */
 	STARTUP_PACKET_TIMEOUT,
 	DEADLOCK_TIMEOUT,
+	LOCK_TIMEOUT,
 	STATEMENT_TIMEOUT,
 	STANDBY_DEADLOCK_TIMEOUT,
 	STANDBY_TIMEOUT,
diff -durpN postgresql.1/src/test/regress/expected/prepared_xacts.out postgresql.2/src/test/regress/expected/prepared_xacts.out
--- postgresql.1/src/test/regress/expected/prepared_xacts.out	2012-04-16 19:57:22.776919413 +0200
+++ postgresql.2/src/test/regress/expected/prepared_xacts.out	2012-07-22 21:39:00.868925007 +0200
@@ -198,6 +198,22 @@ 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;
+-- Test lock_timeout_option = 'per_statement' and see that lock_timeout
+-- triggers instead of statement_timeout if both are set.
+-- pxtest3 should be locked because of the pending DROP
+set statement_timeout to 2000;
+set lock_timeout to 2000;
+set lock_timeout_option to 'per_statement';
+SELECT * FROM pxtest3;
+ERROR:  canceling statement due to lock timeout
+reset lock_timeout;
+reset statement_timeout;
+reset lock_timeout_option;
 -- Disconnect, we will continue testing in a different backend
 \c -
 -- There should still be two prepared transactions
@@ -213,6 +229,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 -durpN postgresql.1/src/test/regress/sql/prepared_xacts.sql postgresql.2/src/test/regress/sql/prepared_xacts.sql
--- postgresql.1/src/test/regress/sql/prepared_xacts.sql	2012-04-16 19:57:22.796919644 +0200
+++ postgresql.2/src/test/regress/sql/prepared_xacts.sql	2012-07-22 21:39:00.868925007 +0200
@@ -126,6 +126,22 @@ 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;
+
+-- Test lock_timeout_option = 'per_statement' and see that lock_timeout
+-- triggers instead of statement_timeout if both are set.
+-- pxtest3 should be locked because of the pending DROP
+set statement_timeout to 2000;
+set lock_timeout to 2000;
+set lock_timeout_option to 'per_statement';
+SELECT * FROM pxtest3;
+reset lock_timeout;
+reset statement_timeout;
+reset lock_timeout_option;
+
 -- Disconnect, we will continue testing in a different backend
 \c -
 
@@ -137,6 +153,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

Reply via email to