[PATCH] lock_timeout and common SIGALRM framework
Hi,
attached is a patch to implement a framework to simplify and
correctly nest multiplexing more than two timeout sources
into the same SIGALRM signal handler.
The framework is using a new internal API for timeouts:
bool enable_timeout(TimeoutName tn, int delayms);
bool disable_timeout(TimeoutName tn, bool keep_indicator);
bool disable_all_timeouts(bool keep_indicators);
A timeout source has these features to allow different initialization,
cleanup and check functions and rescheduling:
typedef void (*timeout_init)(TimestampTz, TimestampTz);
typedef void (*timeout_destroy)(bool);
typedef bool (*timeout_check)(void);
typedef TimestampTz (*timeout_start)(void);
typedef struct {
TimeoutName index;
bool resched_next;
timeout_init timeout_init;
timeout_destroy timeout_destroy;
timeout_check timeout_check;
timeout_start timeout_start;
TimestampTz fin_time;
} timeout_params;
This makes it possible to differentiate between the standby and
statement timeouts, regular deadlock and standby deadlock using
the same signal handler function.
And finally, this makes it possible to implement the lock_timeout
feature that we at Cybertec implemented more than 2 years ago.
The patch also adds two new tests into prepared_xacts.sql to trigger
the lock_timeout instead of statement_timeout.
Documentation and extensive comments are included.
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/
Attachments:
pg92-common-lock-framework-lock_timeout.patchtext/x-patch; name=pg92-common-lock-framework-lock_timeout.patchDownload
diff -dcrpN postgresql.orig/doc/src/sgml/config.sgml postgresql/doc/src/sgml/config.sgml
*** postgresql.orig/doc/src/sgml/config.sgml 2012-03-28 10:54:25.177368339 +0200
--- postgresql/doc/src/sgml/config.sgml 2012-04-04 10:36:01.982604984 +0200
*************** COPY postgres_log FROM '/full/path/to/lo
*** 4944,4950 ****
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.
</para>
<para>
--- 4944,4953 ----
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. 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>
*************** COPY postgres_log FROM '/full/path/to/lo
*** 4955,4960 ****
--- 4958,4991 ----
</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 has to wait more
+ than the specified number of milliseconds, starting from the time the
+ command arrives at the server from the client. If the statement involves
+ more than one such lock, the timeout applies to every one of them.
+ 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 to acquire.
+ As opposed to <varname>statement_timeout</>, this timeout (and the error)
+ may only occurs 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 off the limitation.
+ </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 -dcrpN postgresql.orig/doc/src/sgml/ref/lock.sgml postgresql/doc/src/sgml/ref/lock.sgml
*** postgresql.orig/doc/src/sgml/ref/lock.sgml 2011-08-07 11:29:16.004256905 +0200
--- postgresql/doc/src/sgml/ref/lock.sgml 2012-04-03 15:54:26.543224868 +0200
*************** LOCK [ TABLE ] [ ONLY ] <replaceable cla
*** 39,46 ****
<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.)
</para>
--- 39,49 ----
<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. 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 -dcrpN postgresql.orig/doc/src/sgml/ref/select.sgml postgresql/doc/src/sgml/ref/select.sgml
*** postgresql.orig/doc/src/sgml/ref/select.sgml 2012-01-29 20:33:53.893650856 +0100
--- postgresql/doc/src/sgml/ref/select.sgml 2012-04-04 10:30:35.035960738 +0200
*************** FOR SHARE [ OF <replaceable class="param
*** 1199,1204 ****
--- 1199,1211 ----
</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 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 -dcrpN postgresql.orig/src/backend/port/posix_sema.c postgresql/src/backend/port/posix_sema.c
*** postgresql.orig/src/backend/port/posix_sema.c 2012-01-02 12:35:11.502186906 +0100
--- postgresql/src/backend/port/posix_sema.c 2012-04-03 15:33:14.193385578 +0200
***************
*** 24,29 ****
--- 24,30 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/proc.h"
#ifdef USE_NAMED_POSIX_SEMAPHORES
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 313,315 ****
--- 314,341 ----
return true;
}
+
+ /*
+ * PGSemaphoreTimedLock
+ *
+ * Lock a semaphore (decrement count), blocking if count would be < 0
+ * Return if lock_timeout expired
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ int errStatus;
+
+ do
+ {
+ ImmediateInterruptOK = interruptOK;
+ CHECK_FOR_INTERRUPTS();
+ errStatus = sem_wait(PG_SEM_REF(sema));
+ ImmediateInterruptOK = false;
+ } while (errStatus < 0 && errno == EINTR && !lock_timeout_detected);
+
+ if (lock_timeout_detected)
+ return;
+ if (errStatus < 0)
+ elog(FATAL, "sem_wait failed: %m");
+ }
diff -dcrpN postgresql.orig/src/backend/port/sysv_sema.c postgresql/src/backend/port/sysv_sema.c
*** postgresql.orig/src/backend/port/sysv_sema.c 2012-01-02 12:35:11.503186847 +0100
--- postgresql/src/backend/port/sysv_sema.c 2012-04-04 09:16:29.002527208 +0200
***************
*** 30,35 ****
--- 30,36 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/proc.h"
#ifndef HAVE_UNION_SEMUN
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 495,497 ****
--- 496,528 ----
return true;
}
+
+ /*
+ * PGSemaphoreTimedLock
+ *
+ * Lock a semaphore (decrement count), blocking if count would be < 0
+ * Return if lock_timeout expired
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ int errStatus;
+ 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;
+ } while (errStatus < 0 && errno == EINTR && !lock_timeout_detected);
+
+ if (lock_timeout_detected)
+ return;
+ if (errStatus < 0)
+ elog(FATAL, "semop(id=%d) failed: %m", sema->semId);
+ }
diff -dcrpN postgresql.orig/src/backend/port/win32_sema.c postgresql/src/backend/port/win32_sema.c
*** postgresql.orig/src/backend/port/win32_sema.c 2012-01-02 12:35:11.504186789 +0100
--- postgresql/src/backend/port/win32_sema.c 2012-04-03 15:33:14.194385583 +0200
***************
*** 16,21 ****
--- 16,22 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/proc.h"
static HANDLE *mySemSet; /* IDs of sema sets acquired so far */
static int numSems; /* number of sema sets acquired so far */
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 205,207 ****
--- 206,263 ----
/* 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.
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ DWORD ret;
+ HANDLE wh[2];
+
+ wh[0] = *sema;
+ wh[1] = pgwin32_signal_event;
+
+ /*
+ * 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();
+
+ errno = 0;
+ ret = WaitForMultipleObjectsEx(2, wh, FALSE, INFINITE, TRUE);
+
+ if (ret == WAIT_OBJECT_0)
+ {
+ /* We got it! */
+ return;
+ }
+ else if (ret == WAIT_OBJECT_0 + 1)
+ {
+ /* Signal event is set - we have a signal to deliver */
+ pgwin32_dispatch_queued_signals();
+ errno = EINTR;
+ }
+ else
+ /* Otherwise we are in trouble */
+ errno = EIDRM;
+
+ ImmediateInterruptOK = false;
+ } while (errno == EINTR && !lock_timeout_detected);
+
+ if (lock_timeout_detected)
+ return;
+ if (errno != 0)
+ ereport(FATAL,
+ (errmsg("could not lock semaphore: error code %d", (int) GetLastError())));
+ }
diff -dcrpN postgresql.orig/src/backend/postmaster/autovacuum.c postgresql/src/backend/postmaster/autovacuum.c
*** postgresql.orig/src/backend/postmaster/autovacuum.c 2012-01-27 10:29:06.483649830 +0100
--- postgresql/src/backend/postmaster/autovacuum.c 2012-04-04 09:03:39.597690176 +0200
*************** AutoVacLauncherMain(int argc, char *argv
*** 484,490 ****
/* Forget any pending QueryCancel request */
QueryCancelPending = false;
! disable_sig_alarm(true);
QueryCancelPending = false; /* again in case timeout occurred */
/* Report the error to the server log */
--- 484,490 ----
/* Forget any pending QueryCancel request */
QueryCancelPending = false;
! disable_all_timeouts(false);
QueryCancelPending = false; /* again in case timeout occurred */
/* Report the error to the server log */
diff -dcrpN postgresql.orig/src/backend/postmaster/postmaster.c postgresql/src/backend/postmaster/postmaster.c
*** postgresql.orig/src/backend/postmaster/postmaster.c 2012-03-29 08:36:59.775468454 +0200
--- postgresql/src/backend/postmaster/postmaster.c 2012-04-04 09:04:04.022811571 +0200
*************** BackendInitialize(Port *port)
*** 3466,3474 ****
* Ready to begin client interaction. We will give up and exit(1) after a
* time delay, so that a broken client can't hog a connection
* indefinitely. PreAuthDelay and any DNS interactions above don't count
! * against the time limit.
*/
! if (!enable_sig_alarm(AuthenticationTimeout * 1000, false))
elog(FATAL, "could not set timer for startup packet timeout");
/*
--- 3466,3474 ----
* Ready to begin client interaction. We will give up and exit(1) after a
* time delay, so that a broken client can't hog a connection
* indefinitely. PreAuthDelay and any DNS interactions above don't count
! * against the time limit. Use the deadlock timeout interface.
*/
! if (!enable_timeout(DEADLOCK_TIMEOUT, AuthenticationTimeout * 1000))
elog(FATAL, "could not set timer for startup packet timeout");
/*
*************** BackendInitialize(Port *port)
*** 3506,3512 ****
/*
* Disable the timeout, and prevent SIGTERM/SIGQUIT again.
*/
! if (!disable_sig_alarm(false))
elog(FATAL, "could not disable timer for startup packet timeout");
PG_SETMASK(&BlockSig);
}
--- 3506,3512 ----
/*
* Disable the timeout, and prevent SIGTERM/SIGQUIT again.
*/
! if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for startup packet timeout");
PG_SETMASK(&BlockSig);
}
diff -dcrpN postgresql.orig/src/backend/postmaster/startup.c postgresql/src/backend/postmaster/startup.c
*** postgresql.orig/src/backend/postmaster/startup.c 2012-01-02 12:35:11.508186555 +0100
--- postgresql/src/backend/postmaster/startup.c 2012-04-04 06:42:53.812799815 +0200
*************** StartupProcessMain(void)
*** 195,202 ****
pqsignal(SIGTERM, StartupProcShutdownHandler); /* request shutdown */
pqsignal(SIGQUIT, startupproc_quickdie); /* hard crash time */
if (EnableHotStandby)
! pqsignal(SIGALRM, handle_standby_sig_alarm); /* ignored unless
! * InHotStandby */
else
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
--- 195,202 ----
pqsignal(SIGTERM, StartupProcShutdownHandler); /* request shutdown */
pqsignal(SIGQUIT, startupproc_quickdie); /* hard crash time */
if (EnableHotStandby)
! pqsignal(SIGALRM, handle_sig_alarm); /* ignored unless
! * InHotStandby */
else
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
diff -dcrpN postgresql.orig/src/backend/storage/ipc/standby.c postgresql/src/backend/storage/ipc/standby.c
*** postgresql.orig/src/backend/storage/ipc/standby.c 2012-02-05 12:28:36.003281960 +0100
--- postgresql/src/backend/storage/ipc/standby.c 2012-04-04 10:06:07.282576364 +0200
*************** ResolveRecoveryConflictWithLock(Oid dbOi
*** 394,400 ****
void
ResolveRecoveryConflictWithBufferPin(void)
{
- bool sig_alarm_enabled = false;
TimestampTz ltime;
TimestampTz now;
--- 394,399 ----
*************** ResolveRecoveryConflictWithBufferPin(voi
*** 409,417 ****
* We're willing to wait forever for conflicts, so set timeout for
* deadlock check (only)
*/
! if (enable_standby_sig_alarm(now, now, true))
! sig_alarm_enabled = true;
! else
elog(FATAL, "could not set timer for process wakeup");
}
else if (now >= ltime)
--- 408,414 ----
* We're willing to wait forever for conflicts, so set timeout for
* deadlock check (only)
*/
! if (!enable_timeout(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout))
elog(FATAL, "could not set timer for process wakeup");
}
else if (now >= ltime)
*************** ResolveRecoveryConflictWithBufferPin(voi
*** 423,446 ****
}
else
{
/*
* Wake up at ltime, and check for deadlocks as well if we will be
* waiting longer than deadlock_timeout
*/
! if (enable_standby_sig_alarm(now, ltime, false))
! sig_alarm_enabled = true;
! else
elog(FATAL, "could not set timer for process wakeup");
}
/* Wait to be signaled by UnpinBuffer() */
ProcWaitForSignal();
! if (sig_alarm_enabled)
! {
! if (!disable_standby_sig_alarm())
! elog(FATAL, "could not disable timer for process wakeup");
! }
}
void
--- 420,447 ----
}
else
{
+ long secs, msecs;
+ int usecs;
+
/*
* Wake up at ltime, and check for deadlocks as well if we will be
* waiting longer than deadlock_timeout
*/
! if (!enable_timeout(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout))
! elog(FATAL, "could not set timer for process wakeup");
!
! TimestampDifference(now, ltime, &secs, &usecs);
! msecs = secs * 1000 + usecs / 1000;
!
! if (!enable_timeout(STANDBY_TIMEOUT, msecs))
elog(FATAL, "could not set timer for process wakeup");
}
/* Wait to be signaled by UnpinBuffer() */
ProcWaitForSignal();
! if (!disable_all_timeouts(false))
! elog(FATAL, "could not disable timer for process wakeup");
}
void
diff -dcrpN postgresql.orig/src/backend/storage/lmgr/lmgr.c postgresql/src/backend/storage/lmgr/lmgr.c
*** postgresql.orig/src/backend/storage/lmgr/lmgr.c 2012-01-02 12:35:11.517186027 +0100
--- postgresql/src/backend/storage/lmgr/lmgr.c 2012-04-03 15:55:39.981617113 +0200
***************
*** 19,26 ****
--- 19,29 ----
#include "access/transam.h"
#include "access/xact.h"
#include "catalog/catalog.h"
+ #include "catalog/pg_database.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
+ #include "utils/lsyscache.h"
+ #include "storage/proc.h"
#include "storage/procarray.h"
#include "utils/inval.h"
*************** LockRelationOid(Oid relid, LOCKMODE lock
*** 78,83 ****
--- 81,101 ----
res = LockAcquire(&tag, lockmode, false, false);
+ if (res == LOCKACQUIRE_NOT_AVAIL)
+ {
+ char *relname = get_rel_name(relid);
+ if (relname)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ relname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation with OID %u",
+ relid)));
+ }
+
/*
* Now that we have the lock, check for invalidation messages, so that we
* will update or flush any stale relcache entry before we try to use it.
*************** LockRelation(Relation relation, LOCKMODE
*** 174,179 ****
--- 192,203 ----
res = LockAcquire(&tag, lockmode, false, false);
+ if (res == LOCKACQUIRE_NOT_AVAIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ RelationGetRelationName(relation))));
+
/*
* Now that we have the lock, check for invalidation messages; see notes
* in LockRelationOid.
*************** LockRelationIdForSession(LockRelId *reli
*** 251,257 ****
SET_LOCKTAG_RELATION(tag, relid->dbId, relid->relId);
! (void) LockAcquire(&tag, lockmode, true, false);
}
/*
--- 275,294 ----
SET_LOCKTAG_RELATION(tag, relid->dbId, relid->relId);
! if (LockAcquire(&tag, lockmode, true, false) == LOCKACQUIRE_NOT_AVAIL)
! {
! char *relname = get_rel_name(relid->relId);
! if (relname)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on relation \"%s\"",
! relname)));
! else
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on relation with OID %u",
! relid->relId)));
! }
}
/*
*************** LockRelationForExtension(Relation relati
*** 286,292 ****
relation->rd_lockInfo.lockRelId.dbId,
relation->rd_lockInfo.lockRelId.relId);
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 323,333 ----
relation->rd_lockInfo.lockRelId.dbId,
relation->rd_lockInfo.lockRelId.relId);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on index \"%s\"",
! RelationGetRelationName(relation))));
}
/*
*************** LockPage(Relation relation, BlockNumber
*** 320,326 ****
relation->rd_lockInfo.lockRelId.relId,
blkno);
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 361,371 ----
relation->rd_lockInfo.lockRelId.relId,
blkno);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on page %u of relation \"%s\"",
! blkno, RelationGetRelationName(relation))));
}
/*
*************** LockTuple(Relation relation, ItemPointer
*** 376,382 ****
ItemPointerGetBlockNumber(tid),
ItemPointerGetOffsetNumber(tid));
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 421,431 ----
ItemPointerGetBlockNumber(tid),
ItemPointerGetOffsetNumber(tid));
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on row in relation \"%s\"",
! RelationGetRelationName(relation))));
}
/*
*************** XactLockTableInsert(TransactionId xid)
*** 430,436 ****
SET_LOCKTAG_TRANSACTION(tag, xid);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
}
/*
--- 479,488 ----
SET_LOCKTAG_TRANSACTION(tag, xid);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on transaction with ID %u", xid)));
}
/*
*************** XactLockTableWait(TransactionId xid)
*** 474,480 ****
SET_LOCKTAG_TRANSACTION(tag, xid);
! (void) LockAcquire(&tag, ShareLock, false, false);
LockRelease(&tag, ShareLock, false);
--- 526,535 ----
SET_LOCKTAG_TRANSACTION(tag, xid);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on transaction with ID %u", xid)));
LockRelease(&tag, ShareLock, false);
*************** LockDatabaseObject(Oid classid, Oid obji
*** 535,541 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, false, false);
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
--- 590,600 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
*************** LockSharedObject(Oid classid, Oid objid,
*** 576,582 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, false, false);
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
--- 635,645 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
*************** LockSharedObjectForSession(Oid classid,
*** 618,624 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, true, false);
}
/*
--- 681,702 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, true, false) == LOCKACQUIRE_NOT_AVAIL)
! switch(classid)
! {
! case DatabaseRelationId:
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on database with ID %u",
! objid)));
! break;
! default:
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
! break;
! }
}
/*
diff -dcrpN postgresql.orig/src/backend/storage/lmgr/lock.c postgresql/src/backend/storage/lmgr/lock.c
*** postgresql.orig/src/backend/storage/lmgr/lock.c 2012-01-25 06:45:42.322747976 +0100
--- postgresql/src/backend/storage/lmgr/lock.c 2012-04-04 10:47:12.072945214 +0200
*************** static void RemoveLocalLock(LOCALLOCK *l
*** 338,344 ****
static PROCLOCK *SetupLockInTable(LockMethod lockMethodTable, PGPROC *proc,
const LOCKTAG *locktag, uint32 hashcode, LOCKMODE lockmode);
static void GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner);
! static void WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner);
static void ReleaseLockForOwner(LOCALLOCK *locallock, ResourceOwner owner);
static bool UnGrantLock(LOCK *lock, LOCKMODE lockmode,
PROCLOCK *proclock, LockMethod lockMethodTable);
--- 338,344 ----
static PROCLOCK *SetupLockInTable(LockMethod lockMethodTable, PGPROC *proc,
const LOCKTAG *locktag, uint32 hashcode, LOCKMODE lockmode);
static void GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner);
! static int WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner);
static void ReleaseLockForOwner(LOCALLOCK *locallock, ResourceOwner owner);
static bool UnGrantLock(LOCK *lock, LOCKMODE lockmode,
PROCLOCK *proclock, LockMethod lockMethodTable);
*************** ProcLockHashCode(const PROCLOCKTAG *proc
*** 544,550 ****
* dontWait: if true, don't wait to acquire lock
*
* Returns one of:
! * LOCKACQUIRE_NOT_AVAIL lock not available, and dontWait=true
* LOCKACQUIRE_OK lock successfully acquired
* LOCKACQUIRE_ALREADY_HELD incremented count for lock already held
*
--- 544,550 ----
* dontWait: if true, don't wait to acquire lock
*
* Returns one of:
! * LOCKACQUIRE_NOT_AVAIL lock not available, either dontWait=true or timeout
* LOCKACQUIRE_OK lock successfully acquired
* LOCKACQUIRE_ALREADY_HELD incremented count for lock already held
*
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 863,869 ****
locktag->locktag_type,
lockmode);
! WaitOnLock(locallock, owner);
TRACE_POSTGRESQL_LOCK_WAIT_DONE(locktag->locktag_field1,
locktag->locktag_field2,
--- 863,869 ----
locktag->locktag_type,
lockmode);
! status = WaitOnLock(locallock, owner);
TRACE_POSTGRESQL_LOCK_WAIT_DONE(locktag->locktag_field1,
locktag->locktag_field2,
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 878,897 ****
* done when the lock was granted to us --- see notes in WaitOnLock.
*/
! /*
! * Check the proclock entry status, in case something in the ipc
! * communication doesn't work correctly.
! */
! if (!(proclock->holdMask & LOCKBIT_ON(lockmode)))
{
! PROCLOCK_PRINT("LockAcquire: INCONSISTENT", proclock);
! LOCK_PRINT("LockAcquire: INCONSISTENT", lock, lockmode);
! /* Should we retry ? */
! LWLockRelease(partitionLock);
! elog(ERROR, "LockAcquire failed");
}
- PROCLOCK_PRINT("LockAcquire: granted", proclock);
- LOCK_PRINT("LockAcquire: granted", lock, lockmode);
}
LWLockRelease(partitionLock);
--- 878,909 ----
* done when the lock was granted to us --- see notes in WaitOnLock.
*/
! switch (status)
{
! case STATUS_OK:
! /*
! * Check the proclock entry status, in case something in the ipc
! * communication doesn't work correctly.
! */
! if (!(proclock->holdMask & LOCKBIT_ON(lockmode)))
! {
! PROCLOCK_PRINT("LockAcquire: INCONSISTENT", proclock);
! LOCK_PRINT("LockAcquire: INCONSISTENT", lock, lockmode);
! /* Should we retry ? */
! LWLockRelease(partitionLock);
! elog(ERROR, "LockAcquire failed");
! }
! PROCLOCK_PRINT("LockAcquire: granted", proclock);
! LOCK_PRINT("LockAcquire: granted", lock, lockmode);
! break;
! case STATUS_WAITING:
! PROCLOCK_PRINT("LockAcquire: timed out", proclock);
! LOCK_PRINT("LockAcquire: timed out", lock, lockmode);
! break;
! default:
! elog(ERROR, "LockAcquire invalid status");
! break;
}
}
LWLockRelease(partitionLock);
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 911,917 ****
locktag->locktag_field2);
}
! return LOCKACQUIRE_OK;
}
/*
--- 923,929 ----
locktag->locktag_field2);
}
! return (status == STATUS_OK ? LOCKACQUIRE_OK : LOCKACQUIRE_NOT_AVAIL);
}
/*
*************** GrantAwaitedLock(void)
*** 1371,1384 ****
* Caller must have set MyProc->heldLocks to reflect locks already held
* on the lockable object by this process.
*
* The appropriate partition lock must be held at entry.
*/
! static void
WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner)
{
LOCKMETHODID lockmethodid = LOCALLOCK_LOCKMETHOD(*locallock);
LockMethod lockMethodTable = LockMethods[lockmethodid];
char *volatile new_status = NULL;
LOCK_PRINT("WaitOnLock: sleeping on lock",
locallock->lock, locallock->tag.mode);
--- 1383,1402 ----
* Caller must have set MyProc->heldLocks to reflect locks already held
* on the lockable object by this process.
*
+ * Result: returns value of ProcSleep()
+ * STATUS_OK if we acquired the lock
+ * STATUS_ERROR if not (deadlock)
+ * STATUS_WAITING if not (timeout)
+ *
* The appropriate partition lock must be held at entry.
*/
! static int
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);
*************** WaitOnLock(LOCALLOCK *locallock, Resourc
*** 1420,1427 ****
*/
PG_TRY();
{
! if (ProcSleep(locallock, lockMethodTable) != STATUS_OK)
{
/*
* We failed as a result of a deadlock, see CheckDeadLock(). Quit
* now.
--- 1438,1450 ----
*/
PG_TRY();
{
! wait_status = ProcSleep(locallock, lockMethodTable);
! switch (wait_status)
{
+ case STATUS_OK:
+ case STATUS_WAITING:
+ break;
+ default:
/*
* We failed as a result of a deadlock, see CheckDeadLock(). Quit
* now.
*************** WaitOnLock(LOCALLOCK *locallock, Resourc
*** 1466,1473 ****
pfree(new_status);
}
! LOCK_PRINT("WaitOnLock: wakeup on lock",
locallock->lock, locallock->tag.mode);
}
/*
--- 1489,1502 ----
pfree(new_status);
}
! 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);
+
+ return wait_status;
}
/*
*************** VirtualXactLock(VirtualTransactionId vxi
*** 3739,3745 ****
LWLockRelease(proc->backendLock);
/* Time to wait. */
! (void) LockAcquire(&tag, ShareLock, false, false);
LockRelease(&tag, ShareLock, false);
return true;
--- 3768,3778 ----
LWLockRelease(proc->backendLock);
/* Time to wait. */
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on virtual transaction with ID %u",
! vxid.localTransactionId)));
LockRelease(&tag, ShareLock, false);
return true;
diff -dcrpN postgresql.orig/src/backend/storage/lmgr/proc.c postgresql/src/backend/storage/lmgr/proc.c
*** postgresql.orig/src/backend/storage/lmgr/proc.c 2012-03-22 11:00:51.723002056 +0100
--- postgresql/src/backend/storage/lmgr/proc.c 2012-04-04 11:56:38.481132982 +0200
***************
*** 54,59 ****
--- 54,60 ----
/* GUC variables */
int DeadlockTimeout = 1000;
int StatementTimeout = 0;
+ int LockTimeout = 0;
bool log_lock_waits = false;
/* Pointer to this process's PGPROC struct, if any */
*************** static LOCALLOCK *lockAwaited = NULL;
*** 79,103 ****
/* Mark these volatile because they can be changed by signal handler */
static volatile bool standby_timeout_active = false;
- static volatile bool statement_timeout_active = false;
- static volatile bool deadlock_timeout_active = false;
static volatile DeadLockState deadlock_state = DS_NOT_YET_CHECKED;
volatile bool cancel_from_timeout = false;
/* timeout_start_time is set when log_lock_waits is true */
static TimestampTz timeout_start_time;
- /* statement_fin_time is valid only if statement_timeout_active is true */
- static TimestampTz statement_fin_time;
- static TimestampTz statement_fin_time2; /* valid only in recovery */
-
-
static void RemoveProcFromArray(int code, Datum arg);
static void ProcKill(int code, Datum arg);
static void AuxiliaryProcKill(int code, Datum arg);
static bool CheckStatementTimeout(void);
static bool CheckStandbyTimeout(void);
/*
* Report shared-memory space needed by InitProcGlobal.
--- 80,184 ----
/* Mark these volatile because they can be changed by signal handler */
static volatile bool standby_timeout_active = false;
static volatile DeadLockState deadlock_state = DS_NOT_YET_CHECKED;
volatile bool cancel_from_timeout = false;
+ volatile bool lock_timeout_detected = false;
/* timeout_start_time is set when log_lock_waits is true */
static TimestampTz timeout_start_time;
static void RemoveProcFromArray(int code, Datum arg);
static void ProcKill(int code, Datum arg);
static void AuxiliaryProcKill(int code, Datum arg);
+
+ /*
+ * Infrastructure for timeouts
+ */
+
+ static void InitDeadLock(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyDeadLock(bool keep_indicator);
+ static bool CheckDeadLock(void);
+
+ static void InitLockTimeout(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyLockTimeout(bool keep_indicator);
+ static bool CheckLockTimeout(void);
+
+ static void InitStatementTimeout(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyStatementTimeout(bool keep_indicator);
static bool CheckStatementTimeout(void);
+
+ static void InitStandbyDeadLock(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyStandbyDeadLock(bool keep_indicator);
+ static bool CheckStandbyDeadLock(void);
+
+ static void InitStandbyTimeout(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyStandbyTimeout(bool keep_indicator);
static bool CheckStandbyTimeout(void);
+ typedef void (*timeout_init)(TimestampTz, TimestampTz);
+ typedef void (*timeout_destroy)(bool);
+ typedef bool (*timeout_check)(void);
+ typedef TimestampTz (*timeout_start)(void);
+
+ typedef struct {
+ TimeoutName index;
+ bool resched_next;
+ timeout_init timeout_init;
+ timeout_destroy timeout_destroy;
+ timeout_check timeout_check;
+ timeout_start timeout_start;
+ TimestampTz fin_time;
+ } timeout_params;
+
+ /*
+ * List of possible timeout reasons in the order of enum TimeoutName.
+ * The priority of timeouts (in case two of them would trigger at the
+ * same time) is determined by this order: the earlier one in the list
+ * has higher priority.
+ */
+ static timeout_params base_timeouts[TIMEOUT_MAX] = {
+ {
+ DEADLOCK_TIMEOUT, true,
+ InitDeadLock, DestroyDeadLock,
+ CheckDeadLock, GetCurrentTimestamp,
+ 0
+ },
+
+ {
+ LOCK_TIMEOUT, false,
+ InitLockTimeout, DestroyLockTimeout,
+ CheckLockTimeout, GetCurrentTimestamp,
+ 0
+ },
+
+ {
+ STATEMENT_TIMEOUT, false,
+ InitStatementTimeout, DestroyStatementTimeout,
+ CheckStatementTimeout, GetCurrentStatementStartTimestamp,
+ 0
+ },
+
+ {
+ STANDBY_DEADLOCK_TIMEOUT, true,
+ InitStandbyDeadLock, DestroyStandbyDeadLock,
+ CheckStandbyDeadLock, GetCurrentTimestamp,
+ 0
+ },
+
+ {
+ STANDBY_TIMEOUT, false,
+ InitStandbyTimeout, DestroyStandbyTimeout,
+ CheckStandbyTimeout, GetCurrentTimestamp,
+ 0
+ }
+ };
+
+ /*
+ * List of active timeouts ordered by their fin_time and priority.
+ */
+ static int n_timeouts = 0;
+ static timeout_params *timeouts[TIMEOUT_MAX];
+
/*
* Report shared-memory space needed by InitProcGlobal.
*************** LockWaitCancel(void)
*** 651,657 ****
return;
/* Turn off the deadlock timer, if it's still running (see ProcSleep) */
! disable_sig_alarm(false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
--- 732,738 ----
return;
/* Turn off the deadlock timer, if it's still running (see ProcSleep) */
! disable_timeout(DEADLOCK_TIMEOUT, false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
*************** ProcQueueInit(PROC_QUEUE *queue)
*** 885,891 ****
* 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).
*
* ASSUME: that no one will fiddle with the queue until after
* we release the partition lock.
--- 966,975 ----
* 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)
! * STATUS_WAITING if not (timeout)
*
* ASSUME: that no one will fiddle with the queue until after
* we release the partition lock.
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 907,912 ****
--- 991,997 ----
LOCKMASK myHeldLocks = MyProc->heldLocks;
bool early_deadlock = false;
bool allow_autovacuum_cancel = true;
+ bool timeout_detected;
int myWaitStatus;
PGPROC *proc;
int i;
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1044,1055 ****
* 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 (!enable_sig_alarm(DeadlockTimeout, false))
elog(FATAL, "could not set timer for process wakeup");
/*
! * 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
* saved wakeup might be leftover from a previous operation (for example,
* we aborted ProcWaitForSignal just before someone did ProcSendSignal).
--- 1129,1148 ----
* 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 (!enable_timeout(DEADLOCK_TIMEOUT, DeadlockTimeout))
elog(FATAL, "could not set timer for process wakeup");
/*
! * Reset timer so we are awaken in case of lock timeout.
! * This doesn't modify the timer for deadlock check in case
! * the deadlock check happens earlier.
! */
! if (!enable_timeout(LOCK_TIMEOUT, LockTimeout))
! elog(FATAL, "could not set timer for process wakeup");
!
! /*
! * 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).
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1066,1072 ****
*/
do
{
! PGSemaphoreLock(&MyProc->sem, true);
/*
* waitStatus could change from STATUS_WAITING to something else
--- 1159,1168 ----
*/
do
{
! PGSemaphoreTimedLock(&MyProc->sem, true);
!
! if (lock_timeout_detected == true)
! break;
/*
* waitStatus could change from STATUS_WAITING to something else
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1194,1202 ****
} while (myWaitStatus == STATUS_WAITING);
/*
! * Disable the timer, if it's still running
*/
! if (!disable_sig_alarm(false))
elog(FATAL, "could not disable timer for process wakeup");
/*
--- 1290,1306 ----
} while (myWaitStatus == STATUS_WAITING);
/*
! * Disable the deadlock timer, if it's still running
*/
! if (!disable_timeout(DEADLOCK_TIMEOUT, false))
! elog(FATAL, "could not disable timer for process wakeup");
!
! /*
! * Disable the lock timeout timer, if it's still running
! * but keep the indicator for later checks.
! */
! timeout_detected = lock_timeout_detected;
! if (!disable_timeout(LOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for process wakeup");
/*
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1207,1212 ****
--- 1311,1325 ----
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 LockWaitCancel to do anything.
*/
lockAwaited = NULL;
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1220,1227 ****
/*
* We don't have to do anything else, because the awaker did all the
* necessary update of the lock table and MyProc.
*/
! return MyProc->waitStatus;
}
--- 1333,1342 ----
/*
* 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 (timeout_detected ? STATUS_WAITING : MyProc->waitStatus);
}
*************** ProcLockWakeup(LockMethod lockMethodTabl
*** 1340,1350 ****
* NB: this is run inside a signal handler, so be very wary about what is done
* here or in called routines.
*/
! static void
CheckDeadLock(void)
{
int i;
/*
* Acquire exclusive lock on the entire shared lock data structures. Must
* grab LWLocks in partition-number order to avoid LWLock deadlock.
--- 1455,1472 ----
* NB: this is run inside a signal handler, so be very wary about what is done
* here or in called routines.
*/
! static bool
CheckDeadLock(void)
{
+ TimestampTz now;
int i;
+ now = GetCurrentTimestamp();
+
+ /* If our time has not come yet, do nothing. */
+ if (now < base_timeouts[DEADLOCK_TIMEOUT].fin_time)
+ return false;
+
/*
* Acquire exclusive lock on the entire shared lock data structures. Must
* grab LWLocks in partition-number order to avoid LWLock deadlock.
*************** CheckDeadLock(void)
*** 1439,1444 ****
--- 1561,1568 ----
check_done:
for (i = NUM_LOCK_PARTITIONS; --i >= 0;)
LWLockRelease(FirstLockMgrLock + i);
+
+ return true;
}
*************** ProcSendSignal(int pid)
*** 1503,1689 ****
*****************************************************************************/
/*
* Enable the SIGALRM interrupt to fire after the specified delay
*
* Delay is given in milliseconds. Caller should be sure a SIGALRM
* signal handler is installed before this is called.
*
! * This code properly handles nesting of deadlock timeout alarms within
! * statement timeout alarms.
*
* Returns TRUE if okay, FALSE on failure.
*/
bool
! enable_sig_alarm(int delayms, bool is_statement_timeout)
{
TimestampTz fin_time;
! struct itimerval timeval;
! if (is_statement_timeout)
{
/*
! * Begin statement-level timeout
! *
! * Note that we compute statement_fin_time with reference to the
! * statement_timestamp, but apply the specified delay without any
! * correction; that is, we ignore whatever time has elapsed since
! * statement_timestamp was set. In the normal case only a small
! * interval will have elapsed and so this doesn't matter, but there
! * are corner cases (involving multi-statement query strings with
! * embedded COMMIT or ROLLBACK) where we might re-initialize the
! * statement timeout long after initial receipt of the message. In
! * such cases the enforcement of the statement timeout will be a bit
! * inconsistent. This annoyance is judged not worth the cost of
! * performing an additional gettimeofday() here.
*/
! Assert(!deadlock_timeout_active);
! fin_time = GetCurrentStatementStartTimestamp();
! fin_time = TimestampTzPlusMilliseconds(fin_time, delayms);
! statement_fin_time = fin_time;
! cancel_from_timeout = false;
! statement_timeout_active = true;
! }
! else if (statement_timeout_active)
! {
/*
! * Begin deadlock timeout with statement-level timeout active
! *
! * Here, we want to interrupt at the closer of the two timeout times.
! * If fin_time >= statement_fin_time then we need not touch the
! * existing timer setting; else set up to interrupt at the deadlock
! * timeout time.
! *
! * NOTE: in this case it is possible that this routine will be
! * interrupted by the previously-set timer alarm. This is okay
! * because the signal handler will do only what it should do according
! * to the state variables. The deadlock checker may get run earlier
! * than normal, but that does no harm.
*/
! timeout_start_time = GetCurrentTimestamp();
! fin_time = TimestampTzPlusMilliseconds(timeout_start_time, delayms);
! deadlock_timeout_active = true;
! if (fin_time >= statement_fin_time)
! return true;
! }
! else
! {
! /* Begin deadlock timeout with no statement-level timeout */
! deadlock_timeout_active = true;
! /* GetCurrentTimestamp can be expensive, so only do it if we must */
! if (log_lock_waits)
! timeout_start_time = GetCurrentTimestamp();
}
/* If we reach here, okay to set the timer interrupt */
! MemSet(&timeval, 0, sizeof(struct itimerval));
! timeval.it_value.tv_sec = delayms / 1000;
! timeval.it_value.tv_usec = (delayms % 1000) * 1000;
! if (setitimer(ITIMER_REAL, &timeval, NULL))
return false;
return true;
}
/*
! * Cancel the SIGALRM timer, either for a deadlock timeout or a statement
! * timeout. If a deadlock timeout is canceled, any active statement timeout
! * remains in force.
*
* Returns TRUE if okay, FALSE on failure.
*/
bool
! disable_sig_alarm(bool is_statement_timeout)
{
/*
* Always disable the interrupt if it is active; this avoids being
* interrupted by the signal handler and thereby possibly getting
* confused.
*
! * We will re-enable the interrupt if necessary in CheckStatementTimeout.
*/
! if (statement_timeout_active || deadlock_timeout_active)
{
struct itimerval timeval;
MemSet(&timeval, 0, sizeof(struct itimerval));
if (setitimer(ITIMER_REAL, &timeval, NULL))
{
! statement_timeout_active = false;
cancel_from_timeout = false;
- deadlock_timeout_active = false;
return false;
}
}
! /* Always cancel deadlock timeout, in case this is error cleanup */
! deadlock_timeout_active = false;
! /* Cancel or reschedule statement timeout */
! if (is_statement_timeout)
! {
! statement_timeout_active = false;
! cancel_from_timeout = false;
! }
! else if (statement_timeout_active)
! {
! if (!CheckStatementTimeout())
return false;
! }
return true;
}
/*
* Check for statement timeout. If the timeout time has come,
! * trigger a query-cancel interrupt; if not, reschedule the SIGALRM
! * interrupt to occur at the right time.
*
! * Returns true if okay, false if failed to set the interrupt.
*/
static bool
CheckStatementTimeout(void)
{
TimestampTz now;
- if (!statement_timeout_active)
- return true; /* do nothing if not active */
-
now = GetCurrentTimestamp();
! if (now >= statement_fin_time)
{
/* Time to die */
- statement_timeout_active = false;
cancel_from_timeout = true;
#ifdef HAVE_SETSID
/* try to signal whole process group */
kill(-MyProcPid, SIGINT);
#endif
kill(MyProcPid, SIGINT);
}
- else
- {
- /* Not time yet, so (re)schedule the interrupt */
- long secs;
- int usecs;
- struct itimerval timeval;
! TimestampDifference(now, statement_fin_time,
! &secs, &usecs);
! /*
! * It's possible that the difference is less than a microsecond;
! * ensure we don't cancel, rather than set, the interrupt.
! */
! if (secs == 0 && usecs == 0)
! usecs = 1;
! MemSet(&timeval, 0, sizeof(struct itimerval));
! timeval.it_value.tv_sec = secs;
! timeval.it_value.tv_usec = usecs;
! if (setitimer(ITIMER_REAL, &timeval, NULL))
! return false;
}
! return true;
}
--- 1627,1973 ----
*****************************************************************************/
/*
+ * Find the index of a given timeout type in the active array
+ */
+ static int
+ find_active_timeout(TimeoutName tn)
+ {
+ int i;
+
+ for (i = 0; i < n_timeouts; i++)
+ {
+ if (timeouts[i]->index == tn)
+ return i;
+ }
+
+ return -1;
+ }
+
+ /*
+ * Check whether the given kind of timeout is active.
+ */
+ #define is_timeout_active(tn) (find_active_timeout(tn) >= 0)
+
+ /*
+ * Init and Destroy functions for different timeouts.
+ */
+ static void
+ InitDeadLock(TimestampTz start_time, TimestampTz fin_time)
+ {
+ if (log_lock_waits)
+ timeout_start_time = start_time;
+ base_timeouts[DEADLOCK_TIMEOUT].fin_time = fin_time;
+ }
+
+ static void
+ DestroyDeadLock(bool keep_indicator)
+ {
+ base_timeouts[DEADLOCK_TIMEOUT].fin_time = 0;
+ }
+
+ static void
+ InitLockTimeout(TimestampTz start_time, TimestampTz fin_time)
+ {
+ base_timeouts[LOCK_TIMEOUT].fin_time = fin_time;
+ }
+
+ static void
+ DestroyLockTimeout(bool keep_indicator)
+ {
+ if (!keep_indicator)
+ lock_timeout_detected = false;
+ base_timeouts[LOCK_TIMEOUT].fin_time = 0;
+ }
+
+ static void
+ InitStatementTimeout(TimestampTz start_time, TimestampTz fin_time)
+ {
+ cancel_from_timeout = false;
+ base_timeouts[STATEMENT_TIMEOUT].fin_time = fin_time;
+ }
+
+ static void
+ DestroyStatementTimeout(bool keep_indicator)
+ {
+ if (!keep_indicator)
+ cancel_from_timeout = false;
+ base_timeouts[STATEMENT_TIMEOUT].fin_time = 0;
+ }
+
+ static void
+ InitStandbyDeadLock(TimestampTz start_time, TimestampTz fin_time)
+ {
+ base_timeouts[STANDBY_DEADLOCK_TIMEOUT].fin_time = fin_time;
+ }
+
+ static void
+ DestroyStandbyDeadLock(bool keep_indicator)
+ {
+ base_timeouts[STANDBY_DEADLOCK_TIMEOUT].fin_time = 0;
+ }
+
+ static void
+ InitStandbyTimeout(TimestampTz start_time, TimestampTz fin_time)
+ {
+ base_timeouts[STANDBY_TIMEOUT].fin_time = fin_time;
+ }
+
+ static void
+ DestroyStandbyTimeout(bool keep_indicator)
+ {
+ base_timeouts[STANDBY_TIMEOUT].fin_time = 0;
+ }
+
+ /*
+ * Insert tn'th timeout into the list of timeouts at the given index
+ * if the previous timeout allows rescheduling the next one in the list.
+ */
+ static void
+ insert_timeout(TimeoutName tn, int index)
+ {
+ int i;
+
+ if (index > 0 && !timeouts[index-1]->resched_next)
+ return;
+
+ for (i = n_timeouts - 1; i >= index; i--)
+ timeouts[i+1] = timeouts[i];
+
+ timeouts[index] = &base_timeouts[tn];
+ n_timeouts++;
+ }
+
+ /*
+ * Remove the index'th element from the timeout list.
+ */
+ static void
+ remove_timeout_index(int index)
+ {
+ int i;
+
+ if (index < 0)
+ return;
+
+ for (i = index + 1; i < n_timeouts; i++)
+ timeouts[i-1] = timeouts[i];
+
+ if (n_timeouts > 0 && index < n_timeouts)
+ n_timeouts--;
+ }
+
+
+ /*
+ * (Re)schedule the next active timeout
+ */
+ static bool
+ schedule_timeout(TimestampTz now)
+ {
+ long secs;
+ int usecs;
+ struct itimerval timeval;
+
+ /* There is no active timeout, do nothing. */
+ if (n_timeouts == 0)
+ return true;
+
+ TimestampDifference(now, timeouts[0]->fin_time,
+ &secs, &usecs);
+
+ /*
+ * It's possible that the difference is less than a microsecond;
+ * ensure we don't cancel, rather than set, the interrupt.
+ */
+ if (secs == 0 && usecs == 0)
+ usecs = 1;
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ timeval.it_value.tv_sec = secs;
+ timeval.it_value.tv_usec = usecs;
+ if (setitimer(ITIMER_REAL, &timeval, NULL))
+ return false;
+ return true;
+ }
+
+
+ /*
* Enable the SIGALRM interrupt to fire after the specified delay
*
* Delay is given in milliseconds. Caller should be sure a SIGALRM
* signal handler is installed before this is called.
*
! * This code properly handles nesting of different timeout alarms.
*
* Returns TRUE if okay, FALSE on failure.
*/
bool
! enable_timeout(TimeoutName tn, int delayms)
{
+ TimestampTz start_time;
TimestampTz fin_time;
! int i;
! Assert(!is_timeout_active(tn));
!
! if (delayms <= 0)
! return true;
!
! start_time = base_timeouts[tn].timeout_start();
! fin_time = TimestampTzPlusMilliseconds(start_time, delayms);
!
! /* Find out the index where to insert the new timeout. */
! for (i = 0; i < n_timeouts; i++)
{
/*
! * The new timeout triggers earlier than
! * a previously added one: insert here.
*/
! if (fin_time < timeouts[i]->fin_time)
! break;
/*
! * The new timeout triggers at the same time
! * as the previously added one but has greater priority.
*/
! if (fin_time == timeouts[i]->fin_time && tn < timeouts[i]->index)
! break;
}
+ /*
+ * Initialize the timeout parameters
+ */
+ base_timeouts[tn].timeout_init(start_time, fin_time);
+
+ insert_timeout(tn, i);
+
+ if (i > 0)
+ return true;
+
/* If we reach here, okay to set the timer interrupt */
! if (!schedule_timeout(start_time))
return false;
return true;
}
/*
! * Cancel the SIGALRM timer for the specific timeout.
! * If a timeout is canceled, any other active timeout remains in force.
*
* Returns TRUE if okay, FALSE on failure.
*/
bool
! disable_timeout(TimeoutName tn, bool keep_indicator)
{
+ int i;
+
/*
* Always disable the interrupt if it is active; this avoids being
* interrupted by the signal handler and thereby possibly getting
* confused.
*
! * We will re-enable the interrupt if necessary in ->check_timeout().
*/
! if (n_timeouts > 0)
{
struct itimerval timeval;
MemSet(&timeval, 0, sizeof(struct itimerval));
if (setitimer(ITIMER_REAL, &timeval, NULL))
{
! n_timeouts = 0;
! for (i = 0; i < TIMEOUT_MAX; i++)
! base_timeouts[i].fin_time = 0;
!
cancel_from_timeout = false;
return false;
}
}
! /* Find the timeout and remove from the list. */
! i = find_active_timeout(tn);
! remove_timeout_index(i);
! /* Do cleanup. */
! base_timeouts[tn].timeout_destroy(keep_indicator);
!
! /*
! * If the first timeout was removed from the list and there is
! * at least one active left, reschedule it.
! */
! if (i == 0 && n_timeouts > 0)
! if (!schedule_timeout(GetCurrentTimestamp()))
return false;
!
return true;
}
/*
+ * Disable SIGALRM and remove all timeouts from the list and
+ * reset the timeout indicators.
+ */
+ bool
+ disable_all_timeouts(bool keep_indicators)
+ {
+ struct itimerval timeval;
+ int i;
+ bool ret;
+
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ ret = (setitimer(ITIMER_REAL, &timeval, NULL) == 0);
+
+ n_timeouts = 0;
+ for (i = 0; i < TIMEOUT_MAX; i++)
+ base_timeouts[i].timeout_destroy(keep_indicators);
+
+ return ret;
+ }
+
+
+ /*
* Check for statement timeout. If the timeout time has come,
! * trigger a query-cancel interrupt.
*
! * Returns true if the time has come.
*/
static bool
CheckStatementTimeout(void)
{
TimestampTz now;
now = GetCurrentTimestamp();
! if (now >= base_timeouts[STATEMENT_TIMEOUT].fin_time)
{
/* Time to die */
cancel_from_timeout = true;
#ifdef HAVE_SETSID
/* try to signal whole process group */
kill(-MyProcPid, SIGINT);
#endif
kill(MyProcPid, SIGINT);
+
+ return true;
}
! return false;
! }
! /*
! * Check for lock timeout. If the timeout time has come,
! * indicate that our timeout was detected.
! */
! static bool
! CheckLockTimeout(void)
! {
! TimestampTz now;
!
! now = GetCurrentTimestamp();
!
! if (now >= base_timeouts[LOCK_TIMEOUT].fin_time)
! {
! lock_timeout_detected = true;
! return true;
}
! return false;
}
*************** handle_sig_alarm(SIGNAL_ARGS)
*** 1704,1894 ****
if (MyProc)
SetLatch(&MyProc->procLatch);
! if (deadlock_timeout_active)
! {
! deadlock_timeout_active = false;
! CheckDeadLock();
! }
!
! if (statement_timeout_active)
! (void) CheckStatementTimeout();
!
! errno = save_errno;
! }
!
! /*
! * Signal handler for SIGALRM in Startup process
! *
! * To avoid various edge cases, we must be careful to do nothing
! * when there is nothing to be done. We also need to be able to
! * reschedule the timer interrupt if called before end of statement.
! *
! * We set either deadlock_timeout_active or statement_timeout_active
! * or both. Interrupts are enabled if standby_timeout_active.
! */
! bool
! enable_standby_sig_alarm(TimestampTz now, TimestampTz fin_time, bool deadlock_only)
! {
! TimestampTz deadlock_time = TimestampTzPlusMilliseconds(now,
! DeadlockTimeout);
!
! if (deadlock_only)
! {
! /*
! * Wake up at deadlock_time only, then wait forever
! */
! statement_fin_time = deadlock_time;
! deadlock_timeout_active = true;
! statement_timeout_active = false;
! }
! else if (fin_time > deadlock_time)
! {
! /*
! * Wake up at deadlock_time, then again at fin_time
! */
! statement_fin_time = deadlock_time;
! statement_fin_time2 = fin_time;
! deadlock_timeout_active = true;
! statement_timeout_active = true;
! }
! else
! {
! /*
! * Wake only at fin_time because its fairly soon
! */
! statement_fin_time = fin_time;
! deadlock_timeout_active = false;
! statement_timeout_active = true;
! }
!
! if (deadlock_timeout_active || statement_timeout_active)
{
! long secs;
! int usecs;
! struct itimerval timeval;
!
! TimestampDifference(now, statement_fin_time,
! &secs, &usecs);
! if (secs == 0 && usecs == 0)
! usecs = 1;
! MemSet(&timeval, 0, sizeof(struct itimerval));
! timeval.it_value.tv_sec = secs;
! timeval.it_value.tv_usec = usecs;
! if (setitimer(ITIMER_REAL, &timeval, NULL))
! return false;
! standby_timeout_active = true;
! }
!
! return true;
! }
! bool
! disable_standby_sig_alarm(void)
! {
! /*
! * Always disable the interrupt if it is active; this avoids being
! * interrupted by the signal handler and thereby possibly getting
! * confused.
! *
! * We will re-enable the interrupt if necessary in CheckStandbyTimeout.
! */
! if (standby_timeout_active)
! {
! struct itimerval timeval;
! MemSet(&timeval, 0, sizeof(struct itimerval));
! if (setitimer(ITIMER_REAL, &timeval, NULL))
{
! standby_timeout_active = false;
! return false;
}
- }
! standby_timeout_active = false;
! return true;
}
/*
! * CheckStandbyTimeout() runs unconditionally in the Startup process
* SIGALRM handler. Timers will only be set when InHotStandby.
* We simply ignore any signals unless the timer has been set.
*/
static bool
! CheckStandbyTimeout(void)
{
TimestampTz now;
- bool reschedule = false;
-
- standby_timeout_active = false;
now = GetCurrentTimestamp();
! /*
! * Reschedule the timer if its not time to wake yet, or if we have both
! * timers set and the first one has just been reached.
! */
! if (now >= statement_fin_time)
! {
! if (deadlock_timeout_active)
! {
! /*
! * We're still waiting when we reach deadlock timeout, so send out
! * a request to have other backends check themselves for deadlock.
! * Then continue waiting until statement_fin_time, if that's set.
! */
! SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
! deadlock_timeout_active = false;
!
! /*
! * Begin second waiting period if required.
! */
! if (statement_timeout_active)
! {
! reschedule = true;
! statement_fin_time = statement_fin_time2;
! }
! }
! else
! {
! /*
! * We've now reached statement_fin_time, so ask all conflicts to
! * leave, so we can press ahead with applying changes in recovery.
! */
! SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
! }
! }
! else
! reschedule = true;
!
! if (reschedule)
{
! long secs;
! int usecs;
! struct itimerval timeval;
!
! TimestampDifference(now, statement_fin_time,
! &secs, &usecs);
! if (secs == 0 && usecs == 0)
! usecs = 1;
! MemSet(&timeval, 0, sizeof(struct itimerval));
! timeval.it_value.tv_sec = secs;
! timeval.it_value.tv_usec = usecs;
! if (setitimer(ITIMER_REAL, &timeval, NULL))
! return false;
! standby_timeout_active = true;
}
! return true;
}
! void
! handle_standby_sig_alarm(SIGNAL_ARGS)
{
! int save_errno = errno;
! if (standby_timeout_active)
! (void) CheckStandbyTimeout();
! errno = save_errno;
}
--- 1988,2049 ----
if (MyProc)
SetLatch(&MyProc->procLatch);
! if (n_timeouts > 0)
{
! bool ret;
! bool reschedule = true;
! ret = timeouts[0]->timeout_check();
! /* Shift timeouts if the timeout was triggered */
! if (ret)
{
! reschedule = timeouts[0]->resched_next;
! remove_timeout_index(0);
}
! if (reschedule)
! schedule_timeout(GetCurrentTimestamp());
! else
! disable_all_timeouts(true);
! }
! errno = save_errno;
}
/*
! * CheckStandby*() runs unconditionally in the Startup process
* SIGALRM handler. Timers will only be set when InHotStandby.
* We simply ignore any signals unless the timer has been set.
*/
static bool
! CheckStandbyDeadLock(void)
{
TimestampTz now;
now = GetCurrentTimestamp();
! if (now >= base_timeouts[STANDBY_DEADLOCK_TIMEOUT].fin_time)
{
! SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
! return true;
}
! return false;
}
! static bool
! CheckStandbyTimeout(void)
{
! TimestampTz now;
! now = GetCurrentTimestamp();
! if (now >= base_timeouts[STANDBY_TIMEOUT].fin_time)
! {
! SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
! return true;
! }
!
! return false;
}
diff -dcrpN postgresql.orig/src/backend/tcop/postgres.c postgresql/src/backend/tcop/postgres.c
*** postgresql.orig/src/backend/tcop/postgres.c 2012-03-28 10:54:25.195368447 +0200
--- postgresql/src/backend/tcop/postgres.c 2012-04-04 09:08:41.215191200 +0200
*************** start_xact_command(void)
*** 2368,2374 ****
/* Set statement timeout running, if any */
/* NB: this mustn't be enabled until we are within an xact */
if (StatementTimeout > 0)
! enable_sig_alarm(StatementTimeout, true);
else
cancel_from_timeout = false;
--- 2368,2374 ----
/* Set statement timeout running, if any */
/* NB: this mustn't be enabled until we are within an xact */
if (StatementTimeout > 0)
! enable_timeout(STATEMENT_TIMEOUT, StatementTimeout);
else
cancel_from_timeout = false;
*************** finish_xact_command(void)
*** 2382,2388 ****
if (xact_started)
{
/* Cancel any active statement timeout before committing */
! disable_sig_alarm(true);
/* Now commit the command */
ereport(DEBUG3,
--- 2382,2388 ----
if (xact_started)
{
/* Cancel any active statement timeout before committing */
! disable_all_timeouts(false);
/* Now commit the command */
ereport(DEBUG3,
*************** PostgresMain(int argc, char *argv[], con
*** 3728,3737 ****
/*
* Forget any pending QueryCancel request, since we're returning to
! * the idle loop anyway, and cancel the statement timer if running.
*/
QueryCancelPending = false;
! disable_sig_alarm(true);
QueryCancelPending = false; /* again in case timeout occurred */
/*
--- 3728,3737 ----
/*
* Forget any pending QueryCancel request, since we're returning to
! * the idle loop anyway, and cancel the timer if running.
*/
QueryCancelPending = false;
! disable_all_timeouts(false);
QueryCancelPending = false; /* again in case timeout occurred */
/*
diff -dcrpN postgresql.orig/src/backend/utils/adt/lockfuncs.c postgresql/src/backend/utils/adt/lockfuncs.c
*** postgresql.orig/src/backend/utils/adt/lockfuncs.c 2012-01-02 12:35:11.533185089 +0100
--- postgresql/src/backend/utils/adt/lockfuncs.c 2012-04-04 11:24:59.796965393 +0200
*************** pg_advisory_lock_int8(PG_FUNCTION_ARGS)
*** 421,427 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ExclusiveLock, true, false);
PG_RETURN_VOID();
}
--- 421,431 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ExclusiveLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_int8(PG_FUNCTION_A
*** 438,444 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
PG_RETURN_VOID();
}
--- 442,452 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_shared_int8(PG_FUNCTION
*** 454,460 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ShareLock, true, false);
PG_RETURN_VOID();
}
--- 462,472 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ShareLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_shared_int8(PG_FUN
*** 471,477 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ShareLock, false, false);
PG_RETURN_VOID();
}
--- 483,493 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_int4(PG_FUNCTION_ARGS)
*** 604,610 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ExclusiveLock, true, false);
PG_RETURN_VOID();
}
--- 620,630 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ExclusiveLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_int4(PG_FUNCTION_A
*** 622,628 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
PG_RETURN_VOID();
}
--- 642,652 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_shared_int4(PG_FUNCTION
*** 639,645 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ShareLock, true, false);
PG_RETURN_VOID();
}
--- 663,673 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ShareLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_shared_int4(PG_FUN
*** 657,663 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ShareLock, false, false);
PG_RETURN_VOID();
}
--- 685,695 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
diff -dcrpN postgresql.orig/src/backend/utils/init/postinit.c postgresql/src/backend/utils/init/postinit.c
*** postgresql.orig/src/backend/utils/init/postinit.c 2012-01-20 13:59:24.913240794 +0100
--- postgresql/src/backend/utils/init/postinit.c 2012-04-04 09:09:16.103365096 +0200
*************** PerformAuthentication(Port *port)
*** 204,210 ****
* during authentication. Since we're inside a transaction and might do
* database access, we have to use the statement_timeout infrastructure.
*/
! if (!enable_sig_alarm(AuthenticationTimeout * 1000, true))
elog(FATAL, "could not set timer for authorization timeout");
/*
--- 204,210 ----
* during authentication. Since we're inside a transaction and might do
* database access, we have to use the statement_timeout infrastructure.
*/
! if (!enable_timeout(STATEMENT_TIMEOUT, AuthenticationTimeout * 1000))
elog(FATAL, "could not set timer for authorization timeout");
/*
*************** PerformAuthentication(Port *port)
*** 215,221 ****
/*
* Done with authentication. Disable the timeout, and log if needed.
*/
! if (!disable_sig_alarm(true))
elog(FATAL, "could not disable timer for authorization timeout");
if (Log_connections)
--- 215,221 ----
/*
* Done with authentication. Disable the timeout, and log if needed.
*/
! if (!disable_timeout(STATEMENT_TIMEOUT, false))
elog(FATAL, "could not disable timer for authorization timeout");
if (Log_connections)
diff -dcrpN postgresql.orig/src/backend/utils/misc/guc.c postgresql/src/backend/utils/misc/guc.c
*** postgresql.orig/src/backend/utils/misc/guc.c 2012-03-28 10:54:25.196368452 +0200
--- postgresql/src/backend/utils/misc/guc.c 2012-04-03 15:27:36.964637553 +0200
*************** static struct config_int ConfigureNamesI
*** 1860,1865 ****
--- 1860,1876 ----
},
{
+ {"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 -dcrpN postgresql.orig/src/backend/utils/misc/postgresql.conf.sample postgresql/src/backend/utils/misc/postgresql.conf.sample
*** postgresql.orig/src/backend/utils/misc/postgresql.conf.sample 2012-03-28 10:54:25.197368458 +0200
--- postgresql/src/backend/utils/misc/postgresql.conf.sample 2012-04-03 15:29:03.725060295 +0200
***************
*** 527,532 ****
--- 527,535 ----
#------------------------------------------------------------------------------
#deadlock_timeout = 1s
+ #lock_timeout = 0 # timeout value for heavy-weight locks
+ # taken by statements. 0 disables timeout
+ # unit in milliseconds, default is 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 -dcrpN postgresql.orig/src/include/storage/pg_sema.h postgresql/src/include/storage/pg_sema.h
*** postgresql.orig/src/include/storage/pg_sema.h 2012-01-02 12:35:11.640178818 +0100
--- postgresql/src/include/storage/pg_sema.h 2012-04-03 15:30:52.716621135 +0200
*************** extern void PGSemaphoreUnlock(PGSemaphor
*** 80,83 ****
--- 80,86 ----
/* 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 */
+ extern void PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK);
+
#endif /* PG_SEMA_H */
diff -dcrpN postgresql.orig/src/include/storage/proc.h postgresql/src/include/storage/proc.h
*** postgresql.orig/src/include/storage/proc.h 2012-01-31 09:43:43.936392561 +0100
--- postgresql/src/include/storage/proc.h 2012-04-04 11:41:55.978800729 +0200
*************** extern PGPROC *PreparedXactProcs;
*** 217,225 ****
--- 217,236 ----
/* configurable options */
extern int DeadlockTimeout;
extern int StatementTimeout;
+ extern int LockTimeout;
extern bool log_lock_waits;
+ typedef enum TimeoutName {
+ DEADLOCK_TIMEOUT,
+ LOCK_TIMEOUT,
+ STATEMENT_TIMEOUT,
+ STANDBY_DEADLOCK_TIMEOUT,
+ STANDBY_TIMEOUT,
+ TIMEOUT_MAX
+ } TimeoutName;
+
extern volatile bool cancel_from_timeout;
+ extern volatile bool lock_timeout_detected;
/*
*************** extern void LockWaitCancel(void);
*** 249,261 ****
extern void ProcWaitForSignal(void);
extern void ProcSendSignal(int pid);
! extern bool enable_sig_alarm(int delayms, bool is_statement_timeout);
! extern bool disable_sig_alarm(bool is_statement_timeout);
extern void handle_sig_alarm(SIGNAL_ARGS);
- extern bool enable_standby_sig_alarm(TimestampTz now,
- TimestampTz fin_time, bool deadlock_only);
- extern bool disable_standby_sig_alarm(void);
- extern void handle_standby_sig_alarm(SIGNAL_ARGS);
-
#endif /* PROC_H */
--- 260,268 ----
extern void ProcWaitForSignal(void);
extern void ProcSendSignal(int pid);
! extern bool enable_timeout(TimeoutName tn, int delayms);
! extern bool disable_timeout(TimeoutName tn, bool keep_indicator);
! extern bool disable_all_timeouts(bool keep_indicators);
extern void handle_sig_alarm(SIGNAL_ARGS);
#endif /* PROC_H */
diff -dcrpN postgresql.orig/src/test/regress/expected/prepared_xacts.out postgresql/src/test/regress/expected/prepared_xacts.out
*** postgresql.orig/src/test/regress/expected/prepared_xacts.out 2011-08-16 09:39:01.117194748 +0200
--- postgresql/src/test/regress/expected/prepared_xacts.out 2012-04-04 12:22:18.719786919 +0200
*************** set statement_timeout to 2000;
*** 198,203 ****
--- 198,210 ----
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: could not obtain lock on relation "pxtest3"
+ LINE 1: SELECT * FROM pxtest3;
+ ^
+ reset lock_timeout;
-- Disconnect, we will continue testing in a different backend
\c -
-- There should still be two prepared transactions
*************** set statement_timeout to 2000;
*** 213,218 ****
--- 220,232 ----
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: could not obtain lock on relation "pxtest3"
+ LINE 1: SELECT * FROM pxtest3;
+ ^
+ reset lock_timeout;
-- Commit table creation
COMMIT PREPARED 'regress-one';
\d pxtest2
diff -dcrpN postgresql.orig/src/test/regress/sql/prepared_xacts.sql postgresql/src/test/regress/sql/prepared_xacts.sql
*** postgresql.orig/src/test/regress/sql/prepared_xacts.sql 2011-07-18 15:42:00.117371861 +0200
--- postgresql/src/test/regress/sql/prepared_xacts.sql 2012-04-04 12:20:31.456241386 +0200
*************** set statement_timeout to 2000;
*** 126,131 ****
--- 126,136 ----
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 -
*************** set statement_timeout to 2000;
*** 137,142 ****
--- 142,152 ----
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
Hi,
2012-04-04 12:30 keltezéssel, Boszormenyi Zoltan írta:
Hi,
attached is a patch to implement a framework to simplify and
correctly nest multiplexing more than two timeout sources
into the same SIGALRM signal handler.The framework is using a new internal API for timeouts:
bool enable_timeout(TimeoutName tn, int delayms);
bool disable_timeout(TimeoutName tn, bool keep_indicator);
bool disable_all_timeouts(bool keep_indicators);A timeout source has these features to allow different initialization,
cleanup and check functions and rescheduling:typedef void (*timeout_init)(TimestampTz, TimestampTz);
typedef void (*timeout_destroy)(bool);
typedef bool (*timeout_check)(void);
typedef TimestampTz (*timeout_start)(void);typedef struct {
TimeoutName index;
bool resched_next;
timeout_init timeout_init;
timeout_destroy timeout_destroy;
timeout_check timeout_check;
timeout_start timeout_start;
TimestampTz fin_time;
} timeout_params;This makes it possible to differentiate between the standby and
statement timeouts, regular deadlock and standby deadlock using
the same signal handler function.And finally, this makes it possible to implement the lock_timeout
feature that we at Cybertec implemented more than 2 years ago.The patch also adds two new tests into prepared_xacts.sql to trigger
the lock_timeout instead of statement_timeout.Documentation and extensive comments are included.
Second version. Every timeout-related functions are now in a separate
source file, src/backend/storage/timeout.c with accessor functions.
There are no related global variables anymore, only the GUCs.
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/
Attachments:
pg92-common-lock-framework-lock_timeout-v2.patchtext/x-patch; name=pg92-common-lock-framework-lock_timeout-v2.patchDownload
diff -dcrpN postgresql.orig/doc/src/sgml/config.sgml postgresql/doc/src/sgml/config.sgml
*** postgresql.orig/doc/src/sgml/config.sgml 2012-03-28 10:54:25.177368339 +0200
--- postgresql/doc/src/sgml/config.sgml 2012-04-04 10:36:01.982604984 +0200
*************** COPY postgres_log FROM '/full/path/to/lo
*** 4944,4950 ****
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.
</para>
<para>
--- 4944,4953 ----
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. 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>
*************** COPY postgres_log FROM '/full/path/to/lo
*** 4955,4960 ****
--- 4958,4991 ----
</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 has to wait more
+ than the specified number of milliseconds, starting from the time the
+ command arrives at the server from the client. If the statement involves
+ more than one such lock, the timeout applies to every one of them.
+ 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 to acquire.
+ As opposed to <varname>statement_timeout</>, this timeout (and the error)
+ may only occurs 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 off the limitation.
+ </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 -dcrpN postgresql.orig/doc/src/sgml/ref/lock.sgml postgresql/doc/src/sgml/ref/lock.sgml
*** postgresql.orig/doc/src/sgml/ref/lock.sgml 2011-08-07 11:29:16.004256905 +0200
--- postgresql/doc/src/sgml/ref/lock.sgml 2012-04-03 15:54:26.543224868 +0200
*************** LOCK [ TABLE ] [ ONLY ] <replaceable cla
*** 39,46 ****
<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.)
</para>
--- 39,49 ----
<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. 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 -dcrpN postgresql.orig/doc/src/sgml/ref/select.sgml postgresql/doc/src/sgml/ref/select.sgml
*** postgresql.orig/doc/src/sgml/ref/select.sgml 2012-01-29 20:33:53.893650856 +0100
--- postgresql/doc/src/sgml/ref/select.sgml 2012-04-04 10:30:35.035960738 +0200
*************** FOR SHARE [ OF <replaceable class="param
*** 1199,1204 ****
--- 1199,1211 ----
</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 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 -dcrpN postgresql.orig/src/backend/port/posix_sema.c postgresql/src/backend/port/posix_sema.c
*** postgresql.orig/src/backend/port/posix_sema.c 2012-01-02 12:35:11.502186906 +0100
--- postgresql/src/backend/port/posix_sema.c 2012-04-04 15:01:25.781802645 +0200
***************
*** 24,29 ****
--- 24,30 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/timeout.h"
#ifdef USE_NAMED_POSIX_SEMAPHORES
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 313,315 ****
--- 314,341 ----
return true;
}
+
+ /*
+ * PGSemaphoreTimedLock
+ *
+ * Lock a semaphore (decrement count), blocking if count would be < 0
+ * Return if lock_timeout expired
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ int errStatus;
+
+ do
+ {
+ ImmediateInterruptOK = interruptOK;
+ CHECK_FOR_INTERRUPTS();
+ errStatus = sem_wait(PG_SEM_REF(sema));
+ ImmediateInterruptOK = false;
+ } while (errStatus < 0 && errno == EINTR && !get_timeout_indicator(LOCK_TIMEOUT));
+
+ if (get_timeout_indicator(LOCK_TIMEOUT))
+ return;
+ if (errStatus < 0)
+ elog(FATAL, "sem_wait failed: %m");
+ }
diff -dcrpN postgresql.orig/src/backend/port/sysv_sema.c postgresql/src/backend/port/sysv_sema.c
*** postgresql.orig/src/backend/port/sysv_sema.c 2012-01-02 12:35:11.503186847 +0100
--- postgresql/src/backend/port/sysv_sema.c 2012-04-04 15:01:16.902757977 +0200
***************
*** 30,35 ****
--- 30,36 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/timeout.h"
#ifndef HAVE_UNION_SEMUN
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 495,497 ****
--- 496,528 ----
return true;
}
+
+ /*
+ * PGSemaphoreTimedLock
+ *
+ * Lock a semaphore (decrement count), blocking if count would be < 0
+ * Return if lock_timeout expired
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ int errStatus;
+ 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;
+ } while (errStatus < 0 && errno == EINTR && !get_timeout_indicator(LOCK_TIMEOUT));
+
+ if (get_timeout_indicator(LOCK_TIMEOUT))
+ return;
+ if (errStatus < 0)
+ elog(FATAL, "semop(id=%d) failed: %m", sema->semId);
+ }
diff -dcrpN postgresql.orig/src/backend/port/win32_sema.c postgresql/src/backend/port/win32_sema.c
*** postgresql.orig/src/backend/port/win32_sema.c 2012-01-02 12:35:11.504186789 +0100
--- postgresql/src/backend/port/win32_sema.c 2012-04-04 15:01:35.197850015 +0200
***************
*** 16,21 ****
--- 16,22 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/timeout.h"
static HANDLE *mySemSet; /* IDs of sema sets acquired so far */
static int numSems; /* number of sema sets acquired so far */
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 205,207 ****
--- 206,263 ----
/* 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.
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ DWORD ret;
+ HANDLE wh[2];
+
+ wh[0] = *sema;
+ wh[1] = pgwin32_signal_event;
+
+ /*
+ * 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();
+
+ errno = 0;
+ ret = WaitForMultipleObjectsEx(2, wh, FALSE, INFINITE, TRUE);
+
+ if (ret == WAIT_OBJECT_0)
+ {
+ /* We got it! */
+ return;
+ }
+ else if (ret == WAIT_OBJECT_0 + 1)
+ {
+ /* Signal event is set - we have a signal to deliver */
+ pgwin32_dispatch_queued_signals();
+ errno = EINTR;
+ }
+ else
+ /* Otherwise we are in trouble */
+ errno = EIDRM;
+
+ ImmediateInterruptOK = false;
+ } while (errno == EINTR && !get_timeout_indicator(LOCK_TIMEOUT));
+
+ if (get_timeout_indicator(LOCK_TIMEOUT))
+ return;
+ if (errno != 0)
+ ereport(FATAL,
+ (errmsg("could not lock semaphore: error code %d", (int) GetLastError())));
+ }
diff -dcrpN postgresql.orig/src/backend/postmaster/autovacuum.c postgresql/src/backend/postmaster/autovacuum.c
*** postgresql.orig/src/backend/postmaster/autovacuum.c 2012-01-27 10:29:06.483649830 +0100
--- postgresql/src/backend/postmaster/autovacuum.c 2012-04-04 15:03:20.447379486 +0200
***************
*** 86,94 ****
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
- #include "storage/proc.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "tcop/tcopprot.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
--- 86,94 ----
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
+ #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
*************** AutoVacLauncherMain(int argc, char *argv
*** 484,490 ****
/* Forget any pending QueryCancel request */
QueryCancelPending = false;
! disable_sig_alarm(true);
QueryCancelPending = false; /* again in case timeout occurred */
/* Report the error to the server log */
--- 484,490 ----
/* Forget any pending QueryCancel request */
QueryCancelPending = false;
! disable_all_timeouts(false);
QueryCancelPending = false; /* again in case timeout occurred */
/* Report the error to the server log */
diff -dcrpN postgresql.orig/src/backend/postmaster/postmaster.c postgresql/src/backend/postmaster/postmaster.c
*** postgresql.orig/src/backend/postmaster/postmaster.c 2012-03-29 08:36:59.775468454 +0200
--- postgresql/src/backend/postmaster/postmaster.c 2012-04-04 15:03:54.927552935 +0200
***************
*** 112,118 ****
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
! #include "storage/proc.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/datetime.h"
--- 112,118 ----
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
! #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/datetime.h"
*************** BackendInitialize(Port *port)
*** 3466,3474 ****
* Ready to begin client interaction. We will give up and exit(1) after a
* time delay, so that a broken client can't hog a connection
* indefinitely. PreAuthDelay and any DNS interactions above don't count
! * against the time limit.
*/
! if (!enable_sig_alarm(AuthenticationTimeout * 1000, false))
elog(FATAL, "could not set timer for startup packet timeout");
/*
--- 3466,3474 ----
* Ready to begin client interaction. We will give up and exit(1) after a
* time delay, so that a broken client can't hog a connection
* indefinitely. PreAuthDelay and any DNS interactions above don't count
! * against the time limit. Use the deadlock timeout interface.
*/
! if (!enable_timeout(DEADLOCK_TIMEOUT, AuthenticationTimeout * 1000))
elog(FATAL, "could not set timer for startup packet timeout");
/*
*************** BackendInitialize(Port *port)
*** 3506,3512 ****
/*
* Disable the timeout, and prevent SIGTERM/SIGQUIT again.
*/
! if (!disable_sig_alarm(false))
elog(FATAL, "could not disable timer for startup packet timeout");
PG_SETMASK(&BlockSig);
}
--- 3506,3512 ----
/*
* Disable the timeout, and prevent SIGTERM/SIGQUIT again.
*/
! if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for startup packet timeout");
PG_SETMASK(&BlockSig);
}
diff -dcrpN postgresql.orig/src/backend/postmaster/startup.c postgresql/src/backend/postmaster/startup.c
*** postgresql.orig/src/backend/postmaster/startup.c 2012-01-02 12:35:11.508186555 +0100
--- postgresql/src/backend/postmaster/startup.c 2012-04-04 15:04:32.902743966 +0200
***************
*** 27,33 ****
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
! #include "storage/proc.h"
#include "utils/guc.h"
--- 27,33 ----
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
! #include "storage/timeout.h"
#include "utils/guc.h"
*************** StartupProcessMain(void)
*** 195,202 ****
pqsignal(SIGTERM, StartupProcShutdownHandler); /* request shutdown */
pqsignal(SIGQUIT, startupproc_quickdie); /* hard crash time */
if (EnableHotStandby)
! pqsignal(SIGALRM, handle_standby_sig_alarm); /* ignored unless
! * InHotStandby */
else
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
--- 195,202 ----
pqsignal(SIGTERM, StartupProcShutdownHandler); /* request shutdown */
pqsignal(SIGQUIT, startupproc_quickdie); /* hard crash time */
if (EnableHotStandby)
! pqsignal(SIGALRM, handle_sig_alarm); /* ignored unless
! * InHotStandby */
else
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
diff -dcrpN postgresql.orig/src/backend/storage/ipc/standby.c postgresql/src/backend/storage/ipc/standby.c
*** postgresql.orig/src/backend/storage/ipc/standby.c 2012-02-05 12:28:36.003281960 +0100
--- postgresql/src/backend/storage/ipc/standby.c 2012-04-04 15:05:29.887030606 +0200
***************
*** 23,32 ****
#include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
- #include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/sinvaladt.h"
#include "storage/standby.h"
#include "utils/ps_status.h"
#include "utils/timestamp.h"
--- 23,32 ----
#include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
#include "storage/procarray.h"
#include "storage/sinvaladt.h"
#include "storage/standby.h"
+ #include "storage/timeout.h"
#include "utils/ps_status.h"
#include "utils/timestamp.h"
*************** ResolveRecoveryConflictWithLock(Oid dbOi
*** 394,400 ****
void
ResolveRecoveryConflictWithBufferPin(void)
{
- bool sig_alarm_enabled = false;
TimestampTz ltime;
TimestampTz now;
--- 394,399 ----
*************** ResolveRecoveryConflictWithBufferPin(voi
*** 409,417 ****
* We're willing to wait forever for conflicts, so set timeout for
* deadlock check (only)
*/
! if (enable_standby_sig_alarm(now, now, true))
! sig_alarm_enabled = true;
! else
elog(FATAL, "could not set timer for process wakeup");
}
else if (now >= ltime)
--- 408,414 ----
* We're willing to wait forever for conflicts, so set timeout for
* deadlock check (only)
*/
! if (!enable_timeout(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout))
elog(FATAL, "could not set timer for process wakeup");
}
else if (now >= ltime)
*************** ResolveRecoveryConflictWithBufferPin(voi
*** 423,446 ****
}
else
{
/*
* Wake up at ltime, and check for deadlocks as well if we will be
* waiting longer than deadlock_timeout
*/
! if (enable_standby_sig_alarm(now, ltime, false))
! sig_alarm_enabled = true;
! else
elog(FATAL, "could not set timer for process wakeup");
}
/* Wait to be signaled by UnpinBuffer() */
ProcWaitForSignal();
! if (sig_alarm_enabled)
! {
! if (!disable_standby_sig_alarm())
! elog(FATAL, "could not disable timer for process wakeup");
! }
}
void
--- 420,447 ----
}
else
{
+ long secs, msecs;
+ int usecs;
+
/*
* Wake up at ltime, and check for deadlocks as well if we will be
* waiting longer than deadlock_timeout
*/
! if (!enable_timeout(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout))
! elog(FATAL, "could not set timer for process wakeup");
!
! TimestampDifference(now, ltime, &secs, &usecs);
! msecs = secs * 1000 + usecs / 1000;
!
! if (!enable_timeout(STANDBY_TIMEOUT, msecs))
elog(FATAL, "could not set timer for process wakeup");
}
/* Wait to be signaled by UnpinBuffer() */
ProcWaitForSignal();
! if (!disable_all_timeouts(false))
! elog(FATAL, "could not disable timer for process wakeup");
}
void
diff -dcrpN postgresql.orig/src/backend/storage/lmgr/lmgr.c postgresql/src/backend/storage/lmgr/lmgr.c
*** postgresql.orig/src/backend/storage/lmgr/lmgr.c 2012-01-02 12:35:11.517186027 +0100
--- postgresql/src/backend/storage/lmgr/lmgr.c 2012-04-03 15:55:39.981617113 +0200
***************
*** 19,26 ****
--- 19,29 ----
#include "access/transam.h"
#include "access/xact.h"
#include "catalog/catalog.h"
+ #include "catalog/pg_database.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
+ #include "utils/lsyscache.h"
+ #include "storage/proc.h"
#include "storage/procarray.h"
#include "utils/inval.h"
*************** LockRelationOid(Oid relid, LOCKMODE lock
*** 78,83 ****
--- 81,101 ----
res = LockAcquire(&tag, lockmode, false, false);
+ if (res == LOCKACQUIRE_NOT_AVAIL)
+ {
+ char *relname = get_rel_name(relid);
+ if (relname)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ relname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation with OID %u",
+ relid)));
+ }
+
/*
* Now that we have the lock, check for invalidation messages, so that we
* will update or flush any stale relcache entry before we try to use it.
*************** LockRelation(Relation relation, LOCKMODE
*** 174,179 ****
--- 192,203 ----
res = LockAcquire(&tag, lockmode, false, false);
+ if (res == LOCKACQUIRE_NOT_AVAIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ RelationGetRelationName(relation))));
+
/*
* Now that we have the lock, check for invalidation messages; see notes
* in LockRelationOid.
*************** LockRelationIdForSession(LockRelId *reli
*** 251,257 ****
SET_LOCKTAG_RELATION(tag, relid->dbId, relid->relId);
! (void) LockAcquire(&tag, lockmode, true, false);
}
/*
--- 275,294 ----
SET_LOCKTAG_RELATION(tag, relid->dbId, relid->relId);
! if (LockAcquire(&tag, lockmode, true, false) == LOCKACQUIRE_NOT_AVAIL)
! {
! char *relname = get_rel_name(relid->relId);
! if (relname)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on relation \"%s\"",
! relname)));
! else
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on relation with OID %u",
! relid->relId)));
! }
}
/*
*************** LockRelationForExtension(Relation relati
*** 286,292 ****
relation->rd_lockInfo.lockRelId.dbId,
relation->rd_lockInfo.lockRelId.relId);
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 323,333 ----
relation->rd_lockInfo.lockRelId.dbId,
relation->rd_lockInfo.lockRelId.relId);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on index \"%s\"",
! RelationGetRelationName(relation))));
}
/*
*************** LockPage(Relation relation, BlockNumber
*** 320,326 ****
relation->rd_lockInfo.lockRelId.relId,
blkno);
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 361,371 ----
relation->rd_lockInfo.lockRelId.relId,
blkno);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on page %u of relation \"%s\"",
! blkno, RelationGetRelationName(relation))));
}
/*
*************** LockTuple(Relation relation, ItemPointer
*** 376,382 ****
ItemPointerGetBlockNumber(tid),
ItemPointerGetOffsetNumber(tid));
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 421,431 ----
ItemPointerGetBlockNumber(tid),
ItemPointerGetOffsetNumber(tid));
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on row in relation \"%s\"",
! RelationGetRelationName(relation))));
}
/*
*************** XactLockTableInsert(TransactionId xid)
*** 430,436 ****
SET_LOCKTAG_TRANSACTION(tag, xid);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
}
/*
--- 479,488 ----
SET_LOCKTAG_TRANSACTION(tag, xid);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on transaction with ID %u", xid)));
}
/*
*************** XactLockTableWait(TransactionId xid)
*** 474,480 ****
SET_LOCKTAG_TRANSACTION(tag, xid);
! (void) LockAcquire(&tag, ShareLock, false, false);
LockRelease(&tag, ShareLock, false);
--- 526,535 ----
SET_LOCKTAG_TRANSACTION(tag, xid);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on transaction with ID %u", xid)));
LockRelease(&tag, ShareLock, false);
*************** LockDatabaseObject(Oid classid, Oid obji
*** 535,541 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, false, false);
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
--- 590,600 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
*************** LockSharedObject(Oid classid, Oid objid,
*** 576,582 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, false, false);
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
--- 635,645 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
*************** LockSharedObjectForSession(Oid classid,
*** 618,624 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, true, false);
}
/*
--- 681,702 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, true, false) == LOCKACQUIRE_NOT_AVAIL)
! switch(classid)
! {
! case DatabaseRelationId:
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on database with ID %u",
! objid)));
! break;
! default:
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
! break;
! }
}
/*
diff -dcrpN postgresql.orig/src/backend/storage/lmgr/lock.c postgresql/src/backend/storage/lmgr/lock.c
*** postgresql.orig/src/backend/storage/lmgr/lock.c 2012-01-25 06:45:42.322747976 +0100
--- postgresql/src/backend/storage/lmgr/lock.c 2012-04-04 10:47:12.072945214 +0200
*************** static void RemoveLocalLock(LOCALLOCK *l
*** 338,344 ****
static PROCLOCK *SetupLockInTable(LockMethod lockMethodTable, PGPROC *proc,
const LOCKTAG *locktag, uint32 hashcode, LOCKMODE lockmode);
static void GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner);
! static void WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner);
static void ReleaseLockForOwner(LOCALLOCK *locallock, ResourceOwner owner);
static bool UnGrantLock(LOCK *lock, LOCKMODE lockmode,
PROCLOCK *proclock, LockMethod lockMethodTable);
--- 338,344 ----
static PROCLOCK *SetupLockInTable(LockMethod lockMethodTable, PGPROC *proc,
const LOCKTAG *locktag, uint32 hashcode, LOCKMODE lockmode);
static void GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner);
! static int WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner);
static void ReleaseLockForOwner(LOCALLOCK *locallock, ResourceOwner owner);
static bool UnGrantLock(LOCK *lock, LOCKMODE lockmode,
PROCLOCK *proclock, LockMethod lockMethodTable);
*************** ProcLockHashCode(const PROCLOCKTAG *proc
*** 544,550 ****
* dontWait: if true, don't wait to acquire lock
*
* Returns one of:
! * LOCKACQUIRE_NOT_AVAIL lock not available, and dontWait=true
* LOCKACQUIRE_OK lock successfully acquired
* LOCKACQUIRE_ALREADY_HELD incremented count for lock already held
*
--- 544,550 ----
* dontWait: if true, don't wait to acquire lock
*
* Returns one of:
! * LOCKACQUIRE_NOT_AVAIL lock not available, either dontWait=true or timeout
* LOCKACQUIRE_OK lock successfully acquired
* LOCKACQUIRE_ALREADY_HELD incremented count for lock already held
*
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 863,869 ****
locktag->locktag_type,
lockmode);
! WaitOnLock(locallock, owner);
TRACE_POSTGRESQL_LOCK_WAIT_DONE(locktag->locktag_field1,
locktag->locktag_field2,
--- 863,869 ----
locktag->locktag_type,
lockmode);
! status = WaitOnLock(locallock, owner);
TRACE_POSTGRESQL_LOCK_WAIT_DONE(locktag->locktag_field1,
locktag->locktag_field2,
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 878,897 ****
* done when the lock was granted to us --- see notes in WaitOnLock.
*/
! /*
! * Check the proclock entry status, in case something in the ipc
! * communication doesn't work correctly.
! */
! if (!(proclock->holdMask & LOCKBIT_ON(lockmode)))
{
! PROCLOCK_PRINT("LockAcquire: INCONSISTENT", proclock);
! LOCK_PRINT("LockAcquire: INCONSISTENT", lock, lockmode);
! /* Should we retry ? */
! LWLockRelease(partitionLock);
! elog(ERROR, "LockAcquire failed");
}
- PROCLOCK_PRINT("LockAcquire: granted", proclock);
- LOCK_PRINT("LockAcquire: granted", lock, lockmode);
}
LWLockRelease(partitionLock);
--- 878,909 ----
* done when the lock was granted to us --- see notes in WaitOnLock.
*/
! switch (status)
{
! case STATUS_OK:
! /*
! * Check the proclock entry status, in case something in the ipc
! * communication doesn't work correctly.
! */
! if (!(proclock->holdMask & LOCKBIT_ON(lockmode)))
! {
! PROCLOCK_PRINT("LockAcquire: INCONSISTENT", proclock);
! LOCK_PRINT("LockAcquire: INCONSISTENT", lock, lockmode);
! /* Should we retry ? */
! LWLockRelease(partitionLock);
! elog(ERROR, "LockAcquire failed");
! }
! PROCLOCK_PRINT("LockAcquire: granted", proclock);
! LOCK_PRINT("LockAcquire: granted", lock, lockmode);
! break;
! case STATUS_WAITING:
! PROCLOCK_PRINT("LockAcquire: timed out", proclock);
! LOCK_PRINT("LockAcquire: timed out", lock, lockmode);
! break;
! default:
! elog(ERROR, "LockAcquire invalid status");
! break;
}
}
LWLockRelease(partitionLock);
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 911,917 ****
locktag->locktag_field2);
}
! return LOCKACQUIRE_OK;
}
/*
--- 923,929 ----
locktag->locktag_field2);
}
! return (status == STATUS_OK ? LOCKACQUIRE_OK : LOCKACQUIRE_NOT_AVAIL);
}
/*
*************** GrantAwaitedLock(void)
*** 1371,1384 ****
* Caller must have set MyProc->heldLocks to reflect locks already held
* on the lockable object by this process.
*
* The appropriate partition lock must be held at entry.
*/
! static void
WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner)
{
LOCKMETHODID lockmethodid = LOCALLOCK_LOCKMETHOD(*locallock);
LockMethod lockMethodTable = LockMethods[lockmethodid];
char *volatile new_status = NULL;
LOCK_PRINT("WaitOnLock: sleeping on lock",
locallock->lock, locallock->tag.mode);
--- 1383,1402 ----
* Caller must have set MyProc->heldLocks to reflect locks already held
* on the lockable object by this process.
*
+ * Result: returns value of ProcSleep()
+ * STATUS_OK if we acquired the lock
+ * STATUS_ERROR if not (deadlock)
+ * STATUS_WAITING if not (timeout)
+ *
* The appropriate partition lock must be held at entry.
*/
! static int
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);
*************** WaitOnLock(LOCALLOCK *locallock, Resourc
*** 1420,1427 ****
*/
PG_TRY();
{
! if (ProcSleep(locallock, lockMethodTable) != STATUS_OK)
{
/*
* We failed as a result of a deadlock, see CheckDeadLock(). Quit
* now.
--- 1438,1450 ----
*/
PG_TRY();
{
! wait_status = ProcSleep(locallock, lockMethodTable);
! switch (wait_status)
{
+ case STATUS_OK:
+ case STATUS_WAITING:
+ break;
+ default:
/*
* We failed as a result of a deadlock, see CheckDeadLock(). Quit
* now.
*************** WaitOnLock(LOCALLOCK *locallock, Resourc
*** 1466,1473 ****
pfree(new_status);
}
! LOCK_PRINT("WaitOnLock: wakeup on lock",
locallock->lock, locallock->tag.mode);
}
/*
--- 1489,1502 ----
pfree(new_status);
}
! 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);
+
+ return wait_status;
}
/*
*************** VirtualXactLock(VirtualTransactionId vxi
*** 3739,3745 ****
LWLockRelease(proc->backendLock);
/* Time to wait. */
! (void) LockAcquire(&tag, ShareLock, false, false);
LockRelease(&tag, ShareLock, false);
return true;
--- 3768,3778 ----
LWLockRelease(proc->backendLock);
/* Time to wait. */
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on virtual transaction with ID %u",
! vxid.localTransactionId)));
LockRelease(&tag, ShareLock, false);
return true;
diff -dcrpN postgresql.orig/src/backend/storage/lmgr/Makefile postgresql/src/backend/storage/lmgr/Makefile
*** postgresql.orig/src/backend/storage/lmgr/Makefile 2011-02-10 10:36:32.248685484 +0100
--- postgresql/src/backend/storage/lmgr/Makefile 2012-04-04 14:31:15.547760162 +0200
*************** subdir = src/backend/storage/lmgr
*** 12,18 ****
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
! OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o predicate.o
include $(top_srcdir)/src/backend/common.mk
--- 12,18 ----
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
! OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o predicate.o timeout.o
include $(top_srcdir)/src/backend/common.mk
diff -dcrpN postgresql.orig/src/backend/storage/lmgr/proc.c postgresql/src/backend/storage/lmgr/proc.c
*** postgresql.orig/src/backend/storage/lmgr/proc.c 2012-03-22 11:00:51.723002056 +0100
--- postgresql/src/backend/storage/lmgr/proc.c 2012-04-04 14:57:50.740720758 +0200
***************
*** 48,59 ****
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/spin.h"
#include "utils/timestamp.h"
/* GUC variables */
- int DeadlockTimeout = 1000;
- int StatementTimeout = 0;
bool log_lock_waits = false;
/* Pointer to this process's PGPROC struct, if any */
--- 48,58 ----
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/spin.h"
+ #include "storage/timeout.h"
#include "utils/timestamp.h"
/* GUC variables */
bool log_lock_waits = false;
/* Pointer to this process's PGPROC struct, if any */
*************** PGPROC *PreparedXactProcs = NULL;
*** 77,103 ****
/* If we are waiting for a lock, this points to the associated LOCALLOCK */
static LOCALLOCK *lockAwaited = NULL;
! /* Mark these volatile because they can be changed by signal handler */
! static volatile bool standby_timeout_active = false;
! static volatile bool statement_timeout_active = false;
! static volatile bool deadlock_timeout_active = false;
! static volatile DeadLockState deadlock_state = DS_NOT_YET_CHECKED;
! volatile bool cancel_from_timeout = false;
/* timeout_start_time is set when log_lock_waits is true */
static TimestampTz timeout_start_time;
- /* statement_fin_time is valid only if statement_timeout_active is true */
- static TimestampTz statement_fin_time;
- static TimestampTz statement_fin_time2; /* valid only in recovery */
-
-
static void RemoveProcFromArray(int code, Datum arg);
static void ProcKill(int code, Datum arg);
static void AuxiliaryProcKill(int code, Datum arg);
- static bool CheckStatementTimeout(void);
- static bool CheckStandbyTimeout(void);
-
/*
* Report shared-memory space needed by InitProcGlobal.
--- 76,90 ----
/* If we are waiting for a lock, this points to the associated LOCALLOCK */
static LOCALLOCK *lockAwaited = NULL;
! /* Mark this volatile because they can be changed by signal handler */
! volatile DeadLockState deadlock_state = DS_NOT_YET_CHECKED;
/* timeout_start_time is set when log_lock_waits is true */
static TimestampTz timeout_start_time;
static void RemoveProcFromArray(int code, Datum arg);
static void ProcKill(int code, Datum arg);
static void AuxiliaryProcKill(int code, Datum arg);
/*
* Report shared-memory space needed by InitProcGlobal.
*************** LockWaitCancel(void)
*** 651,657 ****
return;
/* Turn off the deadlock timer, if it's still running (see ProcSleep) */
! disable_sig_alarm(false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
--- 638,644 ----
return;
/* Turn off the deadlock timer, if it's still running (see ProcSleep) */
! disable_timeout(DEADLOCK_TIMEOUT, false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
*************** ProcQueueInit(PROC_QUEUE *queue)
*** 885,891 ****
* 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).
*
* ASSUME: that no one will fiddle with the queue until after
* we release the partition lock.
--- 872,881 ----
* 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)
! * STATUS_WAITING if not (timeout)
*
* ASSUME: that no one will fiddle with the queue until after
* we release the partition lock.
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 907,912 ****
--- 897,903 ----
LOCKMASK myHeldLocks = MyProc->heldLocks;
bool early_deadlock = false;
bool allow_autovacuum_cancel = true;
+ bool timeout_detected;
int myWaitStatus;
PGPROC *proc;
int i;
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1044,1055 ****
* 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 (!enable_sig_alarm(DeadlockTimeout, false))
elog(FATAL, "could not set timer for process wakeup");
/*
! * 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
* saved wakeup might be leftover from a previous operation (for example,
* we aborted ProcWaitForSignal just before someone did ProcSendSignal).
--- 1035,1054 ----
* 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 (!enable_timeout(DEADLOCK_TIMEOUT, DeadlockTimeout))
elog(FATAL, "could not set timer for process wakeup");
/*
! * Reset timer so we are awaken in case of lock timeout.
! * This doesn't modify the timer for deadlock check in case
! * the deadlock check happens earlier.
! */
! if (!enable_timeout(LOCK_TIMEOUT, LockTimeout))
! elog(FATAL, "could not set timer for process wakeup");
!
! /*
! * 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).
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1066,1072 ****
*/
do
{
! PGSemaphoreLock(&MyProc->sem, true);
/*
* waitStatus could change from STATUS_WAITING to something else
--- 1065,1074 ----
*/
do
{
! PGSemaphoreTimedLock(&MyProc->sem, true);
!
! if (get_timeout_indicator(LOCK_TIMEOUT))
! break;
/*
* waitStatus could change from STATUS_WAITING to something else
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1194,1202 ****
} while (myWaitStatus == STATUS_WAITING);
/*
! * Disable the timer, if it's still running
*/
! if (!disable_sig_alarm(false))
elog(FATAL, "could not disable timer for process wakeup");
/*
--- 1196,1212 ----
} while (myWaitStatus == STATUS_WAITING);
/*
! * Disable the deadlock timer, if it's still running
*/
! if (!disable_timeout(DEADLOCK_TIMEOUT, false))
! elog(FATAL, "could not disable timer for process wakeup");
!
! /*
! * Disable the lock timeout timer, if it's still running
! * but keep the indicator for later checks.
! */
! timeout_detected = get_timeout_indicator(LOCK_TIMEOUT);
! if (!disable_timeout(LOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for process wakeup");
/*
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1207,1212 ****
--- 1217,1231 ----
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 LockWaitCancel to do anything.
*/
lockAwaited = NULL;
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1220,1227 ****
/*
* We don't have to do anything else, because the awaker did all the
* necessary update of the lock table and MyProc.
*/
! return MyProc->waitStatus;
}
--- 1239,1248 ----
/*
* 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 (timeout_detected ? STATUS_WAITING : MyProc->waitStatus);
}
*************** ProcLockWakeup(LockMethod lockMethodTabl
*** 1328,1448 ****
}
/*
- * CheckDeadLock
- *
- * We only get to this routine if we got SIGALRM after DeadlockTimeout
- * while waiting for a lock to be released by some other process. Look
- * to see if there's a deadlock; if not, just return and continue waiting.
- * (But signal ProcSleep to log a message, if log_lock_waits is true.)
- * If we have a real deadlock, remove ourselves from the lock's wait queue
- * and signal an error to ProcSleep.
- *
- * NB: this is run inside a signal handler, so be very wary about what is done
- * here or in called routines.
- */
- static void
- CheckDeadLock(void)
- {
- int i;
-
- /*
- * Acquire exclusive lock on the entire shared lock data structures. Must
- * grab LWLocks in partition-number order to avoid LWLock deadlock.
- *
- * Note that the deadlock check interrupt had better not be enabled
- * anywhere that this process itself holds lock partition locks, else this
- * will wait forever. Also note that LWLockAcquire creates a critical
- * section, so that this routine cannot be interrupted by cancel/die
- * interrupts.
- */
- for (i = 0; i < NUM_LOCK_PARTITIONS; i++)
- LWLockAcquire(FirstLockMgrLock + i, LW_EXCLUSIVE);
-
- /*
- * Check to see if we've been awoken by anyone in the interim.
- *
- * If we have, we can return and resume our transaction -- happy day.
- * Before we are awoken the process releasing the lock grants it to us so
- * we know that we don't have to wait anymore.
- *
- * We check by looking to see if we've been unlinked from the wait queue.
- * This is quicker than checking our semaphore's state, since no kernel
- * call is needed, and it is safe because we hold the lock partition lock.
- */
- if (MyProc->links.prev == NULL ||
- MyProc->links.next == NULL)
- goto check_done;
-
- #ifdef LOCK_DEBUG
- if (Debug_deadlocks)
- DumpAllLocks();
- #endif
-
- /* Run the deadlock check, and set deadlock_state for use by ProcSleep */
- deadlock_state = DeadLockCheck(MyProc);
-
- if (deadlock_state == DS_HARD_DEADLOCK)
- {
- /*
- * Oops. We have a deadlock.
- *
- * Get this process out of wait state. (Note: we could do this more
- * efficiently by relying on lockAwaited, but use this coding to
- * preserve the flexibility to kill some other transaction than the
- * one detecting the deadlock.)
- *
- * RemoveFromWaitQueue sets MyProc->waitStatus to STATUS_ERROR, so
- * ProcSleep will report an error after we return from the signal
- * handler.
- */
- Assert(MyProc->waitLock != NULL);
- RemoveFromWaitQueue(MyProc, LockTagHashCode(&(MyProc->waitLock->tag)));
-
- /*
- * Unlock my semaphore so that the interrupted ProcSleep() call can
- * finish.
- */
- PGSemaphoreUnlock(&MyProc->sem);
-
- /*
- * We're done here. Transaction abort caused by the error that
- * ProcSleep will raise will cause any other locks we hold to be
- * released, thus allowing other processes to wake up; we don't need
- * to do that here. NOTE: an exception is that releasing locks we
- * hold doesn't consider the possibility of waiters that were blocked
- * behind us on the lock we just failed to get, and might now be
- * wakable because we're not in front of them anymore. However,
- * RemoveFromWaitQueue took care of waking up any such processes.
- */
- }
- else if (log_lock_waits || deadlock_state == DS_BLOCKED_BY_AUTOVACUUM)
- {
- /*
- * Unlock my semaphore so that the interrupted ProcSleep() call can
- * print the log message (we daren't do it here because we are inside
- * a signal handler). It will then sleep again until someone releases
- * the lock.
- *
- * If blocked by autovacuum, this wakeup will enable ProcSleep to send
- * the canceling signal to the autovacuum worker.
- */
- PGSemaphoreUnlock(&MyProc->sem);
- }
-
- /*
- * And release locks. We do this in reverse order for two reasons: (1)
- * Anyone else who needs more than one of the locks will be trying to lock
- * them in increasing order; we don't want to release the other process
- * until it can get all the locks it needs. (2) This avoids O(N^2)
- * behavior inside LWLockRelease.
- */
- check_done:
- for (i = NUM_LOCK_PARTITIONS; --i >= 0;)
- LWLockRelease(FirstLockMgrLock + i);
- }
-
-
- /*
* ProcWaitForSignal - wait for a signal from another backend.
*
* This can share the semaphore normally used for waiting for locks,
--- 1349,1354 ----
*************** ProcSendSignal(int pid)
*** 1494,1894 ****
if (proc != NULL)
PGSemaphoreUnlock(&proc->sem);
}
-
-
- /*****************************************************************************
- * SIGALRM interrupt support
- *
- * Maybe these should be in pqsignal.c?
- *****************************************************************************/
-
- /*
- * Enable the SIGALRM interrupt to fire after the specified delay
- *
- * Delay is given in milliseconds. Caller should be sure a SIGALRM
- * signal handler is installed before this is called.
- *
- * This code properly handles nesting of deadlock timeout alarms within
- * statement timeout alarms.
- *
- * Returns TRUE if okay, FALSE on failure.
- */
- bool
- enable_sig_alarm(int delayms, bool is_statement_timeout)
- {
- TimestampTz fin_time;
- struct itimerval timeval;
-
- if (is_statement_timeout)
- {
- /*
- * Begin statement-level timeout
- *
- * Note that we compute statement_fin_time with reference to the
- * statement_timestamp, but apply the specified delay without any
- * correction; that is, we ignore whatever time has elapsed since
- * statement_timestamp was set. In the normal case only a small
- * interval will have elapsed and so this doesn't matter, but there
- * are corner cases (involving multi-statement query strings with
- * embedded COMMIT or ROLLBACK) where we might re-initialize the
- * statement timeout long after initial receipt of the message. In
- * such cases the enforcement of the statement timeout will be a bit
- * inconsistent. This annoyance is judged not worth the cost of
- * performing an additional gettimeofday() here.
- */
- Assert(!deadlock_timeout_active);
- fin_time = GetCurrentStatementStartTimestamp();
- fin_time = TimestampTzPlusMilliseconds(fin_time, delayms);
- statement_fin_time = fin_time;
- cancel_from_timeout = false;
- statement_timeout_active = true;
- }
- else if (statement_timeout_active)
- {
- /*
- * Begin deadlock timeout with statement-level timeout active
- *
- * Here, we want to interrupt at the closer of the two timeout times.
- * If fin_time >= statement_fin_time then we need not touch the
- * existing timer setting; else set up to interrupt at the deadlock
- * timeout time.
- *
- * NOTE: in this case it is possible that this routine will be
- * interrupted by the previously-set timer alarm. This is okay
- * because the signal handler will do only what it should do according
- * to the state variables. The deadlock checker may get run earlier
- * than normal, but that does no harm.
- */
- timeout_start_time = GetCurrentTimestamp();
- fin_time = TimestampTzPlusMilliseconds(timeout_start_time, delayms);
- deadlock_timeout_active = true;
- if (fin_time >= statement_fin_time)
- return true;
- }
- else
- {
- /* Begin deadlock timeout with no statement-level timeout */
- deadlock_timeout_active = true;
- /* GetCurrentTimestamp can be expensive, so only do it if we must */
- if (log_lock_waits)
- timeout_start_time = GetCurrentTimestamp();
- }
-
- /* If we reach here, okay to set the timer interrupt */
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = delayms / 1000;
- timeval.it_value.tv_usec = (delayms % 1000) * 1000;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- return true;
- }
-
- /*
- * Cancel the SIGALRM timer, either for a deadlock timeout or a statement
- * timeout. If a deadlock timeout is canceled, any active statement timeout
- * remains in force.
- *
- * Returns TRUE if okay, FALSE on failure.
- */
- bool
- disable_sig_alarm(bool is_statement_timeout)
- {
- /*
- * Always disable the interrupt if it is active; this avoids being
- * interrupted by the signal handler and thereby possibly getting
- * confused.
- *
- * We will re-enable the interrupt if necessary in CheckStatementTimeout.
- */
- if (statement_timeout_active || deadlock_timeout_active)
- {
- struct itimerval timeval;
-
- MemSet(&timeval, 0, sizeof(struct itimerval));
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- {
- statement_timeout_active = false;
- cancel_from_timeout = false;
- deadlock_timeout_active = false;
- return false;
- }
- }
-
- /* Always cancel deadlock timeout, in case this is error cleanup */
- deadlock_timeout_active = false;
-
- /* Cancel or reschedule statement timeout */
- if (is_statement_timeout)
- {
- statement_timeout_active = false;
- cancel_from_timeout = false;
- }
- else if (statement_timeout_active)
- {
- if (!CheckStatementTimeout())
- return false;
- }
- return true;
- }
-
-
- /*
- * Check for statement timeout. If the timeout time has come,
- * trigger a query-cancel interrupt; if not, reschedule the SIGALRM
- * interrupt to occur at the right time.
- *
- * Returns true if okay, false if failed to set the interrupt.
- */
- static bool
- CheckStatementTimeout(void)
- {
- TimestampTz now;
-
- if (!statement_timeout_active)
- return true; /* do nothing if not active */
-
- now = GetCurrentTimestamp();
-
- if (now >= statement_fin_time)
- {
- /* Time to die */
- statement_timeout_active = false;
- cancel_from_timeout = true;
- #ifdef HAVE_SETSID
- /* try to signal whole process group */
- kill(-MyProcPid, SIGINT);
- #endif
- kill(MyProcPid, SIGINT);
- }
- else
- {
- /* Not time yet, so (re)schedule the interrupt */
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
-
- /*
- * It's possible that the difference is less than a microsecond;
- * ensure we don't cancel, rather than set, the interrupt.
- */
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- }
-
- return true;
- }
-
-
- /*
- * Signal handler for SIGALRM for normal user backends
- *
- * Process deadlock check and/or statement timeout check, as needed.
- * To avoid various edge cases, we must be careful to do nothing
- * when there is nothing to be done. We also need to be able to
- * reschedule the timer interrupt if called before end of statement.
- */
- void
- handle_sig_alarm(SIGNAL_ARGS)
- {
- int save_errno = errno;
-
- /* SIGALRM is cause for waking anything waiting on the process latch */
- if (MyProc)
- SetLatch(&MyProc->procLatch);
-
- if (deadlock_timeout_active)
- {
- deadlock_timeout_active = false;
- CheckDeadLock();
- }
-
- if (statement_timeout_active)
- (void) CheckStatementTimeout();
-
- errno = save_errno;
- }
-
- /*
- * Signal handler for SIGALRM in Startup process
- *
- * To avoid various edge cases, we must be careful to do nothing
- * when there is nothing to be done. We also need to be able to
- * reschedule the timer interrupt if called before end of statement.
- *
- * We set either deadlock_timeout_active or statement_timeout_active
- * or both. Interrupts are enabled if standby_timeout_active.
- */
- bool
- enable_standby_sig_alarm(TimestampTz now, TimestampTz fin_time, bool deadlock_only)
- {
- TimestampTz deadlock_time = TimestampTzPlusMilliseconds(now,
- DeadlockTimeout);
-
- if (deadlock_only)
- {
- /*
- * Wake up at deadlock_time only, then wait forever
- */
- statement_fin_time = deadlock_time;
- deadlock_timeout_active = true;
- statement_timeout_active = false;
- }
- else if (fin_time > deadlock_time)
- {
- /*
- * Wake up at deadlock_time, then again at fin_time
- */
- statement_fin_time = deadlock_time;
- statement_fin_time2 = fin_time;
- deadlock_timeout_active = true;
- statement_timeout_active = true;
- }
- else
- {
- /*
- * Wake only at fin_time because its fairly soon
- */
- statement_fin_time = fin_time;
- deadlock_timeout_active = false;
- statement_timeout_active = true;
- }
-
- if (deadlock_timeout_active || statement_timeout_active)
- {
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- standby_timeout_active = true;
- }
-
- return true;
- }
-
- bool
- disable_standby_sig_alarm(void)
- {
- /*
- * Always disable the interrupt if it is active; this avoids being
- * interrupted by the signal handler and thereby possibly getting
- * confused.
- *
- * We will re-enable the interrupt if necessary in CheckStandbyTimeout.
- */
- if (standby_timeout_active)
- {
- struct itimerval timeval;
-
- MemSet(&timeval, 0, sizeof(struct itimerval));
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- {
- standby_timeout_active = false;
- return false;
- }
- }
-
- standby_timeout_active = false;
-
- return true;
- }
-
- /*
- * CheckStandbyTimeout() runs unconditionally in the Startup process
- * SIGALRM handler. Timers will only be set when InHotStandby.
- * We simply ignore any signals unless the timer has been set.
- */
- static bool
- CheckStandbyTimeout(void)
- {
- TimestampTz now;
- bool reschedule = false;
-
- standby_timeout_active = false;
-
- now = GetCurrentTimestamp();
-
- /*
- * Reschedule the timer if its not time to wake yet, or if we have both
- * timers set and the first one has just been reached.
- */
- if (now >= statement_fin_time)
- {
- if (deadlock_timeout_active)
- {
- /*
- * We're still waiting when we reach deadlock timeout, so send out
- * a request to have other backends check themselves for deadlock.
- * Then continue waiting until statement_fin_time, if that's set.
- */
- SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
- deadlock_timeout_active = false;
-
- /*
- * Begin second waiting period if required.
- */
- if (statement_timeout_active)
- {
- reschedule = true;
- statement_fin_time = statement_fin_time2;
- }
- }
- else
- {
- /*
- * We've now reached statement_fin_time, so ask all conflicts to
- * leave, so we can press ahead with applying changes in recovery.
- */
- SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
- }
- }
- else
- reschedule = true;
-
- if (reschedule)
- {
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- standby_timeout_active = true;
- }
-
- return true;
- }
-
- void
- handle_standby_sig_alarm(SIGNAL_ARGS)
- {
- int save_errno = errno;
-
- if (standby_timeout_active)
- (void) CheckStandbyTimeout();
-
- errno = save_errno;
- }
--- 1400,1402 ----
diff -dcrpN postgresql.orig/src/backend/storage/lmgr/timeout.c postgresql/src/backend/storage/lmgr/timeout.c
*** postgresql.orig/src/backend/storage/lmgr/timeout.c 1970-01-01 01:00:00.000000000 +0100
--- postgresql/src/backend/storage/lmgr/timeout.c 2012-04-04 15:12:26.057149045 +0200
***************
*** 0 ****
--- 1,723 ----
+ /*-------------------------------------------------------------------------
+ *
+ * timeout.c
+ * routines to manage timeout sources handled by SIGALRM
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/storage/lmgr/timeout.c
+ *
+ *-------------------------------------------------------------------------
+ */
+ /*
+ * Interface:
+ * enable_timeout(),
+ * disable_timeout(), disable_all_timeouts()
+ * get_timeout_indicator(), get_timeout_start()
+ * and
+ * handle_sig_alarm()
+ */
+ #include "postgres.h"
+
+ #include <sys/time.h>
+
+ #include "access/xact.h"
+ #include "miscadmin.h"
+ #include "storage/proc.h"
+ #include "storage/standby.h"
+ #include "storage/timeout.h"
+ #include "utils/timestamp.h"
+
+ /* GUC variables */
+ int DeadlockTimeout = 1000;
+ int StatementTimeout = 0;
+ int LockTimeout = 0;
+
+ /*
+ * Infrastructure for timeouts
+ */
+
+ static void InitDeadLock(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyDeadLock(bool keep_indicator);
+ static bool CheckDeadLock(void);
+
+ static void InitLockTimeout(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyLockTimeout(bool keep_indicator);
+ static bool CheckLockTimeout(void);
+
+ static void InitStatementTimeout(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyStatementTimeout(bool keep_indicator);
+ static bool CheckStatementTimeout(void);
+
+ static void InitStandbyDeadLock(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyStandbyDeadLock(bool keep_indicator);
+ static bool CheckStandbyDeadLock(void);
+
+ static void InitStandbyTimeout(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyStandbyTimeout(bool keep_indicator);
+ static bool CheckStandbyTimeout(void);
+
+ typedef void (*timeout_init)(TimestampTz, TimestampTz);
+ typedef void (*timeout_destroy)(bool);
+ typedef bool (*timeout_check)(void);
+ typedef TimestampTz (*timeout_start)(void);
+
+ typedef struct {
+ TimeoutName index;
+ bool resched_next;
+
+ /* volatile because it may be changed from the signal handler */
+ volatile bool indicator;
+
+ timeout_init timeout_init;
+ timeout_destroy timeout_destroy;
+ timeout_check timeout_check;
+ timeout_start timeout_start;
+
+ TimestampTz start_time;
+ TimestampTz fin_time;
+ } timeout_params;
+
+ /*
+ * List of possible timeout reasons in the order of enum TimeoutName.
+ * The priority of timeouts (in case two of them would trigger at the
+ * same time) is determined by this order: the earlier one in the list
+ * has higher priority.
+ */
+ static timeout_params base_timeouts[TIMEOUT_MAX] = {
+ {
+ DEADLOCK_TIMEOUT, true, false,
+ InitDeadLock, DestroyDeadLock,
+ CheckDeadLock, GetCurrentTimestamp,
+ 0
+ },
+
+ {
+ LOCK_TIMEOUT, false, false,
+ InitLockTimeout, DestroyLockTimeout,
+ CheckLockTimeout, GetCurrentTimestamp,
+ 0
+ },
+
+ {
+ STATEMENT_TIMEOUT, false, false,
+ InitStatementTimeout, DestroyStatementTimeout,
+ CheckStatementTimeout, GetCurrentStatementStartTimestamp,
+ 0
+ },
+
+ {
+ STANDBY_DEADLOCK_TIMEOUT, true, false,
+ InitStandbyDeadLock, DestroyStandbyDeadLock,
+ CheckStandbyDeadLock, GetCurrentTimestamp,
+ 0
+ },
+
+ {
+ STANDBY_TIMEOUT, false, false,
+ InitStandbyTimeout, DestroyStandbyTimeout,
+ CheckStandbyTimeout, GetCurrentTimestamp,
+ 0
+ }
+ };
+
+ /*
+ * List of active timeouts ordered by their fin_time and priority.
+ */
+ static int n_timeouts = 0;
+ static timeout_params *timeouts[TIMEOUT_MAX];
+
+ /*****************************************************************************
+ * Internal helper functions
+ *****************************************************************************/
+
+ /*
+ * Find the index of a given timeout type in the active array
+ */
+ static int
+ find_active_timeout(TimeoutName tn)
+ {
+ int i;
+
+ for (i = 0; i < n_timeouts; i++)
+ {
+ if (timeouts[i]->index == tn)
+ return i;
+ }
+
+ return -1;
+ }
+
+ #define is_timeout_active(tn) (find_active_timeout(tn) >= 0)
+
+ /*****************************************************************************
+ * Init, Destroy and Check functions for different timeouts.
+ *****************************************************************************/
+
+ static void
+ InitTimeout(TimeoutName tn, TimestampTz start_time, TimestampTz fin_time)
+ {
+ base_timeouts[tn].indicator = false;
+ base_timeouts[tn].start_time = start_time;
+ base_timeouts[tn].fin_time = fin_time;
+ }
+
+ static void
+ DestroyTimeout(TimeoutName tn, bool keep_indicator)
+ {
+ if (!keep_indicator)
+ base_timeouts[tn].indicator = false;
+ base_timeouts[tn].start_time = 0;
+ base_timeouts[tn].fin_time = 0;
+ }
+
+ /*
+ * DeadLock functions
+ */
+
+ static void
+ InitDeadLock(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(DEADLOCK_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyDeadLock(bool keep_indicator)
+ {
+ DestroyTimeout(DEADLOCK_TIMEOUT, keep_indicator);
+ }
+
+ /*
+ * CheckDeadLock
+ *
+ * We only get to this routine if we got SIGALRM after DeadlockTimeout
+ * while waiting for a lock to be released by some other process. Look
+ * to see if there's a deadlock; if not, just return and continue waiting.
+ * (But signal ProcSleep to log a message, if log_lock_waits is true.)
+ * If we have a real deadlock, remove ourselves from the lock's wait queue
+ * and signal an error to ProcSleep.
+ *
+ * NB: this is run inside a signal handler, so be very wary about what is done
+ * here or in called routines.
+ */
+ static bool
+ CheckDeadLock(void)
+ {
+ TimestampTz now;
+ int i;
+
+ now = GetCurrentTimestamp();
+
+ /* If our time has not come yet, do nothing. */
+ if (now < base_timeouts[DEADLOCK_TIMEOUT].fin_time)
+ return false;
+
+ /*
+ * Acquire exclusive lock on the entire shared lock data structures. Must
+ * grab LWLocks in partition-number order to avoid LWLock deadlock.
+ *
+ * Note that the deadlock check interrupt had better not be enabled
+ * anywhere that this process itself holds lock partition locks, else this
+ * will wait forever. Also note that LWLockAcquire creates a critical
+ * section, so that this routine cannot be interrupted by cancel/die
+ * interrupts.
+ */
+ for (i = 0; i < NUM_LOCK_PARTITIONS; i++)
+ LWLockAcquire(FirstLockMgrLock + i, LW_EXCLUSIVE);
+
+ /*
+ * Check to see if we've been awoken by anyone in the interim.
+ *
+ * If we have, we can return and resume our transaction -- happy day.
+ * Before we are awoken the process releasing the lock grants it to us so
+ * we know that we don't have to wait anymore.
+ *
+ * We check by looking to see if we've been unlinked from the wait queue.
+ * This is quicker than checking our semaphore's state, since no kernel
+ * call is needed, and it is safe because we hold the lock partition lock.
+ */
+ if (MyProc->links.prev == NULL ||
+ MyProc->links.next == NULL)
+ goto check_done;
+
+ #ifdef LOCK_DEBUG
+ if (Debug_deadlocks)
+ DumpAllLocks();
+ #endif
+
+ /* Run the deadlock check, and set deadlock_state for use by ProcSleep */
+ deadlock_state = DeadLockCheck(MyProc);
+
+ if (deadlock_state == DS_HARD_DEADLOCK)
+ {
+ /*
+ * Oops. We have a deadlock.
+ *
+ * Get this process out of wait state. (Note: we could do this more
+ * efficiently by relying on lockAwaited, but use this coding to
+ * preserve the flexibility to kill some other transaction than the
+ * one detecting the deadlock.)
+ *
+ * RemoveFromWaitQueue sets MyProc->waitStatus to STATUS_ERROR, so
+ * ProcSleep will report an error after we return from the signal
+ * handler.
+ */
+ Assert(MyProc->waitLock != NULL);
+ RemoveFromWaitQueue(MyProc, LockTagHashCode(&(MyProc->waitLock->tag)));
+
+ /*
+ * Unlock my semaphore so that the interrupted ProcSleep() call can
+ * finish.
+ */
+ PGSemaphoreUnlock(&MyProc->sem);
+
+ /*
+ * We're done here. Transaction abort caused by the error that
+ * ProcSleep will raise will cause any other locks we hold to be
+ * released, thus allowing other processes to wake up; we don't need
+ * to do that here. NOTE: an exception is that releasing locks we
+ * hold doesn't consider the possibility of waiters that were blocked
+ * behind us on the lock we just failed to get, and might now be
+ * wakable because we're not in front of them anymore. However,
+ * RemoveFromWaitQueue took care of waking up any such processes.
+ */
+ }
+ else if (log_lock_waits || deadlock_state == DS_BLOCKED_BY_AUTOVACUUM)
+ {
+ /*
+ * Unlock my semaphore so that the interrupted ProcSleep() call can
+ * print the log message (we daren't do it here because we are inside
+ * a signal handler). It will then sleep again until someone releases
+ * the lock.
+ *
+ * If blocked by autovacuum, this wakeup will enable ProcSleep to send
+ * the canceling signal to the autovacuum worker.
+ */
+ PGSemaphoreUnlock(&MyProc->sem);
+ }
+
+ /*
+ * And release locks. We do this in reverse order for two reasons: (1)
+ * Anyone else who needs more than one of the locks will be trying to lock
+ * them in increasing order; we don't want to release the other process
+ * until it can get all the locks it needs. (2) This avoids O(N^2)
+ * behavior inside LWLockRelease.
+ */
+ check_done:
+ for (i = NUM_LOCK_PARTITIONS; --i >= 0;)
+ LWLockRelease(FirstLockMgrLock + i);
+
+ base_timeouts[DEADLOCK_TIMEOUT].indicator = true;
+
+ return true;
+ }
+
+ /*
+ * LockTimeout functions
+ */
+
+ static void
+ InitLockTimeout(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(LOCK_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyLockTimeout(bool keep_indicator)
+ {
+ DestroyTimeout(LOCK_TIMEOUT, keep_indicator);
+ }
+
+ /*
+ * Check for lock timeout. If the timeout time has come,
+ * indicate that our timeout was detected.
+ */
+ static bool
+ CheckLockTimeout(void)
+ {
+ TimestampTz now;
+
+ now = GetCurrentTimestamp();
+
+ if (now >= base_timeouts[LOCK_TIMEOUT].fin_time)
+ {
+ base_timeouts[LOCK_TIMEOUT].indicator = true;
+ return true;
+ }
+
+ return false;
+ }
+
+ /*
+ * StatementTimeout functions
+ */
+
+ static void
+ InitStatementTimeout(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(STATEMENT_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyStatementTimeout(bool keep_indicator)
+ {
+ DestroyTimeout(STATEMENT_TIMEOUT, keep_indicator);
+ }
+
+ /*
+ * Check for statement timeout. If the timeout time has come,
+ * trigger a query-cancel interrupt.
+ *
+ * Returns true if the time has come.
+ */
+ static bool
+ CheckStatementTimeout(void)
+ {
+ TimestampTz now;
+
+ now = GetCurrentTimestamp();
+
+ if (now >= base_timeouts[STATEMENT_TIMEOUT].fin_time)
+ {
+ base_timeouts[STATEMENT_TIMEOUT].indicator = true;
+ #ifdef HAVE_SETSID
+ /* try to signal whole process group */
+ kill(-MyProcPid, SIGINT);
+ #endif
+ kill(MyProcPid, SIGINT);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /*
+ * StandbyDeadLock functions
+ */
+
+ static void
+ InitStandbyDeadLock(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(STANDBY_DEADLOCK_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyStandbyDeadLock(bool keep_indicator)
+ {
+ DestroyTimeout(STANDBY_DEADLOCK_TIMEOUT, keep_indicator);
+ }
+
+ static bool
+ CheckStandbyDeadLock(void)
+ {
+ TimestampTz now;
+
+ now = GetCurrentTimestamp();
+
+ if (now >= base_timeouts[STANDBY_DEADLOCK_TIMEOUT].fin_time)
+ {
+ base_timeouts[STANDBY_DEADLOCK_TIMEOUT].indicator = true;
+ SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
+ return true;
+ }
+
+ return false;
+ }
+
+ /*
+ * StandbyTimeout functions
+ */
+
+ static void
+ InitStandbyTimeout(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(STANDBY_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyStandbyTimeout(bool keep_indicator)
+ {
+ DestroyTimeout(STANDBY_TIMEOUT, keep_indicator);
+ }
+
+ static bool
+ CheckStandbyTimeout(void)
+ {
+ TimestampTz now;
+
+ now = GetCurrentTimestamp();
+
+ if (now >= base_timeouts[STANDBY_TIMEOUT].fin_time)
+ {
+ base_timeouts[STANDBY_TIMEOUT].indicator = true;
+ SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
+ return true;
+ }
+
+ return false;
+ }
+
+ /*
+ * Insert tn'th timeout into the list of timeouts at the given index
+ * if the previous timeout allows rescheduling the next one in the list.
+ */
+ static void
+ insert_timeout(TimeoutName tn, int index)
+ {
+ int i;
+
+ if (index > 0 && !timeouts[index-1]->resched_next)
+ return;
+
+ for (i = n_timeouts - 1; i >= index; i--)
+ timeouts[i+1] = timeouts[i];
+
+ timeouts[index] = &base_timeouts[tn];
+ n_timeouts++;
+ }
+
+ /*
+ * Remove the index'th element from the timeout list.
+ */
+ static void
+ remove_timeout_index(int index)
+ {
+ int i;
+
+ if (index < 0)
+ return;
+
+ for (i = index + 1; i < n_timeouts; i++)
+ timeouts[i-1] = timeouts[i];
+
+ if (n_timeouts > 0 && index < n_timeouts)
+ n_timeouts--;
+ }
+
+ /*
+ * (Re)schedule the next active timeout
+ */
+ static bool
+ schedule_timeout(TimestampTz now)
+ {
+ long secs;
+ int usecs;
+ struct itimerval timeval;
+
+ /* There is no active timeout, do nothing. */
+ if (n_timeouts == 0)
+ return true;
+
+ TimestampDifference(now, timeouts[0]->fin_time,
+ &secs, &usecs);
+
+ /*
+ * It's possible that the difference is less than a microsecond;
+ * ensure we don't cancel, rather than set, the interrupt.
+ */
+ if (secs == 0 && usecs == 0)
+ usecs = 1;
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ timeval.it_value.tv_sec = secs;
+ timeval.it_value.tv_usec = usecs;
+ if (setitimer(ITIMER_REAL, &timeval, NULL))
+ return false;
+ return true;
+ }
+
+ /*****************************************************************************
+ * Public API
+ *****************************************************************************/
+
+
+ /*
+ * Enable the SIGALRM interrupt to fire after the specified delay
+ *
+ * Delay is given in milliseconds. Caller should be sure a SIGALRM
+ * signal handler is installed before this is called.
+ *
+ * This code properly handles nesting of different timeout alarms.
+ *
+ * Returns TRUE if okay, FALSE on failure to set the timer.
+ */
+ bool
+ enable_timeout(TimeoutName tn, int delayms)
+ {
+ TimestampTz start_time;
+ TimestampTz fin_time;
+ int i;
+
+ Assert(!is_timeout_active(tn));
+
+ if (delayms <= 0)
+ return true;
+
+ start_time = base_timeouts[tn].timeout_start();
+ fin_time = TimestampTzPlusMilliseconds(start_time, delayms);
+
+ /* Find out the index where to insert the new timeout. */
+ for (i = 0; i < n_timeouts; i++)
+ {
+ /*
+ * The new timeout triggers earlier than
+ * a previously added one: insert here.
+ */
+ if (fin_time < timeouts[i]->fin_time)
+ break;
+ /*
+ * The new timeout triggers at the same time
+ * as the previously added one but has greater priority.
+ */
+ if (fin_time == timeouts[i]->fin_time && tn < timeouts[i]->index)
+ break;
+ }
+
+ /*
+ * Initialize the timeout parameters
+ */
+ base_timeouts[tn].timeout_init(start_time, fin_time);
+
+ insert_timeout(tn, i);
+
+ if (i > 0)
+ return true;
+
+ /* If we reach here, okay to set the timer interrupt */
+ if (!schedule_timeout(start_time))
+ return false;
+ return true;
+ }
+
+ /*
+ * Cancel the SIGALRM timer for the specific timeout.
+ * If a timeout is canceled, any other active timeout remains in force.
+ *
+ * Returns TRUE if okay, FALSE on failure to set the timer.
+ */
+ bool
+ disable_timeout(TimeoutName tn, bool keep_indicator)
+ {
+ int i;
+
+ /*
+ * Always disable the interrupt if it is active; this avoids being
+ * interrupted by the signal handler and thereby possibly getting
+ * confused.
+ *
+ * We will re-enable the interrupt if necessary in ->check_timeout().
+ */
+ if (n_timeouts > 0)
+ {
+ struct itimerval timeval;
+
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ if (setitimer(ITIMER_REAL, &timeval, NULL))
+ {
+ n_timeouts = 0;
+ for (i = 0; i < TIMEOUT_MAX; i++)
+ base_timeouts[i].timeout_destroy(false);
+
+ return false;
+ }
+ }
+
+ /* Find the timeout and remove from the list. */
+ i = find_active_timeout(tn);
+ remove_timeout_index(i);
+
+ /* Do cleanup. */
+ base_timeouts[tn].timeout_destroy(keep_indicator);
+
+ /*
+ * If the first timeout was removed from the list and there is
+ * at least one active left, reschedule it.
+ */
+ if (i == 0 && n_timeouts > 0)
+ if (!schedule_timeout(GetCurrentTimestamp()))
+ return false;
+
+ return true;
+ }
+
+ /*
+ * Disable SIGALRM and remove all timeouts from the list and
+ * reset the timeout indicators.
+ */
+ bool
+ disable_all_timeouts(bool keep_indicators)
+ {
+ struct itimerval timeval;
+ int i;
+ bool ret;
+
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ ret = (setitimer(ITIMER_REAL, &timeval, NULL) == 0);
+
+ n_timeouts = 0;
+ for (i = 0; i < TIMEOUT_MAX; i++)
+ base_timeouts[i].timeout_destroy(keep_indicators);
+
+ return ret;
+ }
+
+ /*
+ * Return the timeout indicator
+ */
+ bool
+ get_timeout_indicator(TimeoutName tn)
+ {
+ return base_timeouts[tn].indicator;
+ }
+
+ /*
+ * Return the start of the timer for this timeout
+ */
+ TimestampTz
+ get_timeout_start(TimeoutName tn)
+ {
+ return base_timeouts[tn].start_time;
+ }
+
+ /*
+ * Signal handler for SIGALRM
+ *
+ * Process deadlock check and/or statement timeout check, as needed.
+ * To avoid various edge cases, we must be careful to do nothing
+ * when there is nothing to be done. We also need to be able to
+ * reschedule the timer interrupt if called before end of statement.
+ */
+ void
+ handle_sig_alarm(SIGNAL_ARGS)
+ {
+ int save_errno = errno;
+
+ /* SIGALRM is cause for waking anything waiting on the process latch */
+ if (MyProc)
+ SetLatch(&MyProc->procLatch);
+
+ if (n_timeouts > 0)
+ {
+ bool ret;
+ bool reschedule = true;
+
+ ret = timeouts[0]->timeout_check();
+
+ /* Shift timeouts if the timeout was triggered */
+ if (ret)
+ {
+ reschedule = timeouts[0]->resched_next;
+ remove_timeout_index(0);
+ }
+
+ if (reschedule)
+ schedule_timeout(GetCurrentTimestamp());
+ else
+ disable_all_timeouts(true);
+ }
+
+ errno = save_errno;
+ }
diff -dcrpN postgresql.orig/src/backend/tcop/postgres.c postgresql/src/backend/tcop/postgres.c
*** postgresql.orig/src/backend/tcop/postgres.c 2012-03-28 10:54:25.195368447 +0200
--- postgresql/src/backend/tcop/postgres.c 2012-04-04 15:07:43.152700939 +0200
***************
*** 64,69 ****
--- 64,70 ----
#include "storage/proc.h"
#include "storage/procsignal.h"
#include "storage/sinval.h"
+ #include "storage/timeout.h"
#include "tcop/fastpath.h"
#include "tcop/pquery.h"
#include "tcop/tcopprot.h"
*************** start_xact_command(void)
*** 2368,2376 ****
/* Set statement timeout running, if any */
/* NB: this mustn't be enabled until we are within an xact */
if (StatementTimeout > 0)
! enable_sig_alarm(StatementTimeout, true);
else
! cancel_from_timeout = false;
xact_started = true;
}
--- 2369,2377 ----
/* Set statement timeout running, if any */
/* NB: this mustn't be enabled until we are within an xact */
if (StatementTimeout > 0)
! enable_timeout(STATEMENT_TIMEOUT, StatementTimeout);
else
! disable_timeout(STATEMENT_TIMEOUT, false);
xact_started = true;
}
*************** finish_xact_command(void)
*** 2382,2388 ****
if (xact_started)
{
/* Cancel any active statement timeout before committing */
! disable_sig_alarm(true);
/* Now commit the command */
ereport(DEBUG3,
--- 2383,2389 ----
if (xact_started)
{
/* Cancel any active statement timeout before committing */
! disable_all_timeouts(false);
/* Now commit the command */
ereport(DEBUG3,
*************** ProcessInterrupts(void)
*** 2863,2869 ****
(errcode(ERRCODE_QUERY_CANCELED),
errmsg("canceling authentication due to timeout")));
}
! if (cancel_from_timeout)
{
ImmediateInterruptOK = false; /* not idle anymore */
DisableNotifyInterrupt();
--- 2864,2870 ----
(errcode(ERRCODE_QUERY_CANCELED),
errmsg("canceling authentication due to timeout")));
}
! if (get_timeout_indicator(STATEMENT_TIMEOUT))
{
ImmediateInterruptOK = false; /* not idle anymore */
DisableNotifyInterrupt();
*************** PostgresMain(int argc, char *argv[], con
*** 3728,3737 ****
/*
* Forget any pending QueryCancel request, since we're returning to
! * the idle loop anyway, and cancel the statement timer if running.
*/
QueryCancelPending = false;
! disable_sig_alarm(true);
QueryCancelPending = false; /* again in case timeout occurred */
/*
--- 3729,3738 ----
/*
* Forget any pending QueryCancel request, since we're returning to
! * the idle loop anyway, and cancel the timer if running.
*/
QueryCancelPending = false;
! disable_all_timeouts(false);
QueryCancelPending = false; /* again in case timeout occurred */
/*
diff -dcrpN postgresql.orig/src/backend/utils/adt/lockfuncs.c postgresql/src/backend/utils/adt/lockfuncs.c
*** postgresql.orig/src/backend/utils/adt/lockfuncs.c 2012-01-02 12:35:11.533185089 +0100
--- postgresql/src/backend/utils/adt/lockfuncs.c 2012-04-04 11:24:59.796965393 +0200
*************** pg_advisory_lock_int8(PG_FUNCTION_ARGS)
*** 421,427 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ExclusiveLock, true, false);
PG_RETURN_VOID();
}
--- 421,431 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ExclusiveLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_int8(PG_FUNCTION_A
*** 438,444 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
PG_RETURN_VOID();
}
--- 442,452 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_shared_int8(PG_FUNCTION
*** 454,460 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ShareLock, true, false);
PG_RETURN_VOID();
}
--- 462,472 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ShareLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_shared_int8(PG_FUN
*** 471,477 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ShareLock, false, false);
PG_RETURN_VOID();
}
--- 483,493 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_int4(PG_FUNCTION_ARGS)
*** 604,610 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ExclusiveLock, true, false);
PG_RETURN_VOID();
}
--- 620,630 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ExclusiveLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_int4(PG_FUNCTION_A
*** 622,628 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
PG_RETURN_VOID();
}
--- 642,652 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_shared_int4(PG_FUNCTION
*** 639,645 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ShareLock, true, false);
PG_RETURN_VOID();
}
--- 663,673 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ShareLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_shared_int4(PG_FUN
*** 657,663 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ShareLock, false, false);
PG_RETURN_VOID();
}
--- 685,695 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
diff -dcrpN postgresql.orig/src/backend/utils/init/postinit.c postgresql/src/backend/utils/init/postinit.c
*** postgresql.orig/src/backend/utils/init/postinit.c 2012-01-20 13:59:24.913240794 +0100
--- postgresql/src/backend/utils/init/postinit.c 2012-04-04 15:08:31.671944981 +0200
***************
*** 41,51 ****
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lmgr.h"
- #include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "storage/smgr.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
#include "utils/fmgroids.h"
--- 41,51 ----
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lmgr.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "storage/smgr.h"
+ #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
#include "utils/fmgroids.h"
*************** PerformAuthentication(Port *port)
*** 204,210 ****
* during authentication. Since we're inside a transaction and might do
* database access, we have to use the statement_timeout infrastructure.
*/
! if (!enable_sig_alarm(AuthenticationTimeout * 1000, true))
elog(FATAL, "could not set timer for authorization timeout");
/*
--- 204,210 ----
* during authentication. Since we're inside a transaction and might do
* database access, we have to use the statement_timeout infrastructure.
*/
! if (!enable_timeout(STATEMENT_TIMEOUT, AuthenticationTimeout * 1000))
elog(FATAL, "could not set timer for authorization timeout");
/*
*************** PerformAuthentication(Port *port)
*** 215,221 ****
/*
* Done with authentication. Disable the timeout, and log if needed.
*/
! if (!disable_sig_alarm(true))
elog(FATAL, "could not disable timer for authorization timeout");
if (Log_connections)
--- 215,221 ----
/*
* Done with authentication. Disable the timeout, and log if needed.
*/
! if (!disable_timeout(STATEMENT_TIMEOUT, false))
elog(FATAL, "could not disable timer for authorization timeout");
if (Log_connections)
diff -dcrpN postgresql.orig/src/backend/utils/misc/guc.c postgresql/src/backend/utils/misc/guc.c
*** postgresql.orig/src/backend/utils/misc/guc.c 2012-03-28 10:54:25.196368452 +0200
--- postgresql/src/backend/utils/misc/guc.c 2012-04-04 15:09:07.395124656 +0200
***************
*** 63,68 ****
--- 63,69 ----
#include "storage/standby.h"
#include "storage/fd.h"
#include "storage/predicate.h"
+ #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "tsearch/ts_cache.h"
#include "utils/builtins.h"
*************** static struct config_int ConfigureNamesI
*** 1858,1863 ****
--- 1859,1875 ----
0, 0, INT_MAX,
NULL, NULL, NULL
},
+
+ {
+ {"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,
diff -dcrpN postgresql.orig/src/backend/utils/misc/postgresql.conf.sample postgresql/src/backend/utils/misc/postgresql.conf.sample
*** postgresql.orig/src/backend/utils/misc/postgresql.conf.sample 2012-03-28 10:54:25.197368458 +0200
--- postgresql/src/backend/utils/misc/postgresql.conf.sample 2012-04-03 15:29:03.725060295 +0200
***************
*** 527,532 ****
--- 527,535 ----
#------------------------------------------------------------------------------
#deadlock_timeout = 1s
+ #lock_timeout = 0 # timeout value for heavy-weight locks
+ # taken by statements. 0 disables timeout
+ # unit in milliseconds, default is 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 -dcrpN postgresql.orig/src/include/storage/pg_sema.h postgresql/src/include/storage/pg_sema.h
*** postgresql.orig/src/include/storage/pg_sema.h 2012-01-02 12:35:11.640178818 +0100
--- postgresql/src/include/storage/pg_sema.h 2012-04-03 15:30:52.716621135 +0200
*************** extern void PGSemaphoreUnlock(PGSemaphor
*** 80,83 ****
--- 80,86 ----
/* 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 */
+ extern void PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK);
+
#endif /* PG_SEMA_H */
diff -dcrpN postgresql.orig/src/include/storage/proc.h postgresql/src/include/storage/proc.h
*** postgresql.orig/src/include/storage/proc.h 2012-01-31 09:43:43.936392561 +0100
--- postgresql/src/include/storage/proc.h 2012-04-04 14:58:19.578865852 +0200
*************** extern PGPROC *PreparedXactProcs;
*** 215,226 ****
/* configurable options */
- extern int DeadlockTimeout;
- extern int StatementTimeout;
extern bool log_lock_waits;
! extern volatile bool cancel_from_timeout;
!
/*
* Function Prototypes
--- 215,224 ----
/* configurable options */
extern bool log_lock_waits;
! /* */
! extern volatile DeadLockState deadlock_state;
/*
* Function Prototypes
*************** extern void LockWaitCancel(void);
*** 249,261 ****
extern void ProcWaitForSignal(void);
extern void ProcSendSignal(int pid);
- extern bool enable_sig_alarm(int delayms, bool is_statement_timeout);
- extern bool disable_sig_alarm(bool is_statement_timeout);
- extern void handle_sig_alarm(SIGNAL_ARGS);
-
- extern bool enable_standby_sig_alarm(TimestampTz now,
- TimestampTz fin_time, bool deadlock_only);
- extern bool disable_standby_sig_alarm(void);
- extern void handle_standby_sig_alarm(SIGNAL_ARGS);
-
#endif /* PROC_H */
--- 247,250 ----
diff -dcrpN postgresql.orig/src/include/storage/timeout.h postgresql/src/include/storage/timeout.h
*** postgresql.orig/src/include/storage/timeout.h 1970-01-01 01:00:00.000000000 +0100
--- postgresql/src/include/storage/timeout.h 2012-04-04 15:02:21.255081715 +0200
***************
*** 0 ****
--- 1,40 ----
+ /*-------------------------------------------------------------------------
+ *
+ * timeout.h
+ * SIGALRM timeout API
+ *
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/storage/timeout.h
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef _TIMEOUT_H_
+ #define _TIMEOUT_H_
+
+ #include "datatype/timestamp.h"
+
+ /* configurable options */
+ extern int DeadlockTimeout;
+ extern int StatementTimeout;
+ extern int LockTimeout;
+
+ typedef enum TimeoutName {
+ DEADLOCK_TIMEOUT,
+ LOCK_TIMEOUT,
+ STATEMENT_TIMEOUT,
+ STANDBY_DEADLOCK_TIMEOUT,
+ STANDBY_TIMEOUT,
+ TIMEOUT_MAX
+ } TimeoutName;
+
+ extern bool enable_timeout(TimeoutName tn, int delayms);
+ extern bool disable_timeout(TimeoutName tn, bool keep_indicator);
+ extern bool disable_all_timeouts(bool keep_indicators);
+ extern bool get_timeout_indicator(TimeoutName tn);
+ extern TimestampTz get_timeout_start(TimeoutName tn);
+ extern void handle_sig_alarm(SIGNAL_ARGS);
+
+ #endif /* _TIMEOUT_H_ */
diff -dcrpN postgresql.orig/src/test/regress/expected/prepared_xacts.out postgresql/src/test/regress/expected/prepared_xacts.out
*** postgresql.orig/src/test/regress/expected/prepared_xacts.out 2011-08-16 09:39:01.117194748 +0200
--- postgresql/src/test/regress/expected/prepared_xacts.out 2012-04-04 12:22:18.719786919 +0200
*************** set statement_timeout to 2000;
*** 198,203 ****
--- 198,210 ----
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: could not obtain lock on relation "pxtest3"
+ LINE 1: SELECT * FROM pxtest3;
+ ^
+ reset lock_timeout;
-- Disconnect, we will continue testing in a different backend
\c -
-- There should still be two prepared transactions
*************** set statement_timeout to 2000;
*** 213,218 ****
--- 220,232 ----
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: could not obtain lock on relation "pxtest3"
+ LINE 1: SELECT * FROM pxtest3;
+ ^
+ reset lock_timeout;
-- Commit table creation
COMMIT PREPARED 'regress-one';
\d pxtest2
diff -dcrpN postgresql.orig/src/test/regress/sql/prepared_xacts.sql postgresql/src/test/regress/sql/prepared_xacts.sql
*** postgresql.orig/src/test/regress/sql/prepared_xacts.sql 2011-07-18 15:42:00.117371861 +0200
--- postgresql/src/test/regress/sql/prepared_xacts.sql 2012-04-04 12:20:31.456241386 +0200
*************** set statement_timeout to 2000;
*** 126,131 ****
--- 126,136 ----
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 -
*************** set statement_timeout to 2000;
*** 137,142 ****
--- 142,152 ----
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
2012-04-04 15:17 keltezéssel, Boszormenyi Zoltan írta:
Hi,
2012-04-04 12:30 keltezéssel, Boszormenyi Zoltan írta:
Hi,
attached is a patch to implement a framework to simplify and
correctly nest multiplexing more than two timeout sources
into the same SIGALRM signal handler.The framework is using a new internal API for timeouts:
bool enable_timeout(TimeoutName tn, int delayms);
bool disable_timeout(TimeoutName tn, bool keep_indicator);
bool disable_all_timeouts(bool keep_indicators);A timeout source has these features to allow different initialization,
cleanup and check functions and rescheduling:typedef void (*timeout_init)(TimestampTz, TimestampTz);
typedef void (*timeout_destroy)(bool);
typedef bool (*timeout_check)(void);
typedef TimestampTz (*timeout_start)(void);typedef struct {
TimeoutName index;
bool resched_next;
timeout_init timeout_init;
timeout_destroy timeout_destroy;
timeout_check timeout_check;
timeout_start timeout_start;
TimestampTz fin_time;
} timeout_params;This makes it possible to differentiate between the standby and
statement timeouts, regular deadlock and standby deadlock using
the same signal handler function.And finally, this makes it possible to implement the lock_timeout
feature that we at Cybertec implemented more than 2 years ago.The patch also adds two new tests into prepared_xacts.sql to trigger
the lock_timeout instead of statement_timeout.Documentation and extensive comments are included.
Second version. Every timeout-related functions are now in a separate
source file, src/backend/storage/timeout.c with accessor functions.
There are no related global variables anymore, only the GUCs.
3rd and (for now) final version. Tidied comments, the time checks in
Check*() functions and function order in timeout.c.
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/
Attachments:
pg92-common-lock-framework-lock_timeout-v3.patchtext/x-patch; name=pg92-common-lock-framework-lock_timeout-v3.patchDownload
diff -dcrpN postgresql.orig/doc/src/sgml/config.sgml postgresql/doc/src/sgml/config.sgml
*** postgresql.orig/doc/src/sgml/config.sgml 2012-03-28 10:54:25.177368339 +0200
--- postgresql/doc/src/sgml/config.sgml 2012-04-04 10:36:01.982604984 +0200
*************** COPY postgres_log FROM '/full/path/to/lo
*** 4944,4950 ****
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.
</para>
<para>
--- 4944,4953 ----
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. 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>
*************** COPY postgres_log FROM '/full/path/to/lo
*** 4955,4960 ****
--- 4958,4991 ----
</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 has to wait more
+ than the specified number of milliseconds, starting from the time the
+ command arrives at the server from the client. If the statement involves
+ more than one such lock, the timeout applies to every one of them.
+ 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 to acquire.
+ As opposed to <varname>statement_timeout</>, this timeout (and the error)
+ may only occurs 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 off the limitation.
+ </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 -dcrpN postgresql.orig/doc/src/sgml/ref/lock.sgml postgresql/doc/src/sgml/ref/lock.sgml
*** postgresql.orig/doc/src/sgml/ref/lock.sgml 2011-08-07 11:29:16.004256905 +0200
--- postgresql/doc/src/sgml/ref/lock.sgml 2012-04-03 15:54:26.543224868 +0200
*************** LOCK [ TABLE ] [ ONLY ] <replaceable cla
*** 39,46 ****
<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.)
</para>
--- 39,49 ----
<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. 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 -dcrpN postgresql.orig/doc/src/sgml/ref/select.sgml postgresql/doc/src/sgml/ref/select.sgml
*** postgresql.orig/doc/src/sgml/ref/select.sgml 2012-01-29 20:33:53.893650856 +0100
--- postgresql/doc/src/sgml/ref/select.sgml 2012-04-04 10:30:35.035960738 +0200
*************** FOR SHARE [ OF <replaceable class="param
*** 1199,1204 ****
--- 1199,1211 ----
</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 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 -dcrpN postgresql.orig/src/backend/port/posix_sema.c postgresql/src/backend/port/posix_sema.c
*** postgresql.orig/src/backend/port/posix_sema.c 2012-01-02 12:35:11.502186906 +0100
--- postgresql/src/backend/port/posix_sema.c 2012-04-04 15:01:25.781802645 +0200
***************
*** 24,29 ****
--- 24,30 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/timeout.h"
#ifdef USE_NAMED_POSIX_SEMAPHORES
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 313,315 ****
--- 314,341 ----
return true;
}
+
+ /*
+ * PGSemaphoreTimedLock
+ *
+ * Lock a semaphore (decrement count), blocking if count would be < 0
+ * Return if lock_timeout expired
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ int errStatus;
+
+ do
+ {
+ ImmediateInterruptOK = interruptOK;
+ CHECK_FOR_INTERRUPTS();
+ errStatus = sem_wait(PG_SEM_REF(sema));
+ ImmediateInterruptOK = false;
+ } while (errStatus < 0 && errno == EINTR && !get_timeout_indicator(LOCK_TIMEOUT));
+
+ if (get_timeout_indicator(LOCK_TIMEOUT))
+ return;
+ if (errStatus < 0)
+ elog(FATAL, "sem_wait failed: %m");
+ }
diff -dcrpN postgresql.orig/src/backend/port/sysv_sema.c postgresql/src/backend/port/sysv_sema.c
*** postgresql.orig/src/backend/port/sysv_sema.c 2012-01-02 12:35:11.503186847 +0100
--- postgresql/src/backend/port/sysv_sema.c 2012-04-04 15:01:16.902757977 +0200
***************
*** 30,35 ****
--- 30,36 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/timeout.h"
#ifndef HAVE_UNION_SEMUN
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 495,497 ****
--- 496,528 ----
return true;
}
+
+ /*
+ * PGSemaphoreTimedLock
+ *
+ * Lock a semaphore (decrement count), blocking if count would be < 0
+ * Return if lock_timeout expired
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ int errStatus;
+ 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;
+ } while (errStatus < 0 && errno == EINTR && !get_timeout_indicator(LOCK_TIMEOUT));
+
+ if (get_timeout_indicator(LOCK_TIMEOUT))
+ return;
+ if (errStatus < 0)
+ elog(FATAL, "semop(id=%d) failed: %m", sema->semId);
+ }
diff -dcrpN postgresql.orig/src/backend/port/win32_sema.c postgresql/src/backend/port/win32_sema.c
*** postgresql.orig/src/backend/port/win32_sema.c 2012-01-02 12:35:11.504186789 +0100
--- postgresql/src/backend/port/win32_sema.c 2012-04-04 15:01:35.197850015 +0200
***************
*** 16,21 ****
--- 16,22 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/timeout.h"
static HANDLE *mySemSet; /* IDs of sema sets acquired so far */
static int numSems; /* number of sema sets acquired so far */
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 205,207 ****
--- 206,263 ----
/* 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.
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ DWORD ret;
+ HANDLE wh[2];
+
+ wh[0] = *sema;
+ wh[1] = pgwin32_signal_event;
+
+ /*
+ * 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();
+
+ errno = 0;
+ ret = WaitForMultipleObjectsEx(2, wh, FALSE, INFINITE, TRUE);
+
+ if (ret == WAIT_OBJECT_0)
+ {
+ /* We got it! */
+ return;
+ }
+ else if (ret == WAIT_OBJECT_0 + 1)
+ {
+ /* Signal event is set - we have a signal to deliver */
+ pgwin32_dispatch_queued_signals();
+ errno = EINTR;
+ }
+ else
+ /* Otherwise we are in trouble */
+ errno = EIDRM;
+
+ ImmediateInterruptOK = false;
+ } while (errno == EINTR && !get_timeout_indicator(LOCK_TIMEOUT));
+
+ if (get_timeout_indicator(LOCK_TIMEOUT))
+ return;
+ if (errno != 0)
+ ereport(FATAL,
+ (errmsg("could not lock semaphore: error code %d", (int) GetLastError())));
+ }
diff -dcrpN postgresql.orig/src/backend/postmaster/autovacuum.c postgresql/src/backend/postmaster/autovacuum.c
*** postgresql.orig/src/backend/postmaster/autovacuum.c 2012-01-27 10:29:06.483649830 +0100
--- postgresql/src/backend/postmaster/autovacuum.c 2012-04-04 15:03:20.447379486 +0200
***************
*** 86,94 ****
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
- #include "storage/proc.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "tcop/tcopprot.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
--- 86,94 ----
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
+ #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
*************** AutoVacLauncherMain(int argc, char *argv
*** 484,490 ****
/* Forget any pending QueryCancel request */
QueryCancelPending = false;
! disable_sig_alarm(true);
QueryCancelPending = false; /* again in case timeout occurred */
/* Report the error to the server log */
--- 484,490 ----
/* Forget any pending QueryCancel request */
QueryCancelPending = false;
! disable_all_timeouts(false);
QueryCancelPending = false; /* again in case timeout occurred */
/* Report the error to the server log */
diff -dcrpN postgresql.orig/src/backend/postmaster/postmaster.c postgresql/src/backend/postmaster/postmaster.c
*** postgresql.orig/src/backend/postmaster/postmaster.c 2012-03-29 08:36:59.775468454 +0200
--- postgresql/src/backend/postmaster/postmaster.c 2012-04-04 15:03:54.927552935 +0200
***************
*** 112,118 ****
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
! #include "storage/proc.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/datetime.h"
--- 112,118 ----
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
! #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/datetime.h"
*************** BackendInitialize(Port *port)
*** 3466,3474 ****
* Ready to begin client interaction. We will give up and exit(1) after a
* time delay, so that a broken client can't hog a connection
* indefinitely. PreAuthDelay and any DNS interactions above don't count
! * against the time limit.
*/
! if (!enable_sig_alarm(AuthenticationTimeout * 1000, false))
elog(FATAL, "could not set timer for startup packet timeout");
/*
--- 3466,3474 ----
* Ready to begin client interaction. We will give up and exit(1) after a
* time delay, so that a broken client can't hog a connection
* indefinitely. PreAuthDelay and any DNS interactions above don't count
! * against the time limit. Use the deadlock timeout interface.
*/
! if (!enable_timeout(DEADLOCK_TIMEOUT, AuthenticationTimeout * 1000))
elog(FATAL, "could not set timer for startup packet timeout");
/*
*************** BackendInitialize(Port *port)
*** 3506,3512 ****
/*
* Disable the timeout, and prevent SIGTERM/SIGQUIT again.
*/
! if (!disable_sig_alarm(false))
elog(FATAL, "could not disable timer for startup packet timeout");
PG_SETMASK(&BlockSig);
}
--- 3506,3512 ----
/*
* Disable the timeout, and prevent SIGTERM/SIGQUIT again.
*/
! if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for startup packet timeout");
PG_SETMASK(&BlockSig);
}
diff -dcrpN postgresql.orig/src/backend/postmaster/startup.c postgresql/src/backend/postmaster/startup.c
*** postgresql.orig/src/backend/postmaster/startup.c 2012-01-02 12:35:11.508186555 +0100
--- postgresql/src/backend/postmaster/startup.c 2012-04-04 15:04:32.902743966 +0200
***************
*** 27,33 ****
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
! #include "storage/proc.h"
#include "utils/guc.h"
--- 27,33 ----
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
! #include "storage/timeout.h"
#include "utils/guc.h"
*************** StartupProcessMain(void)
*** 195,202 ****
pqsignal(SIGTERM, StartupProcShutdownHandler); /* request shutdown */
pqsignal(SIGQUIT, startupproc_quickdie); /* hard crash time */
if (EnableHotStandby)
! pqsignal(SIGALRM, handle_standby_sig_alarm); /* ignored unless
! * InHotStandby */
else
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
--- 195,202 ----
pqsignal(SIGTERM, StartupProcShutdownHandler); /* request shutdown */
pqsignal(SIGQUIT, startupproc_quickdie); /* hard crash time */
if (EnableHotStandby)
! pqsignal(SIGALRM, handle_sig_alarm); /* ignored unless
! * InHotStandby */
else
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
diff -dcrpN postgresql.orig/src/backend/storage/ipc/standby.c postgresql/src/backend/storage/ipc/standby.c
*** postgresql.orig/src/backend/storage/ipc/standby.c 2012-02-05 12:28:36.003281960 +0100
--- postgresql/src/backend/storage/ipc/standby.c 2012-04-04 15:05:29.887030606 +0200
***************
*** 23,32 ****
#include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
- #include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/sinvaladt.h"
#include "storage/standby.h"
#include "utils/ps_status.h"
#include "utils/timestamp.h"
--- 23,32 ----
#include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
#include "storage/procarray.h"
#include "storage/sinvaladt.h"
#include "storage/standby.h"
+ #include "storage/timeout.h"
#include "utils/ps_status.h"
#include "utils/timestamp.h"
*************** ResolveRecoveryConflictWithLock(Oid dbOi
*** 394,400 ****
void
ResolveRecoveryConflictWithBufferPin(void)
{
- bool sig_alarm_enabled = false;
TimestampTz ltime;
TimestampTz now;
--- 394,399 ----
*************** ResolveRecoveryConflictWithBufferPin(voi
*** 409,417 ****
* We're willing to wait forever for conflicts, so set timeout for
* deadlock check (only)
*/
! if (enable_standby_sig_alarm(now, now, true))
! sig_alarm_enabled = true;
! else
elog(FATAL, "could not set timer for process wakeup");
}
else if (now >= ltime)
--- 408,414 ----
* We're willing to wait forever for conflicts, so set timeout for
* deadlock check (only)
*/
! if (!enable_timeout(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout))
elog(FATAL, "could not set timer for process wakeup");
}
else if (now >= ltime)
*************** ResolveRecoveryConflictWithBufferPin(voi
*** 423,446 ****
}
else
{
/*
* Wake up at ltime, and check for deadlocks as well if we will be
* waiting longer than deadlock_timeout
*/
! if (enable_standby_sig_alarm(now, ltime, false))
! sig_alarm_enabled = true;
! else
elog(FATAL, "could not set timer for process wakeup");
}
/* Wait to be signaled by UnpinBuffer() */
ProcWaitForSignal();
! if (sig_alarm_enabled)
! {
! if (!disable_standby_sig_alarm())
! elog(FATAL, "could not disable timer for process wakeup");
! }
}
void
--- 420,447 ----
}
else
{
+ long secs, msecs;
+ int usecs;
+
/*
* Wake up at ltime, and check for deadlocks as well if we will be
* waiting longer than deadlock_timeout
*/
! if (!enable_timeout(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout))
! elog(FATAL, "could not set timer for process wakeup");
!
! TimestampDifference(now, ltime, &secs, &usecs);
! msecs = secs * 1000 + usecs / 1000;
!
! if (!enable_timeout(STANDBY_TIMEOUT, msecs))
elog(FATAL, "could not set timer for process wakeup");
}
/* Wait to be signaled by UnpinBuffer() */
ProcWaitForSignal();
! if (!disable_all_timeouts(false))
! elog(FATAL, "could not disable timer for process wakeup");
}
void
diff -dcrpN postgresql.orig/src/backend/storage/lmgr/lmgr.c postgresql/src/backend/storage/lmgr/lmgr.c
*** postgresql.orig/src/backend/storage/lmgr/lmgr.c 2012-01-02 12:35:11.517186027 +0100
--- postgresql/src/backend/storage/lmgr/lmgr.c 2012-04-03 15:55:39.981617113 +0200
***************
*** 19,26 ****
--- 19,29 ----
#include "access/transam.h"
#include "access/xact.h"
#include "catalog/catalog.h"
+ #include "catalog/pg_database.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
+ #include "utils/lsyscache.h"
+ #include "storage/proc.h"
#include "storage/procarray.h"
#include "utils/inval.h"
*************** LockRelationOid(Oid relid, LOCKMODE lock
*** 78,83 ****
--- 81,101 ----
res = LockAcquire(&tag, lockmode, false, false);
+ if (res == LOCKACQUIRE_NOT_AVAIL)
+ {
+ char *relname = get_rel_name(relid);
+ if (relname)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ relname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation with OID %u",
+ relid)));
+ }
+
/*
* Now that we have the lock, check for invalidation messages, so that we
* will update or flush any stale relcache entry before we try to use it.
*************** LockRelation(Relation relation, LOCKMODE
*** 174,179 ****
--- 192,203 ----
res = LockAcquire(&tag, lockmode, false, false);
+ if (res == LOCKACQUIRE_NOT_AVAIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ RelationGetRelationName(relation))));
+
/*
* Now that we have the lock, check for invalidation messages; see notes
* in LockRelationOid.
*************** LockRelationIdForSession(LockRelId *reli
*** 251,257 ****
SET_LOCKTAG_RELATION(tag, relid->dbId, relid->relId);
! (void) LockAcquire(&tag, lockmode, true, false);
}
/*
--- 275,294 ----
SET_LOCKTAG_RELATION(tag, relid->dbId, relid->relId);
! if (LockAcquire(&tag, lockmode, true, false) == LOCKACQUIRE_NOT_AVAIL)
! {
! char *relname = get_rel_name(relid->relId);
! if (relname)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on relation \"%s\"",
! relname)));
! else
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on relation with OID %u",
! relid->relId)));
! }
}
/*
*************** LockRelationForExtension(Relation relati
*** 286,292 ****
relation->rd_lockInfo.lockRelId.dbId,
relation->rd_lockInfo.lockRelId.relId);
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 323,333 ----
relation->rd_lockInfo.lockRelId.dbId,
relation->rd_lockInfo.lockRelId.relId);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on index \"%s\"",
! RelationGetRelationName(relation))));
}
/*
*************** LockPage(Relation relation, BlockNumber
*** 320,326 ****
relation->rd_lockInfo.lockRelId.relId,
blkno);
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 361,371 ----
relation->rd_lockInfo.lockRelId.relId,
blkno);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on page %u of relation \"%s\"",
! blkno, RelationGetRelationName(relation))));
}
/*
*************** LockTuple(Relation relation, ItemPointer
*** 376,382 ****
ItemPointerGetBlockNumber(tid),
ItemPointerGetOffsetNumber(tid));
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 421,431 ----
ItemPointerGetBlockNumber(tid),
ItemPointerGetOffsetNumber(tid));
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on row in relation \"%s\"",
! RelationGetRelationName(relation))));
}
/*
*************** XactLockTableInsert(TransactionId xid)
*** 430,436 ****
SET_LOCKTAG_TRANSACTION(tag, xid);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
}
/*
--- 479,488 ----
SET_LOCKTAG_TRANSACTION(tag, xid);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on transaction with ID %u", xid)));
}
/*
*************** XactLockTableWait(TransactionId xid)
*** 474,480 ****
SET_LOCKTAG_TRANSACTION(tag, xid);
! (void) LockAcquire(&tag, ShareLock, false, false);
LockRelease(&tag, ShareLock, false);
--- 526,535 ----
SET_LOCKTAG_TRANSACTION(tag, xid);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on transaction with ID %u", xid)));
LockRelease(&tag, ShareLock, false);
*************** LockDatabaseObject(Oid classid, Oid obji
*** 535,541 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, false, false);
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
--- 590,600 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
*************** LockSharedObject(Oid classid, Oid objid,
*** 576,582 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, false, false);
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
--- 635,645 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
*************** LockSharedObjectForSession(Oid classid,
*** 618,624 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, true, false);
}
/*
--- 681,702 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, true, false) == LOCKACQUIRE_NOT_AVAIL)
! switch(classid)
! {
! case DatabaseRelationId:
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on database with ID %u",
! objid)));
! break;
! default:
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
! break;
! }
}
/*
diff -dcrpN postgresql.orig/src/backend/storage/lmgr/lock.c postgresql/src/backend/storage/lmgr/lock.c
*** postgresql.orig/src/backend/storage/lmgr/lock.c 2012-01-25 06:45:42.322747976 +0100
--- postgresql/src/backend/storage/lmgr/lock.c 2012-04-04 10:47:12.072945214 +0200
*************** static void RemoveLocalLock(LOCALLOCK *l
*** 338,344 ****
static PROCLOCK *SetupLockInTable(LockMethod lockMethodTable, PGPROC *proc,
const LOCKTAG *locktag, uint32 hashcode, LOCKMODE lockmode);
static void GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner);
! static void WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner);
static void ReleaseLockForOwner(LOCALLOCK *locallock, ResourceOwner owner);
static bool UnGrantLock(LOCK *lock, LOCKMODE lockmode,
PROCLOCK *proclock, LockMethod lockMethodTable);
--- 338,344 ----
static PROCLOCK *SetupLockInTable(LockMethod lockMethodTable, PGPROC *proc,
const LOCKTAG *locktag, uint32 hashcode, LOCKMODE lockmode);
static void GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner);
! static int WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner);
static void ReleaseLockForOwner(LOCALLOCK *locallock, ResourceOwner owner);
static bool UnGrantLock(LOCK *lock, LOCKMODE lockmode,
PROCLOCK *proclock, LockMethod lockMethodTable);
*************** ProcLockHashCode(const PROCLOCKTAG *proc
*** 544,550 ****
* dontWait: if true, don't wait to acquire lock
*
* Returns one of:
! * LOCKACQUIRE_NOT_AVAIL lock not available, and dontWait=true
* LOCKACQUIRE_OK lock successfully acquired
* LOCKACQUIRE_ALREADY_HELD incremented count for lock already held
*
--- 544,550 ----
* dontWait: if true, don't wait to acquire lock
*
* Returns one of:
! * LOCKACQUIRE_NOT_AVAIL lock not available, either dontWait=true or timeout
* LOCKACQUIRE_OK lock successfully acquired
* LOCKACQUIRE_ALREADY_HELD incremented count for lock already held
*
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 863,869 ****
locktag->locktag_type,
lockmode);
! WaitOnLock(locallock, owner);
TRACE_POSTGRESQL_LOCK_WAIT_DONE(locktag->locktag_field1,
locktag->locktag_field2,
--- 863,869 ----
locktag->locktag_type,
lockmode);
! status = WaitOnLock(locallock, owner);
TRACE_POSTGRESQL_LOCK_WAIT_DONE(locktag->locktag_field1,
locktag->locktag_field2,
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 878,897 ****
* done when the lock was granted to us --- see notes in WaitOnLock.
*/
! /*
! * Check the proclock entry status, in case something in the ipc
! * communication doesn't work correctly.
! */
! if (!(proclock->holdMask & LOCKBIT_ON(lockmode)))
{
! PROCLOCK_PRINT("LockAcquire: INCONSISTENT", proclock);
! LOCK_PRINT("LockAcquire: INCONSISTENT", lock, lockmode);
! /* Should we retry ? */
! LWLockRelease(partitionLock);
! elog(ERROR, "LockAcquire failed");
}
- PROCLOCK_PRINT("LockAcquire: granted", proclock);
- LOCK_PRINT("LockAcquire: granted", lock, lockmode);
}
LWLockRelease(partitionLock);
--- 878,909 ----
* done when the lock was granted to us --- see notes in WaitOnLock.
*/
! switch (status)
{
! case STATUS_OK:
! /*
! * Check the proclock entry status, in case something in the ipc
! * communication doesn't work correctly.
! */
! if (!(proclock->holdMask & LOCKBIT_ON(lockmode)))
! {
! PROCLOCK_PRINT("LockAcquire: INCONSISTENT", proclock);
! LOCK_PRINT("LockAcquire: INCONSISTENT", lock, lockmode);
! /* Should we retry ? */
! LWLockRelease(partitionLock);
! elog(ERROR, "LockAcquire failed");
! }
! PROCLOCK_PRINT("LockAcquire: granted", proclock);
! LOCK_PRINT("LockAcquire: granted", lock, lockmode);
! break;
! case STATUS_WAITING:
! PROCLOCK_PRINT("LockAcquire: timed out", proclock);
! LOCK_PRINT("LockAcquire: timed out", lock, lockmode);
! break;
! default:
! elog(ERROR, "LockAcquire invalid status");
! break;
}
}
LWLockRelease(partitionLock);
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 911,917 ****
locktag->locktag_field2);
}
! return LOCKACQUIRE_OK;
}
/*
--- 923,929 ----
locktag->locktag_field2);
}
! return (status == STATUS_OK ? LOCKACQUIRE_OK : LOCKACQUIRE_NOT_AVAIL);
}
/*
*************** GrantAwaitedLock(void)
*** 1371,1384 ****
* Caller must have set MyProc->heldLocks to reflect locks already held
* on the lockable object by this process.
*
* The appropriate partition lock must be held at entry.
*/
! static void
WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner)
{
LOCKMETHODID lockmethodid = LOCALLOCK_LOCKMETHOD(*locallock);
LockMethod lockMethodTable = LockMethods[lockmethodid];
char *volatile new_status = NULL;
LOCK_PRINT("WaitOnLock: sleeping on lock",
locallock->lock, locallock->tag.mode);
--- 1383,1402 ----
* Caller must have set MyProc->heldLocks to reflect locks already held
* on the lockable object by this process.
*
+ * Result: returns value of ProcSleep()
+ * STATUS_OK if we acquired the lock
+ * STATUS_ERROR if not (deadlock)
+ * STATUS_WAITING if not (timeout)
+ *
* The appropriate partition lock must be held at entry.
*/
! static int
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);
*************** WaitOnLock(LOCALLOCK *locallock, Resourc
*** 1420,1427 ****
*/
PG_TRY();
{
! if (ProcSleep(locallock, lockMethodTable) != STATUS_OK)
{
/*
* We failed as a result of a deadlock, see CheckDeadLock(). Quit
* now.
--- 1438,1450 ----
*/
PG_TRY();
{
! wait_status = ProcSleep(locallock, lockMethodTable);
! switch (wait_status)
{
+ case STATUS_OK:
+ case STATUS_WAITING:
+ break;
+ default:
/*
* We failed as a result of a deadlock, see CheckDeadLock(). Quit
* now.
*************** WaitOnLock(LOCALLOCK *locallock, Resourc
*** 1466,1473 ****
pfree(new_status);
}
! LOCK_PRINT("WaitOnLock: wakeup on lock",
locallock->lock, locallock->tag.mode);
}
/*
--- 1489,1502 ----
pfree(new_status);
}
! 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);
+
+ return wait_status;
}
/*
*************** VirtualXactLock(VirtualTransactionId vxi
*** 3739,3745 ****
LWLockRelease(proc->backendLock);
/* Time to wait. */
! (void) LockAcquire(&tag, ShareLock, false, false);
LockRelease(&tag, ShareLock, false);
return true;
--- 3768,3778 ----
LWLockRelease(proc->backendLock);
/* Time to wait. */
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on virtual transaction with ID %u",
! vxid.localTransactionId)));
LockRelease(&tag, ShareLock, false);
return true;
diff -dcrpN postgresql.orig/src/backend/storage/lmgr/Makefile postgresql/src/backend/storage/lmgr/Makefile
*** postgresql.orig/src/backend/storage/lmgr/Makefile 2011-02-10 10:36:32.248685484 +0100
--- postgresql/src/backend/storage/lmgr/Makefile 2012-04-04 14:31:15.547760162 +0200
*************** subdir = src/backend/storage/lmgr
*** 12,18 ****
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
! OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o predicate.o
include $(top_srcdir)/src/backend/common.mk
--- 12,18 ----
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
! OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o predicate.o timeout.o
include $(top_srcdir)/src/backend/common.mk
diff -dcrpN postgresql.orig/src/backend/storage/lmgr/proc.c postgresql/src/backend/storage/lmgr/proc.c
*** postgresql.orig/src/backend/storage/lmgr/proc.c 2012-03-22 11:00:51.723002056 +0100
--- postgresql/src/backend/storage/lmgr/proc.c 2012-04-04 15:39:56.323621920 +0200
***************
*** 48,59 ****
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/spin.h"
#include "utils/timestamp.h"
/* GUC variables */
- int DeadlockTimeout = 1000;
- int StatementTimeout = 0;
bool log_lock_waits = false;
/* Pointer to this process's PGPROC struct, if any */
--- 48,58 ----
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/spin.h"
+ #include "storage/timeout.h"
#include "utils/timestamp.h"
/* GUC variables */
bool log_lock_waits = false;
/* Pointer to this process's PGPROC struct, if any */
*************** PGPROC *PreparedXactProcs = NULL;
*** 77,103 ****
/* If we are waiting for a lock, this points to the associated LOCALLOCK */
static LOCALLOCK *lockAwaited = NULL;
! /* Mark these volatile because they can be changed by signal handler */
! static volatile bool standby_timeout_active = false;
! static volatile bool statement_timeout_active = false;
! static volatile bool deadlock_timeout_active = false;
! static volatile DeadLockState deadlock_state = DS_NOT_YET_CHECKED;
! volatile bool cancel_from_timeout = false;
/* timeout_start_time is set when log_lock_waits is true */
static TimestampTz timeout_start_time;
- /* statement_fin_time is valid only if statement_timeout_active is true */
- static TimestampTz statement_fin_time;
- static TimestampTz statement_fin_time2; /* valid only in recovery */
-
-
static void RemoveProcFromArray(int code, Datum arg);
static void ProcKill(int code, Datum arg);
static void AuxiliaryProcKill(int code, Datum arg);
- static bool CheckStatementTimeout(void);
- static bool CheckStandbyTimeout(void);
-
/*
* Report shared-memory space needed by InitProcGlobal.
--- 76,90 ----
/* If we are waiting for a lock, this points to the associated LOCALLOCK */
static LOCALLOCK *lockAwaited = NULL;
! /* Declared in timeout.c */
! extern volatile DeadLockState deadlock_state;
/* timeout_start_time is set when log_lock_waits is true */
static TimestampTz timeout_start_time;
static void RemoveProcFromArray(int code, Datum arg);
static void ProcKill(int code, Datum arg);
static void AuxiliaryProcKill(int code, Datum arg);
/*
* Report shared-memory space needed by InitProcGlobal.
*************** LockWaitCancel(void)
*** 651,657 ****
return;
/* Turn off the deadlock timer, if it's still running (see ProcSleep) */
! disable_sig_alarm(false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
--- 638,644 ----
return;
/* Turn off the deadlock timer, if it's still running (see ProcSleep) */
! disable_timeout(DEADLOCK_TIMEOUT, false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
*************** ProcQueueInit(PROC_QUEUE *queue)
*** 885,891 ****
* 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).
*
* ASSUME: that no one will fiddle with the queue until after
* we release the partition lock.
--- 872,881 ----
* 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)
! * STATUS_WAITING if not (timeout)
*
* ASSUME: that no one will fiddle with the queue until after
* we release the partition lock.
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 907,912 ****
--- 897,903 ----
LOCKMASK myHeldLocks = MyProc->heldLocks;
bool early_deadlock = false;
bool allow_autovacuum_cancel = true;
+ bool timeout_detected;
int myWaitStatus;
PGPROC *proc;
int i;
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1044,1055 ****
* 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 (!enable_sig_alarm(DeadlockTimeout, false))
elog(FATAL, "could not set timer for process wakeup");
/*
! * 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
* saved wakeup might be leftover from a previous operation (for example,
* we aborted ProcWaitForSignal just before someone did ProcSendSignal).
--- 1035,1054 ----
* 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 (!enable_timeout(DEADLOCK_TIMEOUT, DeadlockTimeout))
elog(FATAL, "could not set timer for process wakeup");
/*
! * Reset timer so we are awaken in case of lock timeout.
! * This doesn't modify the timer for deadlock check in case
! * the deadlock check happens earlier.
! */
! if (!enable_timeout(LOCK_TIMEOUT, LockTimeout))
! elog(FATAL, "could not set timer for process wakeup");
!
! /*
! * 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).
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1066,1072 ****
*/
do
{
! PGSemaphoreLock(&MyProc->sem, true);
/*
* waitStatus could change from STATUS_WAITING to something else
--- 1065,1074 ----
*/
do
{
! PGSemaphoreTimedLock(&MyProc->sem, true);
!
! if (get_timeout_indicator(LOCK_TIMEOUT))
! break;
/*
* waitStatus could change from STATUS_WAITING to something else
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1194,1202 ****
} while (myWaitStatus == STATUS_WAITING);
/*
! * Disable the timer, if it's still running
*/
! if (!disable_sig_alarm(false))
elog(FATAL, "could not disable timer for process wakeup");
/*
--- 1196,1212 ----
} while (myWaitStatus == STATUS_WAITING);
/*
! * Disable the deadlock timer, if it's still running
*/
! if (!disable_timeout(DEADLOCK_TIMEOUT, false))
! elog(FATAL, "could not disable timer for process wakeup");
!
! /*
! * Disable the lock timeout timer, if it's still running
! * but keep the indicator for later checks.
! */
! timeout_detected = get_timeout_indicator(LOCK_TIMEOUT);
! if (!disable_timeout(LOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for process wakeup");
/*
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1207,1212 ****
--- 1217,1231 ----
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 LockWaitCancel to do anything.
*/
lockAwaited = NULL;
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1220,1227 ****
/*
* We don't have to do anything else, because the awaker did all the
* necessary update of the lock table and MyProc.
*/
! return MyProc->waitStatus;
}
--- 1239,1248 ----
/*
* 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 (timeout_detected ? STATUS_WAITING : MyProc->waitStatus);
}
*************** ProcLockWakeup(LockMethod lockMethodTabl
*** 1328,1448 ****
}
/*
- * CheckDeadLock
- *
- * We only get to this routine if we got SIGALRM after DeadlockTimeout
- * while waiting for a lock to be released by some other process. Look
- * to see if there's a deadlock; if not, just return and continue waiting.
- * (But signal ProcSleep to log a message, if log_lock_waits is true.)
- * If we have a real deadlock, remove ourselves from the lock's wait queue
- * and signal an error to ProcSleep.
- *
- * NB: this is run inside a signal handler, so be very wary about what is done
- * here or in called routines.
- */
- static void
- CheckDeadLock(void)
- {
- int i;
-
- /*
- * Acquire exclusive lock on the entire shared lock data structures. Must
- * grab LWLocks in partition-number order to avoid LWLock deadlock.
- *
- * Note that the deadlock check interrupt had better not be enabled
- * anywhere that this process itself holds lock partition locks, else this
- * will wait forever. Also note that LWLockAcquire creates a critical
- * section, so that this routine cannot be interrupted by cancel/die
- * interrupts.
- */
- for (i = 0; i < NUM_LOCK_PARTITIONS; i++)
- LWLockAcquire(FirstLockMgrLock + i, LW_EXCLUSIVE);
-
- /*
- * Check to see if we've been awoken by anyone in the interim.
- *
- * If we have, we can return and resume our transaction -- happy day.
- * Before we are awoken the process releasing the lock grants it to us so
- * we know that we don't have to wait anymore.
- *
- * We check by looking to see if we've been unlinked from the wait queue.
- * This is quicker than checking our semaphore's state, since no kernel
- * call is needed, and it is safe because we hold the lock partition lock.
- */
- if (MyProc->links.prev == NULL ||
- MyProc->links.next == NULL)
- goto check_done;
-
- #ifdef LOCK_DEBUG
- if (Debug_deadlocks)
- DumpAllLocks();
- #endif
-
- /* Run the deadlock check, and set deadlock_state for use by ProcSleep */
- deadlock_state = DeadLockCheck(MyProc);
-
- if (deadlock_state == DS_HARD_DEADLOCK)
- {
- /*
- * Oops. We have a deadlock.
- *
- * Get this process out of wait state. (Note: we could do this more
- * efficiently by relying on lockAwaited, but use this coding to
- * preserve the flexibility to kill some other transaction than the
- * one detecting the deadlock.)
- *
- * RemoveFromWaitQueue sets MyProc->waitStatus to STATUS_ERROR, so
- * ProcSleep will report an error after we return from the signal
- * handler.
- */
- Assert(MyProc->waitLock != NULL);
- RemoveFromWaitQueue(MyProc, LockTagHashCode(&(MyProc->waitLock->tag)));
-
- /*
- * Unlock my semaphore so that the interrupted ProcSleep() call can
- * finish.
- */
- PGSemaphoreUnlock(&MyProc->sem);
-
- /*
- * We're done here. Transaction abort caused by the error that
- * ProcSleep will raise will cause any other locks we hold to be
- * released, thus allowing other processes to wake up; we don't need
- * to do that here. NOTE: an exception is that releasing locks we
- * hold doesn't consider the possibility of waiters that were blocked
- * behind us on the lock we just failed to get, and might now be
- * wakable because we're not in front of them anymore. However,
- * RemoveFromWaitQueue took care of waking up any such processes.
- */
- }
- else if (log_lock_waits || deadlock_state == DS_BLOCKED_BY_AUTOVACUUM)
- {
- /*
- * Unlock my semaphore so that the interrupted ProcSleep() call can
- * print the log message (we daren't do it here because we are inside
- * a signal handler). It will then sleep again until someone releases
- * the lock.
- *
- * If blocked by autovacuum, this wakeup will enable ProcSleep to send
- * the canceling signal to the autovacuum worker.
- */
- PGSemaphoreUnlock(&MyProc->sem);
- }
-
- /*
- * And release locks. We do this in reverse order for two reasons: (1)
- * Anyone else who needs more than one of the locks will be trying to lock
- * them in increasing order; we don't want to release the other process
- * until it can get all the locks it needs. (2) This avoids O(N^2)
- * behavior inside LWLockRelease.
- */
- check_done:
- for (i = NUM_LOCK_PARTITIONS; --i >= 0;)
- LWLockRelease(FirstLockMgrLock + i);
- }
-
-
- /*
* ProcWaitForSignal - wait for a signal from another backend.
*
* This can share the semaphore normally used for waiting for locks,
--- 1349,1354 ----
*************** ProcSendSignal(int pid)
*** 1494,1894 ****
if (proc != NULL)
PGSemaphoreUnlock(&proc->sem);
}
-
-
- /*****************************************************************************
- * SIGALRM interrupt support
- *
- * Maybe these should be in pqsignal.c?
- *****************************************************************************/
-
- /*
- * Enable the SIGALRM interrupt to fire after the specified delay
- *
- * Delay is given in milliseconds. Caller should be sure a SIGALRM
- * signal handler is installed before this is called.
- *
- * This code properly handles nesting of deadlock timeout alarms within
- * statement timeout alarms.
- *
- * Returns TRUE if okay, FALSE on failure.
- */
- bool
- enable_sig_alarm(int delayms, bool is_statement_timeout)
- {
- TimestampTz fin_time;
- struct itimerval timeval;
-
- if (is_statement_timeout)
- {
- /*
- * Begin statement-level timeout
- *
- * Note that we compute statement_fin_time with reference to the
- * statement_timestamp, but apply the specified delay without any
- * correction; that is, we ignore whatever time has elapsed since
- * statement_timestamp was set. In the normal case only a small
- * interval will have elapsed and so this doesn't matter, but there
- * are corner cases (involving multi-statement query strings with
- * embedded COMMIT or ROLLBACK) where we might re-initialize the
- * statement timeout long after initial receipt of the message. In
- * such cases the enforcement of the statement timeout will be a bit
- * inconsistent. This annoyance is judged not worth the cost of
- * performing an additional gettimeofday() here.
- */
- Assert(!deadlock_timeout_active);
- fin_time = GetCurrentStatementStartTimestamp();
- fin_time = TimestampTzPlusMilliseconds(fin_time, delayms);
- statement_fin_time = fin_time;
- cancel_from_timeout = false;
- statement_timeout_active = true;
- }
- else if (statement_timeout_active)
- {
- /*
- * Begin deadlock timeout with statement-level timeout active
- *
- * Here, we want to interrupt at the closer of the two timeout times.
- * If fin_time >= statement_fin_time then we need not touch the
- * existing timer setting; else set up to interrupt at the deadlock
- * timeout time.
- *
- * NOTE: in this case it is possible that this routine will be
- * interrupted by the previously-set timer alarm. This is okay
- * because the signal handler will do only what it should do according
- * to the state variables. The deadlock checker may get run earlier
- * than normal, but that does no harm.
- */
- timeout_start_time = GetCurrentTimestamp();
- fin_time = TimestampTzPlusMilliseconds(timeout_start_time, delayms);
- deadlock_timeout_active = true;
- if (fin_time >= statement_fin_time)
- return true;
- }
- else
- {
- /* Begin deadlock timeout with no statement-level timeout */
- deadlock_timeout_active = true;
- /* GetCurrentTimestamp can be expensive, so only do it if we must */
- if (log_lock_waits)
- timeout_start_time = GetCurrentTimestamp();
- }
-
- /* If we reach here, okay to set the timer interrupt */
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = delayms / 1000;
- timeval.it_value.tv_usec = (delayms % 1000) * 1000;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- return true;
- }
-
- /*
- * Cancel the SIGALRM timer, either for a deadlock timeout or a statement
- * timeout. If a deadlock timeout is canceled, any active statement timeout
- * remains in force.
- *
- * Returns TRUE if okay, FALSE on failure.
- */
- bool
- disable_sig_alarm(bool is_statement_timeout)
- {
- /*
- * Always disable the interrupt if it is active; this avoids being
- * interrupted by the signal handler and thereby possibly getting
- * confused.
- *
- * We will re-enable the interrupt if necessary in CheckStatementTimeout.
- */
- if (statement_timeout_active || deadlock_timeout_active)
- {
- struct itimerval timeval;
-
- MemSet(&timeval, 0, sizeof(struct itimerval));
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- {
- statement_timeout_active = false;
- cancel_from_timeout = false;
- deadlock_timeout_active = false;
- return false;
- }
- }
-
- /* Always cancel deadlock timeout, in case this is error cleanup */
- deadlock_timeout_active = false;
-
- /* Cancel or reschedule statement timeout */
- if (is_statement_timeout)
- {
- statement_timeout_active = false;
- cancel_from_timeout = false;
- }
- else if (statement_timeout_active)
- {
- if (!CheckStatementTimeout())
- return false;
- }
- return true;
- }
-
-
- /*
- * Check for statement timeout. If the timeout time has come,
- * trigger a query-cancel interrupt; if not, reschedule the SIGALRM
- * interrupt to occur at the right time.
- *
- * Returns true if okay, false if failed to set the interrupt.
- */
- static bool
- CheckStatementTimeout(void)
- {
- TimestampTz now;
-
- if (!statement_timeout_active)
- return true; /* do nothing if not active */
-
- now = GetCurrentTimestamp();
-
- if (now >= statement_fin_time)
- {
- /* Time to die */
- statement_timeout_active = false;
- cancel_from_timeout = true;
- #ifdef HAVE_SETSID
- /* try to signal whole process group */
- kill(-MyProcPid, SIGINT);
- #endif
- kill(MyProcPid, SIGINT);
- }
- else
- {
- /* Not time yet, so (re)schedule the interrupt */
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
-
- /*
- * It's possible that the difference is less than a microsecond;
- * ensure we don't cancel, rather than set, the interrupt.
- */
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- }
-
- return true;
- }
-
-
- /*
- * Signal handler for SIGALRM for normal user backends
- *
- * Process deadlock check and/or statement timeout check, as needed.
- * To avoid various edge cases, we must be careful to do nothing
- * when there is nothing to be done. We also need to be able to
- * reschedule the timer interrupt if called before end of statement.
- */
- void
- handle_sig_alarm(SIGNAL_ARGS)
- {
- int save_errno = errno;
-
- /* SIGALRM is cause for waking anything waiting on the process latch */
- if (MyProc)
- SetLatch(&MyProc->procLatch);
-
- if (deadlock_timeout_active)
- {
- deadlock_timeout_active = false;
- CheckDeadLock();
- }
-
- if (statement_timeout_active)
- (void) CheckStatementTimeout();
-
- errno = save_errno;
- }
-
- /*
- * Signal handler for SIGALRM in Startup process
- *
- * To avoid various edge cases, we must be careful to do nothing
- * when there is nothing to be done. We also need to be able to
- * reschedule the timer interrupt if called before end of statement.
- *
- * We set either deadlock_timeout_active or statement_timeout_active
- * or both. Interrupts are enabled if standby_timeout_active.
- */
- bool
- enable_standby_sig_alarm(TimestampTz now, TimestampTz fin_time, bool deadlock_only)
- {
- TimestampTz deadlock_time = TimestampTzPlusMilliseconds(now,
- DeadlockTimeout);
-
- if (deadlock_only)
- {
- /*
- * Wake up at deadlock_time only, then wait forever
- */
- statement_fin_time = deadlock_time;
- deadlock_timeout_active = true;
- statement_timeout_active = false;
- }
- else if (fin_time > deadlock_time)
- {
- /*
- * Wake up at deadlock_time, then again at fin_time
- */
- statement_fin_time = deadlock_time;
- statement_fin_time2 = fin_time;
- deadlock_timeout_active = true;
- statement_timeout_active = true;
- }
- else
- {
- /*
- * Wake only at fin_time because its fairly soon
- */
- statement_fin_time = fin_time;
- deadlock_timeout_active = false;
- statement_timeout_active = true;
- }
-
- if (deadlock_timeout_active || statement_timeout_active)
- {
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- standby_timeout_active = true;
- }
-
- return true;
- }
-
- bool
- disable_standby_sig_alarm(void)
- {
- /*
- * Always disable the interrupt if it is active; this avoids being
- * interrupted by the signal handler and thereby possibly getting
- * confused.
- *
- * We will re-enable the interrupt if necessary in CheckStandbyTimeout.
- */
- if (standby_timeout_active)
- {
- struct itimerval timeval;
-
- MemSet(&timeval, 0, sizeof(struct itimerval));
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- {
- standby_timeout_active = false;
- return false;
- }
- }
-
- standby_timeout_active = false;
-
- return true;
- }
-
- /*
- * CheckStandbyTimeout() runs unconditionally in the Startup process
- * SIGALRM handler. Timers will only be set when InHotStandby.
- * We simply ignore any signals unless the timer has been set.
- */
- static bool
- CheckStandbyTimeout(void)
- {
- TimestampTz now;
- bool reschedule = false;
-
- standby_timeout_active = false;
-
- now = GetCurrentTimestamp();
-
- /*
- * Reschedule the timer if its not time to wake yet, or if we have both
- * timers set and the first one has just been reached.
- */
- if (now >= statement_fin_time)
- {
- if (deadlock_timeout_active)
- {
- /*
- * We're still waiting when we reach deadlock timeout, so send out
- * a request to have other backends check themselves for deadlock.
- * Then continue waiting until statement_fin_time, if that's set.
- */
- SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
- deadlock_timeout_active = false;
-
- /*
- * Begin second waiting period if required.
- */
- if (statement_timeout_active)
- {
- reschedule = true;
- statement_fin_time = statement_fin_time2;
- }
- }
- else
- {
- /*
- * We've now reached statement_fin_time, so ask all conflicts to
- * leave, so we can press ahead with applying changes in recovery.
- */
- SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
- }
- }
- else
- reschedule = true;
-
- if (reschedule)
- {
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- standby_timeout_active = true;
- }
-
- return true;
- }
-
- void
- handle_standby_sig_alarm(SIGNAL_ARGS)
- {
- int save_errno = errno;
-
- if (standby_timeout_active)
- (void) CheckStandbyTimeout();
-
- errno = save_errno;
- }
--- 1400,1402 ----
diff -dcrpN postgresql.orig/src/backend/storage/lmgr/timeout.c postgresql/src/backend/storage/lmgr/timeout.c
*** postgresql.orig/src/backend/storage/lmgr/timeout.c 1970-01-01 01:00:00.000000000 +0100
--- postgresql/src/backend/storage/lmgr/timeout.c 2012-04-04 16:11:36.110068311 +0200
***************
*** 0 ****
--- 1,722 ----
+ /*-------------------------------------------------------------------------
+ *
+ * timeout.c
+ * routines to manage timeout sources handled by SIGALRM
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/storage/lmgr/timeout.c
+ *
+ *-------------------------------------------------------------------------
+ */
+ /*
+ * Interface:
+ * enable_timeout(),
+ * disable_timeout(), disable_all_timeouts()
+ * get_timeout_indicator(), get_timeout_start()
+ * and
+ * handle_sig_alarm()
+ */
+ #include "postgres.h"
+
+ #include <sys/time.h>
+
+ #include "access/xact.h"
+ #include "miscadmin.h"
+ #include "storage/proc.h"
+ #include "storage/standby.h"
+ #include "storage/timeout.h"
+ #include "utils/timestamp.h"
+
+ /* GUC variables */
+ int DeadlockTimeout = 1000;
+ int StatementTimeout = 0;
+ int LockTimeout = 0;
+
+ /*
+ * This is used by ProcSleep() in proc.c
+ * Mark this volatile because it can be changed by the signal handler.
+ */
+ volatile DeadLockState deadlock_state = DS_NOT_YET_CHECKED;
+
+
+ /*
+ * Infrastructure for timeouts
+ */
+
+ static void InitDeadLock(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyDeadLock(bool keep_indicator);
+ static bool CheckDeadLock(void);
+
+ static void InitLockTimeout(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyLockTimeout(bool keep_indicator);
+ static bool CheckLockTimeout(void);
+
+ static void InitStatementTimeout(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyStatementTimeout(bool keep_indicator);
+ static bool CheckStatementTimeout(void);
+
+ static void InitStandbyDeadLock(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyStandbyDeadLock(bool keep_indicator);
+ static bool CheckStandbyDeadLock(void);
+
+ static void InitStandbyTimeout(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyStandbyTimeout(bool keep_indicator);
+ static bool CheckStandbyTimeout(void);
+
+ typedef void (*timeout_init)(TimestampTz, TimestampTz);
+ typedef void (*timeout_destroy)(bool);
+ typedef bool (*timeout_check)(void);
+ typedef TimestampTz (*timeout_start)(void);
+
+ typedef struct {
+ TimeoutName index;
+ bool resched_next;
+
+ /* volatile because it may be changed from the signal handler */
+ volatile bool indicator;
+
+ timeout_init timeout_init;
+ timeout_destroy timeout_destroy;
+ timeout_check timeout_check;
+ timeout_start timeout_start;
+
+ TimestampTz start_time;
+ TimestampTz fin_time;
+ } timeout_params;
+
+ /*
+ * List of possible timeout reasons in the order of enum TimeoutName.
+ * The priority of timeouts (in case two of them would trigger at the
+ * same time) is determined by this order: the earlier one in the list
+ * has higher priority.
+ */
+ static timeout_params base_timeouts[TIMEOUT_MAX] = {
+ {
+ DEADLOCK_TIMEOUT, true, false,
+ InitDeadLock, DestroyDeadLock,
+ CheckDeadLock, GetCurrentTimestamp,
+ 0
+ },
+
+ {
+ LOCK_TIMEOUT, false, false,
+ InitLockTimeout, DestroyLockTimeout,
+ CheckLockTimeout, GetCurrentTimestamp,
+ 0
+ },
+
+ {
+ STATEMENT_TIMEOUT, false, false,
+ InitStatementTimeout, DestroyStatementTimeout,
+ CheckStatementTimeout, GetCurrentStatementStartTimestamp,
+ 0
+ },
+
+ {
+ STANDBY_DEADLOCK_TIMEOUT, true, false,
+ InitStandbyDeadLock, DestroyStandbyDeadLock,
+ CheckStandbyDeadLock, GetCurrentTimestamp,
+ 0
+ },
+
+ {
+ STANDBY_TIMEOUT, false, false,
+ InitStandbyTimeout, DestroyStandbyTimeout,
+ CheckStandbyTimeout, GetCurrentTimestamp,
+ 0
+ }
+ };
+
+ /*
+ * List of active timeouts ordered by their fin_time and priority.
+ */
+ static int n_timeouts = 0;
+ static timeout_params *timeouts[TIMEOUT_MAX];
+
+ /*****************************************************************************
+ * Internal helper functions
+ *****************************************************************************/
+
+ /*
+ * Find the index of a given timeout type in the active array
+ */
+ static int
+ find_active_timeout(TimeoutName tn)
+ {
+ int i;
+
+ for (i = 0; i < n_timeouts; i++)
+ {
+ if (timeouts[i]->index == tn)
+ return i;
+ }
+
+ return -1;
+ }
+
+ #define is_timeout_active(tn) (find_active_timeout(tn) >= 0)
+
+ /*
+ * Insert tn'th timeout into the list of timeouts at the given index
+ * if the previous timeout allows rescheduling the next one in the list.
+ */
+ static void
+ insert_timeout(TimeoutName tn, int index)
+ {
+ int i;
+
+ if (index > 0 && !timeouts[index-1]->resched_next)
+ return;
+
+ for (i = n_timeouts - 1; i >= index; i--)
+ timeouts[i+1] = timeouts[i];
+
+ timeouts[index] = &base_timeouts[tn];
+ n_timeouts++;
+ }
+
+ /*
+ * Remove the index'th element from the timeout list.
+ */
+ static void
+ remove_timeout_index(int index)
+ {
+ int i;
+
+ if (index < 0)
+ return;
+
+ for (i = index + 1; i < n_timeouts; i++)
+ timeouts[i-1] = timeouts[i];
+
+ if (n_timeouts > 0 && index < n_timeouts)
+ n_timeouts--;
+ }
+
+ /*
+ * (Re)schedule the next active timeout
+ */
+ static bool
+ schedule_timeout(TimestampTz now)
+ {
+ long secs;
+ int usecs;
+ struct itimerval timeval;
+
+ /* There is no active timeout, do nothing. */
+ if (n_timeouts == 0)
+ return true;
+
+ TimestampDifference(now, timeouts[0]->fin_time,
+ &secs, &usecs);
+
+ /*
+ * It's possible that the difference is less than a microsecond;
+ * ensure we don't cancel, rather than set, the interrupt.
+ */
+ if (secs == 0 && usecs == 0)
+ usecs = 1;
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ timeval.it_value.tv_sec = secs;
+ timeval.it_value.tv_usec = usecs;
+ if (setitimer(ITIMER_REAL, &timeval, NULL))
+ return false;
+ return true;
+ }
+
+ /*****************************************************************************
+ * Init, Destroy and Check functions for different timeouts.
+ *
+ * NB: all Check* functions are run inside a signal handler, so be very wary
+ * about what is done in them or in called routines.
+ *****************************************************************************/
+
+ /*
+ * Common Init and Destroy functions
+ */
+
+ static void
+ InitTimeout(TimeoutName tn, TimestampTz start_time, TimestampTz fin_time)
+ {
+ base_timeouts[tn].indicator = false;
+ base_timeouts[tn].start_time = start_time;
+ base_timeouts[tn].fin_time = fin_time;
+ }
+
+ static void
+ DestroyTimeout(TimeoutName tn, bool keep_indicator)
+ {
+ if (!keep_indicator)
+ base_timeouts[tn].indicator = false;
+ base_timeouts[tn].start_time = 0;
+ base_timeouts[tn].fin_time = 0;
+ }
+
+ /*
+ * Functions to manage deadlock
+ */
+
+ static void
+ InitDeadLock(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(DEADLOCK_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyDeadLock(bool keep_indicator)
+ {
+ DestroyTimeout(DEADLOCK_TIMEOUT, keep_indicator);
+ }
+
+ /*
+ * CheckDeadLock
+ *
+ * Look to see if there's a deadlock; if not, just return.
+ * (But signal ProcSleep to log a message, if log_lock_waits is true.)
+ * If we have a real deadlock, remove ourselves from the lock's wait queue
+ * and signal an error to ProcSleep.
+ */
+ static bool
+ CheckDeadLock(void)
+ {
+ TimestampTz now;
+ int i;
+
+ now = GetCurrentTimestamp();
+
+ /* If our time has not come yet, do nothing. */
+ if (now < base_timeouts[DEADLOCK_TIMEOUT].fin_time)
+ return false;
+
+ /*
+ * Acquire exclusive lock on the entire shared lock data structures. Must
+ * grab LWLocks in partition-number order to avoid LWLock deadlock.
+ *
+ * Note that the deadlock check interrupt had better not be enabled
+ * anywhere that this process itself holds lock partition locks, else this
+ * will wait forever. Also note that LWLockAcquire creates a critical
+ * section, so that this routine cannot be interrupted by cancel/die
+ * interrupts.
+ */
+ for (i = 0; i < NUM_LOCK_PARTITIONS; i++)
+ LWLockAcquire(FirstLockMgrLock + i, LW_EXCLUSIVE);
+
+ /*
+ * Check to see if we've been awoken by anyone in the interim.
+ *
+ * If we have, we can return and resume our transaction -- happy day.
+ * Before we are awoken the process releasing the lock grants it to us so
+ * we know that we don't have to wait anymore.
+ *
+ * We check by looking to see if we've been unlinked from the wait queue.
+ * This is quicker than checking our semaphore's state, since no kernel
+ * call is needed, and it is safe because we hold the lock partition lock.
+ */
+ if (MyProc->links.prev == NULL ||
+ MyProc->links.next == NULL)
+ goto check_done;
+
+ #ifdef LOCK_DEBUG
+ if (Debug_deadlocks)
+ DumpAllLocks();
+ #endif
+
+ /* Run the deadlock check, and set deadlock_state for use by ProcSleep */
+ deadlock_state = DeadLockCheck(MyProc);
+
+ if (deadlock_state == DS_HARD_DEADLOCK)
+ {
+ /*
+ * Oops. We have a deadlock.
+ *
+ * Get this process out of wait state. (Note: we could do this more
+ * efficiently by relying on lockAwaited, but use this coding to
+ * preserve the flexibility to kill some other transaction than the
+ * one detecting the deadlock.)
+ *
+ * RemoveFromWaitQueue sets MyProc->waitStatus to STATUS_ERROR, so
+ * ProcSleep will report an error after we return from the signal
+ * handler.
+ */
+ Assert(MyProc->waitLock != NULL);
+ RemoveFromWaitQueue(MyProc, LockTagHashCode(&(MyProc->waitLock->tag)));
+
+ /*
+ * Unlock my semaphore so that the interrupted ProcSleep() call can
+ * finish.
+ */
+ PGSemaphoreUnlock(&MyProc->sem);
+
+ /*
+ * We're done here. Transaction abort caused by the error that
+ * ProcSleep will raise will cause any other locks we hold to be
+ * released, thus allowing other processes to wake up; we don't need
+ * to do that here. NOTE: an exception is that releasing locks we
+ * hold doesn't consider the possibility of waiters that were blocked
+ * behind us on the lock we just failed to get, and might now be
+ * wakable because we're not in front of them anymore. However,
+ * RemoveFromWaitQueue took care of waking up any such processes.
+ */
+ }
+ else if (log_lock_waits || deadlock_state == DS_BLOCKED_BY_AUTOVACUUM)
+ {
+ /*
+ * Unlock my semaphore so that the interrupted ProcSleep() call can
+ * print the log message (we daren't do it here because we are inside
+ * a signal handler). It will then sleep again until someone releases
+ * the lock.
+ *
+ * If blocked by autovacuum, this wakeup will enable ProcSleep to send
+ * the canceling signal to the autovacuum worker.
+ */
+ PGSemaphoreUnlock(&MyProc->sem);
+ }
+
+ /*
+ * And release locks. We do this in reverse order for two reasons: (1)
+ * Anyone else who needs more than one of the locks will be trying to lock
+ * them in increasing order; we don't want to release the other process
+ * until it can get all the locks it needs. (2) This avoids O(N^2)
+ * behavior inside LWLockRelease.
+ */
+ check_done:
+ for (i = NUM_LOCK_PARTITIONS; --i >= 0;)
+ LWLockRelease(FirstLockMgrLock + i);
+
+ base_timeouts[DEADLOCK_TIMEOUT].indicator = true;
+
+ return true;
+ }
+
+ /*
+ * Functions to manage lock timeout
+ */
+
+ static void
+ InitLockTimeout(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(LOCK_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyLockTimeout(bool keep_indicator)
+ {
+ DestroyTimeout(LOCK_TIMEOUT, keep_indicator);
+ }
+
+ static bool
+ CheckLockTimeout(void)
+ {
+ TimestampTz now;
+
+ now = GetCurrentTimestamp();
+
+ if (now < base_timeouts[LOCK_TIMEOUT].fin_time)
+ return false;
+
+ base_timeouts[LOCK_TIMEOUT].indicator = true;
+ return true;
+ }
+
+ /*
+ * Functions to manage statement timeout
+ */
+
+ static void
+ InitStatementTimeout(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(STATEMENT_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyStatementTimeout(bool keep_indicator)
+ {
+ DestroyTimeout(STATEMENT_TIMEOUT, keep_indicator);
+ }
+
+ static bool
+ CheckStatementTimeout(void)
+ {
+ TimestampTz now;
+
+ now = GetCurrentTimestamp();
+
+ if (now < base_timeouts[STATEMENT_TIMEOUT].fin_time)
+ return false;
+
+ base_timeouts[STATEMENT_TIMEOUT].indicator = true;
+ #ifdef HAVE_SETSID
+ /* try to signal whole process group */
+ kill(-MyProcPid, SIGINT);
+ #endif
+ kill(MyProcPid, SIGINT);
+
+ return true;
+ }
+
+ /*
+ * Functions to manage standby deadlock functions
+ */
+
+ static void
+ InitStandbyDeadLock(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(STANDBY_DEADLOCK_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyStandbyDeadLock(bool keep_indicator)
+ {
+ DestroyTimeout(STANDBY_DEADLOCK_TIMEOUT, keep_indicator);
+ }
+
+ static bool
+ CheckStandbyDeadLock(void)
+ {
+ TimestampTz now;
+
+ now = GetCurrentTimestamp();
+
+ if (now < base_timeouts[STANDBY_DEADLOCK_TIMEOUT].fin_time)
+ return false;
+
+ base_timeouts[STANDBY_DEADLOCK_TIMEOUT].indicator = true;
+ SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
+ return true;
+ }
+
+ /*
+ * Functions to manage standby timeout
+ */
+
+ static void
+ InitStandbyTimeout(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(STANDBY_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyStandbyTimeout(bool keep_indicator)
+ {
+ DestroyTimeout(STANDBY_TIMEOUT, keep_indicator);
+ }
+
+ static bool
+ CheckStandbyTimeout(void)
+ {
+ TimestampTz now;
+
+ now = GetCurrentTimestamp();
+
+ if (now < base_timeouts[STANDBY_TIMEOUT].fin_time)
+ return false;
+
+ base_timeouts[STANDBY_TIMEOUT].indicator = true;
+ SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
+ return true;
+ }
+
+ /*****************************************************************************
+ * Public API
+ *****************************************************************************/
+
+
+ /*
+ * Enable the SIGALRM interrupt to fire after the specified delay
+ *
+ * Delay is given in milliseconds. Caller should be sure a SIGALRM
+ * signal handler is installed before this is called.
+ *
+ * This code properly handles nesting of different timeout alarms.
+ *
+ * Returns TRUE if okay, FALSE on failure to set the timer.
+ */
+ bool
+ enable_timeout(TimeoutName tn, int delayms)
+ {
+ TimestampTz start_time;
+ TimestampTz fin_time;
+ int i;
+
+ Assert(!is_timeout_active(tn));
+
+ if (delayms <= 0)
+ return true;
+
+ start_time = base_timeouts[tn].timeout_start();
+ fin_time = TimestampTzPlusMilliseconds(start_time, delayms);
+
+ /* Find out the index where to insert the new timeout. */
+ for (i = 0; i < n_timeouts; i++)
+ {
+ /*
+ * The new timeout triggers earlier than
+ * a previously added one: insert here.
+ */
+ if (fin_time < timeouts[i]->fin_time)
+ break;
+ /*
+ * The new timeout triggers at the same time
+ * as the previously added one but has greater priority.
+ */
+ if (fin_time == timeouts[i]->fin_time && tn < timeouts[i]->index)
+ break;
+ }
+
+ /*
+ * Initialize the timeout parameters
+ */
+ base_timeouts[tn].timeout_init(start_time, fin_time);
+
+ insert_timeout(tn, i);
+
+ if (i > 0)
+ return true;
+
+ /* If we reach here, okay to set the timer interrupt */
+ if (!schedule_timeout(start_time))
+ return false;
+ return true;
+ }
+
+ /*
+ * Cancel the SIGALRM timer for the specific timeout.
+ * If a timeout is canceled, any other active timeout remains in force.
+ *
+ * Returns TRUE if okay, FALSE on failure to set the timer for
+ * the next timeout source.
+ */
+ bool
+ disable_timeout(TimeoutName tn, bool keep_indicator)
+ {
+ int i;
+
+ /*
+ * Always disable the interrupt if it is active; this avoids being
+ * interrupted by the signal handler and thereby possibly getting
+ * confused.
+ *
+ * We will re-enable the interrupt if necessary in ->check_timeout().
+ */
+ if (n_timeouts > 0)
+ {
+ struct itimerval timeval;
+
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ if (setitimer(ITIMER_REAL, &timeval, NULL))
+ {
+ n_timeouts = 0;
+ for (i = 0; i < TIMEOUT_MAX; i++)
+ base_timeouts[i].timeout_destroy(false);
+
+ return false;
+ }
+ }
+
+ /* Find the timeout and remove from the list. */
+ i = find_active_timeout(tn);
+ remove_timeout_index(i);
+
+ /* Do cleanup. */
+ base_timeouts[tn].timeout_destroy(keep_indicator);
+
+ /*
+ * If the first timeout was removed from the list and there is
+ * at least one active left, reschedule it.
+ */
+ if (i == 0 && n_timeouts > 0)
+ if (!schedule_timeout(GetCurrentTimestamp()))
+ return false;
+
+ return true;
+ }
+
+ /*
+ * Disable SIGALRM and remove all timeouts from the list and
+ * reset the timeout indicators.
+ */
+ bool
+ disable_all_timeouts(bool keep_indicators)
+ {
+ struct itimerval timeval;
+ int i;
+ bool ret;
+
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ ret = (setitimer(ITIMER_REAL, &timeval, NULL) == 0);
+
+ n_timeouts = 0;
+ for (i = 0; i < TIMEOUT_MAX; i++)
+ base_timeouts[i].timeout_destroy(keep_indicators);
+
+ return ret;
+ }
+
+ /*
+ * Return the timeout indicator
+ */
+ bool
+ get_timeout_indicator(TimeoutName tn)
+ {
+ return base_timeouts[tn].indicator;
+ }
+
+ /*
+ * Return the start of the timer for this timeout
+ */
+ TimestampTz
+ get_timeout_start(TimeoutName tn)
+ {
+ return base_timeouts[tn].start_time;
+ }
+
+ /*
+ * Signal handler for SIGALRM
+ *
+ * Process the check for the currently active timeout source and
+ * reschedule the next as needed. To avoid various edge cases,
+ * we must be careful to do nothing when there is nothing to be done.
+ */
+ void
+ handle_sig_alarm(SIGNAL_ARGS)
+ {
+ int save_errno = errno;
+
+ /*
+ * SIGALRM is cause for waking anything waiting on the process latch.
+ * Recovery (the startup process) doesn't have MyProc set so
+ * it can also use this signal handler.
+ */
+ if (MyProc)
+ SetLatch(&MyProc->procLatch);
+
+ if (n_timeouts > 0)
+ {
+ bool ret;
+ bool reschedule = true;
+
+ ret = timeouts[0]->timeout_check();
+
+ /* Shift timeouts if the timeout was triggered */
+ if (ret)
+ {
+ reschedule = timeouts[0]->resched_next;
+ /*
+ * Short circuit disable_timeout(..., true) for the
+ * timeout source that just triggered.
+ */
+ remove_timeout_index(0);
+ }
+
+ if (reschedule)
+ schedule_timeout(GetCurrentTimestamp());
+ else
+ disable_all_timeouts(true);
+ }
+
+ errno = save_errno;
+ }
diff -dcrpN postgresql.orig/src/backend/tcop/postgres.c postgresql/src/backend/tcop/postgres.c
*** postgresql.orig/src/backend/tcop/postgres.c 2012-03-28 10:54:25.195368447 +0200
--- postgresql/src/backend/tcop/postgres.c 2012-04-04 15:07:43.152700939 +0200
***************
*** 64,69 ****
--- 64,70 ----
#include "storage/proc.h"
#include "storage/procsignal.h"
#include "storage/sinval.h"
+ #include "storage/timeout.h"
#include "tcop/fastpath.h"
#include "tcop/pquery.h"
#include "tcop/tcopprot.h"
*************** start_xact_command(void)
*** 2368,2376 ****
/* Set statement timeout running, if any */
/* NB: this mustn't be enabled until we are within an xact */
if (StatementTimeout > 0)
! enable_sig_alarm(StatementTimeout, true);
else
! cancel_from_timeout = false;
xact_started = true;
}
--- 2369,2377 ----
/* Set statement timeout running, if any */
/* NB: this mustn't be enabled until we are within an xact */
if (StatementTimeout > 0)
! enable_timeout(STATEMENT_TIMEOUT, StatementTimeout);
else
! disable_timeout(STATEMENT_TIMEOUT, false);
xact_started = true;
}
*************** finish_xact_command(void)
*** 2382,2388 ****
if (xact_started)
{
/* Cancel any active statement timeout before committing */
! disable_sig_alarm(true);
/* Now commit the command */
ereport(DEBUG3,
--- 2383,2389 ----
if (xact_started)
{
/* Cancel any active statement timeout before committing */
! disable_all_timeouts(false);
/* Now commit the command */
ereport(DEBUG3,
*************** ProcessInterrupts(void)
*** 2863,2869 ****
(errcode(ERRCODE_QUERY_CANCELED),
errmsg("canceling authentication due to timeout")));
}
! if (cancel_from_timeout)
{
ImmediateInterruptOK = false; /* not idle anymore */
DisableNotifyInterrupt();
--- 2864,2870 ----
(errcode(ERRCODE_QUERY_CANCELED),
errmsg("canceling authentication due to timeout")));
}
! if (get_timeout_indicator(STATEMENT_TIMEOUT))
{
ImmediateInterruptOK = false; /* not idle anymore */
DisableNotifyInterrupt();
*************** PostgresMain(int argc, char *argv[], con
*** 3728,3737 ****
/*
* Forget any pending QueryCancel request, since we're returning to
! * the idle loop anyway, and cancel the statement timer if running.
*/
QueryCancelPending = false;
! disable_sig_alarm(true);
QueryCancelPending = false; /* again in case timeout occurred */
/*
--- 3729,3738 ----
/*
* Forget any pending QueryCancel request, since we're returning to
! * the idle loop anyway, and cancel the timer if running.
*/
QueryCancelPending = false;
! disable_all_timeouts(false);
QueryCancelPending = false; /* again in case timeout occurred */
/*
diff -dcrpN postgresql.orig/src/backend/utils/adt/lockfuncs.c postgresql/src/backend/utils/adt/lockfuncs.c
*** postgresql.orig/src/backend/utils/adt/lockfuncs.c 2012-01-02 12:35:11.533185089 +0100
--- postgresql/src/backend/utils/adt/lockfuncs.c 2012-04-04 11:24:59.796965393 +0200
*************** pg_advisory_lock_int8(PG_FUNCTION_ARGS)
*** 421,427 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ExclusiveLock, true, false);
PG_RETURN_VOID();
}
--- 421,431 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ExclusiveLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_int8(PG_FUNCTION_A
*** 438,444 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
PG_RETURN_VOID();
}
--- 442,452 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_shared_int8(PG_FUNCTION
*** 454,460 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ShareLock, true, false);
PG_RETURN_VOID();
}
--- 462,472 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ShareLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_shared_int8(PG_FUN
*** 471,477 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ShareLock, false, false);
PG_RETURN_VOID();
}
--- 483,493 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_int4(PG_FUNCTION_ARGS)
*** 604,610 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ExclusiveLock, true, false);
PG_RETURN_VOID();
}
--- 620,630 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ExclusiveLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_int4(PG_FUNCTION_A
*** 622,628 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
PG_RETURN_VOID();
}
--- 642,652 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_shared_int4(PG_FUNCTION
*** 639,645 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ShareLock, true, false);
PG_RETURN_VOID();
}
--- 663,673 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ShareLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_shared_int4(PG_FUN
*** 657,663 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ShareLock, false, false);
PG_RETURN_VOID();
}
--- 685,695 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
diff -dcrpN postgresql.orig/src/backend/utils/init/postinit.c postgresql/src/backend/utils/init/postinit.c
*** postgresql.orig/src/backend/utils/init/postinit.c 2012-01-20 13:59:24.913240794 +0100
--- postgresql/src/backend/utils/init/postinit.c 2012-04-04 15:08:31.671944981 +0200
***************
*** 41,51 ****
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lmgr.h"
- #include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "storage/smgr.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
#include "utils/fmgroids.h"
--- 41,51 ----
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lmgr.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "storage/smgr.h"
+ #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
#include "utils/fmgroids.h"
*************** PerformAuthentication(Port *port)
*** 204,210 ****
* during authentication. Since we're inside a transaction and might do
* database access, we have to use the statement_timeout infrastructure.
*/
! if (!enable_sig_alarm(AuthenticationTimeout * 1000, true))
elog(FATAL, "could not set timer for authorization timeout");
/*
--- 204,210 ----
* during authentication. Since we're inside a transaction and might do
* database access, we have to use the statement_timeout infrastructure.
*/
! if (!enable_timeout(STATEMENT_TIMEOUT, AuthenticationTimeout * 1000))
elog(FATAL, "could not set timer for authorization timeout");
/*
*************** PerformAuthentication(Port *port)
*** 215,221 ****
/*
* Done with authentication. Disable the timeout, and log if needed.
*/
! if (!disable_sig_alarm(true))
elog(FATAL, "could not disable timer for authorization timeout");
if (Log_connections)
--- 215,221 ----
/*
* Done with authentication. Disable the timeout, and log if needed.
*/
! if (!disable_timeout(STATEMENT_TIMEOUT, false))
elog(FATAL, "could not disable timer for authorization timeout");
if (Log_connections)
diff -dcrpN postgresql.orig/src/backend/utils/misc/guc.c postgresql/src/backend/utils/misc/guc.c
*** postgresql.orig/src/backend/utils/misc/guc.c 2012-03-28 10:54:25.196368452 +0200
--- postgresql/src/backend/utils/misc/guc.c 2012-04-04 15:09:07.395124656 +0200
***************
*** 63,68 ****
--- 63,69 ----
#include "storage/standby.h"
#include "storage/fd.h"
#include "storage/predicate.h"
+ #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "tsearch/ts_cache.h"
#include "utils/builtins.h"
*************** static struct config_int ConfigureNamesI
*** 1858,1863 ****
--- 1859,1875 ----
0, 0, INT_MAX,
NULL, NULL, NULL
},
+
+ {
+ {"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,
diff -dcrpN postgresql.orig/src/backend/utils/misc/postgresql.conf.sample postgresql/src/backend/utils/misc/postgresql.conf.sample
*** postgresql.orig/src/backend/utils/misc/postgresql.conf.sample 2012-03-28 10:54:25.197368458 +0200
--- postgresql/src/backend/utils/misc/postgresql.conf.sample 2012-04-03 15:29:03.725060295 +0200
***************
*** 527,532 ****
--- 527,535 ----
#------------------------------------------------------------------------------
#deadlock_timeout = 1s
+ #lock_timeout = 0 # timeout value for heavy-weight locks
+ # taken by statements. 0 disables timeout
+ # unit in milliseconds, default is 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 -dcrpN postgresql.orig/src/include/storage/pg_sema.h postgresql/src/include/storage/pg_sema.h
*** postgresql.orig/src/include/storage/pg_sema.h 2012-01-02 12:35:11.640178818 +0100
--- postgresql/src/include/storage/pg_sema.h 2012-04-03 15:30:52.716621135 +0200
*************** extern void PGSemaphoreUnlock(PGSemaphor
*** 80,83 ****
--- 80,86 ----
/* 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 */
+ extern void PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK);
+
#endif /* PG_SEMA_H */
diff -dcrpN postgresql.orig/src/include/storage/proc.h postgresql/src/include/storage/proc.h
*** postgresql.orig/src/include/storage/proc.h 2012-01-31 09:43:43.936392561 +0100
--- postgresql/src/include/storage/proc.h 2012-04-04 15:40:27.595781818 +0200
*************** extern PGPROC *PreparedXactProcs;
*** 215,227 ****
/* configurable options */
- extern int DeadlockTimeout;
- extern int StatementTimeout;
extern bool log_lock_waits;
- extern volatile bool cancel_from_timeout;
-
-
/*
* Function Prototypes
*/
--- 215,222 ----
*************** extern void LockWaitCancel(void);
*** 249,261 ****
extern void ProcWaitForSignal(void);
extern void ProcSendSignal(int pid);
- extern bool enable_sig_alarm(int delayms, bool is_statement_timeout);
- extern bool disable_sig_alarm(bool is_statement_timeout);
- extern void handle_sig_alarm(SIGNAL_ARGS);
-
- extern bool enable_standby_sig_alarm(TimestampTz now,
- TimestampTz fin_time, bool deadlock_only);
- extern bool disable_standby_sig_alarm(void);
- extern void handle_standby_sig_alarm(SIGNAL_ARGS);
-
#endif /* PROC_H */
--- 244,247 ----
diff -dcrpN postgresql.orig/src/include/storage/timeout.h postgresql/src/include/storage/timeout.h
*** postgresql.orig/src/include/storage/timeout.h 1970-01-01 01:00:00.000000000 +0100
--- postgresql/src/include/storage/timeout.h 2012-04-04 15:02:21.255081715 +0200
***************
*** 0 ****
--- 1,40 ----
+ /*-------------------------------------------------------------------------
+ *
+ * timeout.h
+ * SIGALRM timeout API
+ *
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/storage/timeout.h
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef _TIMEOUT_H_
+ #define _TIMEOUT_H_
+
+ #include "datatype/timestamp.h"
+
+ /* configurable options */
+ extern int DeadlockTimeout;
+ extern int StatementTimeout;
+ extern int LockTimeout;
+
+ typedef enum TimeoutName {
+ DEADLOCK_TIMEOUT,
+ LOCK_TIMEOUT,
+ STATEMENT_TIMEOUT,
+ STANDBY_DEADLOCK_TIMEOUT,
+ STANDBY_TIMEOUT,
+ TIMEOUT_MAX
+ } TimeoutName;
+
+ extern bool enable_timeout(TimeoutName tn, int delayms);
+ extern bool disable_timeout(TimeoutName tn, bool keep_indicator);
+ extern bool disable_all_timeouts(bool keep_indicators);
+ extern bool get_timeout_indicator(TimeoutName tn);
+ extern TimestampTz get_timeout_start(TimeoutName tn);
+ extern void handle_sig_alarm(SIGNAL_ARGS);
+
+ #endif /* _TIMEOUT_H_ */
diff -dcrpN postgresql.orig/src/test/regress/expected/prepared_xacts.out postgresql/src/test/regress/expected/prepared_xacts.out
*** postgresql.orig/src/test/regress/expected/prepared_xacts.out 2011-08-16 09:39:01.117194748 +0200
--- postgresql/src/test/regress/expected/prepared_xacts.out 2012-04-04 12:22:18.719786919 +0200
*************** set statement_timeout to 2000;
*** 198,203 ****
--- 198,210 ----
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: could not obtain lock on relation "pxtest3"
+ LINE 1: SELECT * FROM pxtest3;
+ ^
+ reset lock_timeout;
-- Disconnect, we will continue testing in a different backend
\c -
-- There should still be two prepared transactions
*************** set statement_timeout to 2000;
*** 213,218 ****
--- 220,232 ----
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: could not obtain lock on relation "pxtest3"
+ LINE 1: SELECT * FROM pxtest3;
+ ^
+ reset lock_timeout;
-- Commit table creation
COMMIT PREPARED 'regress-one';
\d pxtest2
diff -dcrpN postgresql.orig/src/test/regress/sql/prepared_xacts.sql postgresql/src/test/regress/sql/prepared_xacts.sql
*** postgresql.orig/src/test/regress/sql/prepared_xacts.sql 2011-07-18 15:42:00.117371861 +0200
--- postgresql/src/test/regress/sql/prepared_xacts.sql 2012-04-04 12:20:31.456241386 +0200
*************** set statement_timeout to 2000;
*** 126,131 ****
--- 126,136 ----
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 -
*************** set statement_timeout to 2000;
*** 137,142 ****
--- 142,152 ----
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
2012-04-04 16:22 keltezéssel, Boszormenyi Zoltan írta:
2012-04-04 15:17 keltezéssel, Boszormenyi Zoltan írta:
Hi,
2012-04-04 12:30 keltezéssel, Boszormenyi Zoltan írta:
Hi,
attached is a patch to implement a framework to simplify and
correctly nest multiplexing more than two timeout sources
into the same SIGALRM signal handler.The framework is using a new internal API for timeouts:
bool enable_timeout(TimeoutName tn, int delayms);
bool disable_timeout(TimeoutName tn, bool keep_indicator);
bool disable_all_timeouts(bool keep_indicators);A timeout source has these features to allow different initialization,
cleanup and check functions and rescheduling:typedef void (*timeout_init)(TimestampTz, TimestampTz);
typedef void (*timeout_destroy)(bool);
typedef bool (*timeout_check)(void);
typedef TimestampTz (*timeout_start)(void);typedef struct {
TimeoutName index;
bool resched_next;
timeout_init timeout_init;
timeout_destroy timeout_destroy;
timeout_check timeout_check;
timeout_start timeout_start;
TimestampTz fin_time;
} timeout_params;This makes it possible to differentiate between the standby and
statement timeouts, regular deadlock and standby deadlock using
the same signal handler function.And finally, this makes it possible to implement the lock_timeout
feature that we at Cybertec implemented more than 2 years ago.The patch also adds two new tests into prepared_xacts.sql to trigger
the lock_timeout instead of statement_timeout.Documentation and extensive comments are included.
Second version. Every timeout-related functions are now in a separate
source file, src/backend/storage/timeout.c with accessor functions.
There are no related global variables anymore, only the GUCs.3rd and (for now) final version.
I lied. This is the final one. I fixed a typo in the documentation
and replaced timeout_start_time (previously static to proc.c)
with get_timeout_start(DEADLOCK_TIMEOUT). This one should
have happened in the second version.
Tidied comments, the time checks in
Check*() functions and function order in timeout.c.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/
--
----------------------------------
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/
Attachments:
pg92-common-lock-framework-lock_timeout-v4.patchtext/x-patch; name=pg92-common-lock-framework-lock_timeout-v4.patchDownload
diff -dcrpN postgresql.orig/doc/src/sgml/config.sgml postgresql/doc/src/sgml/config.sgml
*** postgresql.orig/doc/src/sgml/config.sgml 2012-03-28 10:54:25.177368339 +0200
--- postgresql/doc/src/sgml/config.sgml 2012-04-04 17:11:20.197061781 +0200
*************** COPY postgres_log FROM '/full/path/to/lo
*** 4944,4950 ****
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.
</para>
<para>
--- 4944,4953 ----
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. 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>
*************** COPY postgres_log FROM '/full/path/to/lo
*** 4955,4960 ****
--- 4958,4991 ----
</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 has to wait more
+ than the specified number of milliseconds, starting from the time the
+ command arrives at the server from the client. If the statement involves
+ more than one such lock, the timeout applies to every one of them.
+ 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 to acquire.
+ 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 off the limitation.
+ </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 -dcrpN postgresql.orig/doc/src/sgml/ref/lock.sgml postgresql/doc/src/sgml/ref/lock.sgml
*** postgresql.orig/doc/src/sgml/ref/lock.sgml 2011-08-07 11:29:16.004256905 +0200
--- postgresql/doc/src/sgml/ref/lock.sgml 2012-04-03 15:54:26.543224868 +0200
*************** LOCK [ TABLE ] [ ONLY ] <replaceable cla
*** 39,46 ****
<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.)
</para>
--- 39,49 ----
<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. 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 -dcrpN postgresql.orig/doc/src/sgml/ref/select.sgml postgresql/doc/src/sgml/ref/select.sgml
*** postgresql.orig/doc/src/sgml/ref/select.sgml 2012-01-29 20:33:53.893650856 +0100
--- postgresql/doc/src/sgml/ref/select.sgml 2012-04-04 10:30:35.035960738 +0200
*************** FOR SHARE [ OF <replaceable class="param
*** 1199,1204 ****
--- 1199,1211 ----
</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 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 -dcrpN postgresql.orig/src/backend/port/posix_sema.c postgresql/src/backend/port/posix_sema.c
*** postgresql.orig/src/backend/port/posix_sema.c 2012-01-02 12:35:11.502186906 +0100
--- postgresql/src/backend/port/posix_sema.c 2012-04-04 15:01:25.781802645 +0200
***************
*** 24,29 ****
--- 24,30 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/timeout.h"
#ifdef USE_NAMED_POSIX_SEMAPHORES
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 313,315 ****
--- 314,341 ----
return true;
}
+
+ /*
+ * PGSemaphoreTimedLock
+ *
+ * Lock a semaphore (decrement count), blocking if count would be < 0
+ * Return if lock_timeout expired
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ int errStatus;
+
+ do
+ {
+ ImmediateInterruptOK = interruptOK;
+ CHECK_FOR_INTERRUPTS();
+ errStatus = sem_wait(PG_SEM_REF(sema));
+ ImmediateInterruptOK = false;
+ } while (errStatus < 0 && errno == EINTR && !get_timeout_indicator(LOCK_TIMEOUT));
+
+ if (get_timeout_indicator(LOCK_TIMEOUT))
+ return;
+ if (errStatus < 0)
+ elog(FATAL, "sem_wait failed: %m");
+ }
diff -dcrpN postgresql.orig/src/backend/port/sysv_sema.c postgresql/src/backend/port/sysv_sema.c
*** postgresql.orig/src/backend/port/sysv_sema.c 2012-01-02 12:35:11.503186847 +0100
--- postgresql/src/backend/port/sysv_sema.c 2012-04-04 15:01:16.902757977 +0200
***************
*** 30,35 ****
--- 30,36 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/timeout.h"
#ifndef HAVE_UNION_SEMUN
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 495,497 ****
--- 496,528 ----
return true;
}
+
+ /*
+ * PGSemaphoreTimedLock
+ *
+ * Lock a semaphore (decrement count), blocking if count would be < 0
+ * Return if lock_timeout expired
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ int errStatus;
+ 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;
+ } while (errStatus < 0 && errno == EINTR && !get_timeout_indicator(LOCK_TIMEOUT));
+
+ if (get_timeout_indicator(LOCK_TIMEOUT))
+ return;
+ if (errStatus < 0)
+ elog(FATAL, "semop(id=%d) failed: %m", sema->semId);
+ }
diff -dcrpN postgresql.orig/src/backend/port/win32_sema.c postgresql/src/backend/port/win32_sema.c
*** postgresql.orig/src/backend/port/win32_sema.c 2012-01-02 12:35:11.504186789 +0100
--- postgresql/src/backend/port/win32_sema.c 2012-04-04 15:01:35.197850015 +0200
***************
*** 16,21 ****
--- 16,22 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/timeout.h"
static HANDLE *mySemSet; /* IDs of sema sets acquired so far */
static int numSems; /* number of sema sets acquired so far */
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 205,207 ****
--- 206,263 ----
/* 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.
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ DWORD ret;
+ HANDLE wh[2];
+
+ wh[0] = *sema;
+ wh[1] = pgwin32_signal_event;
+
+ /*
+ * 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();
+
+ errno = 0;
+ ret = WaitForMultipleObjectsEx(2, wh, FALSE, INFINITE, TRUE);
+
+ if (ret == WAIT_OBJECT_0)
+ {
+ /* We got it! */
+ return;
+ }
+ else if (ret == WAIT_OBJECT_0 + 1)
+ {
+ /* Signal event is set - we have a signal to deliver */
+ pgwin32_dispatch_queued_signals();
+ errno = EINTR;
+ }
+ else
+ /* Otherwise we are in trouble */
+ errno = EIDRM;
+
+ ImmediateInterruptOK = false;
+ } while (errno == EINTR && !get_timeout_indicator(LOCK_TIMEOUT));
+
+ if (get_timeout_indicator(LOCK_TIMEOUT))
+ return;
+ if (errno != 0)
+ ereport(FATAL,
+ (errmsg("could not lock semaphore: error code %d", (int) GetLastError())));
+ }
diff -dcrpN postgresql.orig/src/backend/postmaster/autovacuum.c postgresql/src/backend/postmaster/autovacuum.c
*** postgresql.orig/src/backend/postmaster/autovacuum.c 2012-01-27 10:29:06.483649830 +0100
--- postgresql/src/backend/postmaster/autovacuum.c 2012-04-04 15:03:20.447379486 +0200
***************
*** 86,94 ****
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
- #include "storage/proc.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "tcop/tcopprot.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
--- 86,94 ----
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
+ #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
*************** AutoVacLauncherMain(int argc, char *argv
*** 484,490 ****
/* Forget any pending QueryCancel request */
QueryCancelPending = false;
! disable_sig_alarm(true);
QueryCancelPending = false; /* again in case timeout occurred */
/* Report the error to the server log */
--- 484,490 ----
/* Forget any pending QueryCancel request */
QueryCancelPending = false;
! disable_all_timeouts(false);
QueryCancelPending = false; /* again in case timeout occurred */
/* Report the error to the server log */
diff -dcrpN postgresql.orig/src/backend/postmaster/postmaster.c postgresql/src/backend/postmaster/postmaster.c
*** postgresql.orig/src/backend/postmaster/postmaster.c 2012-03-29 08:36:59.775468454 +0200
--- postgresql/src/backend/postmaster/postmaster.c 2012-04-04 15:03:54.927552935 +0200
***************
*** 112,118 ****
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
! #include "storage/proc.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/datetime.h"
--- 112,118 ----
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
! #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/datetime.h"
*************** BackendInitialize(Port *port)
*** 3466,3474 ****
* Ready to begin client interaction. We will give up and exit(1) after a
* time delay, so that a broken client can't hog a connection
* indefinitely. PreAuthDelay and any DNS interactions above don't count
! * against the time limit.
*/
! if (!enable_sig_alarm(AuthenticationTimeout * 1000, false))
elog(FATAL, "could not set timer for startup packet timeout");
/*
--- 3466,3474 ----
* Ready to begin client interaction. We will give up and exit(1) after a
* time delay, so that a broken client can't hog a connection
* indefinitely. PreAuthDelay and any DNS interactions above don't count
! * against the time limit. Use the deadlock timeout interface.
*/
! if (!enable_timeout(DEADLOCK_TIMEOUT, AuthenticationTimeout * 1000))
elog(FATAL, "could not set timer for startup packet timeout");
/*
*************** BackendInitialize(Port *port)
*** 3506,3512 ****
/*
* Disable the timeout, and prevent SIGTERM/SIGQUIT again.
*/
! if (!disable_sig_alarm(false))
elog(FATAL, "could not disable timer for startup packet timeout");
PG_SETMASK(&BlockSig);
}
--- 3506,3512 ----
/*
* Disable the timeout, and prevent SIGTERM/SIGQUIT again.
*/
! if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for startup packet timeout");
PG_SETMASK(&BlockSig);
}
diff -dcrpN postgresql.orig/src/backend/postmaster/startup.c postgresql/src/backend/postmaster/startup.c
*** postgresql.orig/src/backend/postmaster/startup.c 2012-01-02 12:35:11.508186555 +0100
--- postgresql/src/backend/postmaster/startup.c 2012-04-04 15:04:32.902743966 +0200
***************
*** 27,33 ****
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
! #include "storage/proc.h"
#include "utils/guc.h"
--- 27,33 ----
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
! #include "storage/timeout.h"
#include "utils/guc.h"
*************** StartupProcessMain(void)
*** 195,202 ****
pqsignal(SIGTERM, StartupProcShutdownHandler); /* request shutdown */
pqsignal(SIGQUIT, startupproc_quickdie); /* hard crash time */
if (EnableHotStandby)
! pqsignal(SIGALRM, handle_standby_sig_alarm); /* ignored unless
! * InHotStandby */
else
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
--- 195,202 ----
pqsignal(SIGTERM, StartupProcShutdownHandler); /* request shutdown */
pqsignal(SIGQUIT, startupproc_quickdie); /* hard crash time */
if (EnableHotStandby)
! pqsignal(SIGALRM, handle_sig_alarm); /* ignored unless
! * InHotStandby */
else
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
diff -dcrpN postgresql.orig/src/backend/storage/ipc/standby.c postgresql/src/backend/storage/ipc/standby.c
*** postgresql.orig/src/backend/storage/ipc/standby.c 2012-02-05 12:28:36.003281960 +0100
--- postgresql/src/backend/storage/ipc/standby.c 2012-04-04 15:05:29.887030606 +0200
***************
*** 23,32 ****
#include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
- #include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/sinvaladt.h"
#include "storage/standby.h"
#include "utils/ps_status.h"
#include "utils/timestamp.h"
--- 23,32 ----
#include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
#include "storage/procarray.h"
#include "storage/sinvaladt.h"
#include "storage/standby.h"
+ #include "storage/timeout.h"
#include "utils/ps_status.h"
#include "utils/timestamp.h"
*************** ResolveRecoveryConflictWithLock(Oid dbOi
*** 394,400 ****
void
ResolveRecoveryConflictWithBufferPin(void)
{
- bool sig_alarm_enabled = false;
TimestampTz ltime;
TimestampTz now;
--- 394,399 ----
*************** ResolveRecoveryConflictWithBufferPin(voi
*** 409,417 ****
* We're willing to wait forever for conflicts, so set timeout for
* deadlock check (only)
*/
! if (enable_standby_sig_alarm(now, now, true))
! sig_alarm_enabled = true;
! else
elog(FATAL, "could not set timer for process wakeup");
}
else if (now >= ltime)
--- 408,414 ----
* We're willing to wait forever for conflicts, so set timeout for
* deadlock check (only)
*/
! if (!enable_timeout(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout))
elog(FATAL, "could not set timer for process wakeup");
}
else if (now >= ltime)
*************** ResolveRecoveryConflictWithBufferPin(voi
*** 423,446 ****
}
else
{
/*
* Wake up at ltime, and check for deadlocks as well if we will be
* waiting longer than deadlock_timeout
*/
! if (enable_standby_sig_alarm(now, ltime, false))
! sig_alarm_enabled = true;
! else
elog(FATAL, "could not set timer for process wakeup");
}
/* Wait to be signaled by UnpinBuffer() */
ProcWaitForSignal();
! if (sig_alarm_enabled)
! {
! if (!disable_standby_sig_alarm())
! elog(FATAL, "could not disable timer for process wakeup");
! }
}
void
--- 420,447 ----
}
else
{
+ long secs, msecs;
+ int usecs;
+
/*
* Wake up at ltime, and check for deadlocks as well if we will be
* waiting longer than deadlock_timeout
*/
! if (!enable_timeout(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout))
! elog(FATAL, "could not set timer for process wakeup");
!
! TimestampDifference(now, ltime, &secs, &usecs);
! msecs = secs * 1000 + usecs / 1000;
!
! if (!enable_timeout(STANDBY_TIMEOUT, msecs))
elog(FATAL, "could not set timer for process wakeup");
}
/* Wait to be signaled by UnpinBuffer() */
ProcWaitForSignal();
! if (!disable_all_timeouts(false))
! elog(FATAL, "could not disable timer for process wakeup");
}
void
diff -dcrpN postgresql.orig/src/backend/storage/lmgr/lmgr.c postgresql/src/backend/storage/lmgr/lmgr.c
*** postgresql.orig/src/backend/storage/lmgr/lmgr.c 2012-01-02 12:35:11.517186027 +0100
--- postgresql/src/backend/storage/lmgr/lmgr.c 2012-04-03 15:55:39.981617113 +0200
***************
*** 19,26 ****
--- 19,29 ----
#include "access/transam.h"
#include "access/xact.h"
#include "catalog/catalog.h"
+ #include "catalog/pg_database.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
+ #include "utils/lsyscache.h"
+ #include "storage/proc.h"
#include "storage/procarray.h"
#include "utils/inval.h"
*************** LockRelationOid(Oid relid, LOCKMODE lock
*** 78,83 ****
--- 81,101 ----
res = LockAcquire(&tag, lockmode, false, false);
+ if (res == LOCKACQUIRE_NOT_AVAIL)
+ {
+ char *relname = get_rel_name(relid);
+ if (relname)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ relname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation with OID %u",
+ relid)));
+ }
+
/*
* Now that we have the lock, check for invalidation messages, so that we
* will update or flush any stale relcache entry before we try to use it.
*************** LockRelation(Relation relation, LOCKMODE
*** 174,179 ****
--- 192,203 ----
res = LockAcquire(&tag, lockmode, false, false);
+ if (res == LOCKACQUIRE_NOT_AVAIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ RelationGetRelationName(relation))));
+
/*
* Now that we have the lock, check for invalidation messages; see notes
* in LockRelationOid.
*************** LockRelationIdForSession(LockRelId *reli
*** 251,257 ****
SET_LOCKTAG_RELATION(tag, relid->dbId, relid->relId);
! (void) LockAcquire(&tag, lockmode, true, false);
}
/*
--- 275,294 ----
SET_LOCKTAG_RELATION(tag, relid->dbId, relid->relId);
! if (LockAcquire(&tag, lockmode, true, false) == LOCKACQUIRE_NOT_AVAIL)
! {
! char *relname = get_rel_name(relid->relId);
! if (relname)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on relation \"%s\"",
! relname)));
! else
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on relation with OID %u",
! relid->relId)));
! }
}
/*
*************** LockRelationForExtension(Relation relati
*** 286,292 ****
relation->rd_lockInfo.lockRelId.dbId,
relation->rd_lockInfo.lockRelId.relId);
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 323,333 ----
relation->rd_lockInfo.lockRelId.dbId,
relation->rd_lockInfo.lockRelId.relId);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on index \"%s\"",
! RelationGetRelationName(relation))));
}
/*
*************** LockPage(Relation relation, BlockNumber
*** 320,326 ****
relation->rd_lockInfo.lockRelId.relId,
blkno);
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 361,371 ----
relation->rd_lockInfo.lockRelId.relId,
blkno);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on page %u of relation \"%s\"",
! blkno, RelationGetRelationName(relation))));
}
/*
*************** LockTuple(Relation relation, ItemPointer
*** 376,382 ****
ItemPointerGetBlockNumber(tid),
ItemPointerGetOffsetNumber(tid));
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 421,431 ----
ItemPointerGetBlockNumber(tid),
ItemPointerGetOffsetNumber(tid));
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on row in relation \"%s\"",
! RelationGetRelationName(relation))));
}
/*
*************** XactLockTableInsert(TransactionId xid)
*** 430,436 ****
SET_LOCKTAG_TRANSACTION(tag, xid);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
}
/*
--- 479,488 ----
SET_LOCKTAG_TRANSACTION(tag, xid);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on transaction with ID %u", xid)));
}
/*
*************** XactLockTableWait(TransactionId xid)
*** 474,480 ****
SET_LOCKTAG_TRANSACTION(tag, xid);
! (void) LockAcquire(&tag, ShareLock, false, false);
LockRelease(&tag, ShareLock, false);
--- 526,535 ----
SET_LOCKTAG_TRANSACTION(tag, xid);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on transaction with ID %u", xid)));
LockRelease(&tag, ShareLock, false);
*************** LockDatabaseObject(Oid classid, Oid obji
*** 535,541 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, false, false);
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
--- 590,600 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
*************** LockSharedObject(Oid classid, Oid objid,
*** 576,582 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, false, false);
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
--- 635,645 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
*************** LockSharedObjectForSession(Oid classid,
*** 618,624 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, true, false);
}
/*
--- 681,702 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, true, false) == LOCKACQUIRE_NOT_AVAIL)
! switch(classid)
! {
! case DatabaseRelationId:
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on database with ID %u",
! objid)));
! break;
! default:
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
! break;
! }
}
/*
diff -dcrpN postgresql.orig/src/backend/storage/lmgr/lock.c postgresql/src/backend/storage/lmgr/lock.c
*** postgresql.orig/src/backend/storage/lmgr/lock.c 2012-01-25 06:45:42.322747976 +0100
--- postgresql/src/backend/storage/lmgr/lock.c 2012-04-04 10:47:12.072945214 +0200
*************** static void RemoveLocalLock(LOCALLOCK *l
*** 338,344 ****
static PROCLOCK *SetupLockInTable(LockMethod lockMethodTable, PGPROC *proc,
const LOCKTAG *locktag, uint32 hashcode, LOCKMODE lockmode);
static void GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner);
! static void WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner);
static void ReleaseLockForOwner(LOCALLOCK *locallock, ResourceOwner owner);
static bool UnGrantLock(LOCK *lock, LOCKMODE lockmode,
PROCLOCK *proclock, LockMethod lockMethodTable);
--- 338,344 ----
static PROCLOCK *SetupLockInTable(LockMethod lockMethodTable, PGPROC *proc,
const LOCKTAG *locktag, uint32 hashcode, LOCKMODE lockmode);
static void GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner);
! static int WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner);
static void ReleaseLockForOwner(LOCALLOCK *locallock, ResourceOwner owner);
static bool UnGrantLock(LOCK *lock, LOCKMODE lockmode,
PROCLOCK *proclock, LockMethod lockMethodTable);
*************** ProcLockHashCode(const PROCLOCKTAG *proc
*** 544,550 ****
* dontWait: if true, don't wait to acquire lock
*
* Returns one of:
! * LOCKACQUIRE_NOT_AVAIL lock not available, and dontWait=true
* LOCKACQUIRE_OK lock successfully acquired
* LOCKACQUIRE_ALREADY_HELD incremented count for lock already held
*
--- 544,550 ----
* dontWait: if true, don't wait to acquire lock
*
* Returns one of:
! * LOCKACQUIRE_NOT_AVAIL lock not available, either dontWait=true or timeout
* LOCKACQUIRE_OK lock successfully acquired
* LOCKACQUIRE_ALREADY_HELD incremented count for lock already held
*
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 863,869 ****
locktag->locktag_type,
lockmode);
! WaitOnLock(locallock, owner);
TRACE_POSTGRESQL_LOCK_WAIT_DONE(locktag->locktag_field1,
locktag->locktag_field2,
--- 863,869 ----
locktag->locktag_type,
lockmode);
! status = WaitOnLock(locallock, owner);
TRACE_POSTGRESQL_LOCK_WAIT_DONE(locktag->locktag_field1,
locktag->locktag_field2,
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 878,897 ****
* done when the lock was granted to us --- see notes in WaitOnLock.
*/
! /*
! * Check the proclock entry status, in case something in the ipc
! * communication doesn't work correctly.
! */
! if (!(proclock->holdMask & LOCKBIT_ON(lockmode)))
{
! PROCLOCK_PRINT("LockAcquire: INCONSISTENT", proclock);
! LOCK_PRINT("LockAcquire: INCONSISTENT", lock, lockmode);
! /* Should we retry ? */
! LWLockRelease(partitionLock);
! elog(ERROR, "LockAcquire failed");
}
- PROCLOCK_PRINT("LockAcquire: granted", proclock);
- LOCK_PRINT("LockAcquire: granted", lock, lockmode);
}
LWLockRelease(partitionLock);
--- 878,909 ----
* done when the lock was granted to us --- see notes in WaitOnLock.
*/
! switch (status)
{
! case STATUS_OK:
! /*
! * Check the proclock entry status, in case something in the ipc
! * communication doesn't work correctly.
! */
! if (!(proclock->holdMask & LOCKBIT_ON(lockmode)))
! {
! PROCLOCK_PRINT("LockAcquire: INCONSISTENT", proclock);
! LOCK_PRINT("LockAcquire: INCONSISTENT", lock, lockmode);
! /* Should we retry ? */
! LWLockRelease(partitionLock);
! elog(ERROR, "LockAcquire failed");
! }
! PROCLOCK_PRINT("LockAcquire: granted", proclock);
! LOCK_PRINT("LockAcquire: granted", lock, lockmode);
! break;
! case STATUS_WAITING:
! PROCLOCK_PRINT("LockAcquire: timed out", proclock);
! LOCK_PRINT("LockAcquire: timed out", lock, lockmode);
! break;
! default:
! elog(ERROR, "LockAcquire invalid status");
! break;
}
}
LWLockRelease(partitionLock);
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 911,917 ****
locktag->locktag_field2);
}
! return LOCKACQUIRE_OK;
}
/*
--- 923,929 ----
locktag->locktag_field2);
}
! return (status == STATUS_OK ? LOCKACQUIRE_OK : LOCKACQUIRE_NOT_AVAIL);
}
/*
*************** GrantAwaitedLock(void)
*** 1371,1384 ****
* Caller must have set MyProc->heldLocks to reflect locks already held
* on the lockable object by this process.
*
* The appropriate partition lock must be held at entry.
*/
! static void
WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner)
{
LOCKMETHODID lockmethodid = LOCALLOCK_LOCKMETHOD(*locallock);
LockMethod lockMethodTable = LockMethods[lockmethodid];
char *volatile new_status = NULL;
LOCK_PRINT("WaitOnLock: sleeping on lock",
locallock->lock, locallock->tag.mode);
--- 1383,1402 ----
* Caller must have set MyProc->heldLocks to reflect locks already held
* on the lockable object by this process.
*
+ * Result: returns value of ProcSleep()
+ * STATUS_OK if we acquired the lock
+ * STATUS_ERROR if not (deadlock)
+ * STATUS_WAITING if not (timeout)
+ *
* The appropriate partition lock must be held at entry.
*/
! static int
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);
*************** WaitOnLock(LOCALLOCK *locallock, Resourc
*** 1420,1427 ****
*/
PG_TRY();
{
! if (ProcSleep(locallock, lockMethodTable) != STATUS_OK)
{
/*
* We failed as a result of a deadlock, see CheckDeadLock(). Quit
* now.
--- 1438,1450 ----
*/
PG_TRY();
{
! wait_status = ProcSleep(locallock, lockMethodTable);
! switch (wait_status)
{
+ case STATUS_OK:
+ case STATUS_WAITING:
+ break;
+ default:
/*
* We failed as a result of a deadlock, see CheckDeadLock(). Quit
* now.
*************** WaitOnLock(LOCALLOCK *locallock, Resourc
*** 1466,1473 ****
pfree(new_status);
}
! LOCK_PRINT("WaitOnLock: wakeup on lock",
locallock->lock, locallock->tag.mode);
}
/*
--- 1489,1502 ----
pfree(new_status);
}
! 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);
+
+ return wait_status;
}
/*
*************** VirtualXactLock(VirtualTransactionId vxi
*** 3739,3745 ****
LWLockRelease(proc->backendLock);
/* Time to wait. */
! (void) LockAcquire(&tag, ShareLock, false, false);
LockRelease(&tag, ShareLock, false);
return true;
--- 3768,3778 ----
LWLockRelease(proc->backendLock);
/* Time to wait. */
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on virtual transaction with ID %u",
! vxid.localTransactionId)));
LockRelease(&tag, ShareLock, false);
return true;
diff -dcrpN postgresql.orig/src/backend/storage/lmgr/Makefile postgresql/src/backend/storage/lmgr/Makefile
*** postgresql.orig/src/backend/storage/lmgr/Makefile 2011-02-10 10:36:32.248685484 +0100
--- postgresql/src/backend/storage/lmgr/Makefile 2012-04-04 14:31:15.547760162 +0200
*************** subdir = src/backend/storage/lmgr
*** 12,18 ****
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
! OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o predicate.o
include $(top_srcdir)/src/backend/common.mk
--- 12,18 ----
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
! OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o predicate.o timeout.o
include $(top_srcdir)/src/backend/common.mk
diff -dcrpN postgresql.orig/src/backend/storage/lmgr/proc.c postgresql/src/backend/storage/lmgr/proc.c
*** postgresql.orig/src/backend/storage/lmgr/proc.c 2012-03-22 11:00:51.723002056 +0100
--- postgresql/src/backend/storage/lmgr/proc.c 2012-04-04 17:04:07.824916865 +0200
***************
*** 48,59 ****
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/spin.h"
#include "utils/timestamp.h"
/* GUC variables */
- int DeadlockTimeout = 1000;
- int StatementTimeout = 0;
bool log_lock_waits = false;
/* Pointer to this process's PGPROC struct, if any */
--- 48,58 ----
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/spin.h"
+ #include "storage/timeout.h"
#include "utils/timestamp.h"
/* GUC variables */
bool log_lock_waits = false;
/* Pointer to this process's PGPROC struct, if any */
*************** PGPROC *PreparedXactProcs = NULL;
*** 77,103 ****
/* If we are waiting for a lock, this points to the associated LOCALLOCK */
static LOCALLOCK *lockAwaited = NULL;
! /* Mark these volatile because they can be changed by signal handler */
! static volatile bool standby_timeout_active = false;
! static volatile bool statement_timeout_active = false;
! static volatile bool deadlock_timeout_active = false;
! static volatile DeadLockState deadlock_state = DS_NOT_YET_CHECKED;
! volatile bool cancel_from_timeout = false;
!
! /* timeout_start_time is set when log_lock_waits is true */
! static TimestampTz timeout_start_time;
!
! /* statement_fin_time is valid only if statement_timeout_active is true */
! static TimestampTz statement_fin_time;
! static TimestampTz statement_fin_time2; /* valid only in recovery */
!
static void RemoveProcFromArray(int code, Datum arg);
static void ProcKill(int code, Datum arg);
static void AuxiliaryProcKill(int code, Datum arg);
- static bool CheckStatementTimeout(void);
- static bool CheckStandbyTimeout(void);
-
/*
* Report shared-memory space needed by InitProcGlobal.
--- 76,87 ----
/* If we are waiting for a lock, this points to the associated LOCALLOCK */
static LOCALLOCK *lockAwaited = NULL;
! /* Declared in timeout.c */
! extern volatile DeadLockState deadlock_state;
static void RemoveProcFromArray(int code, Datum arg);
static void ProcKill(int code, Datum arg);
static void AuxiliaryProcKill(int code, Datum arg);
/*
* Report shared-memory space needed by InitProcGlobal.
*************** LockWaitCancel(void)
*** 651,657 ****
return;
/* Turn off the deadlock timer, if it's still running (see ProcSleep) */
! disable_sig_alarm(false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
--- 635,641 ----
return;
/* Turn off the deadlock timer, if it's still running (see ProcSleep) */
! disable_timeout(DEADLOCK_TIMEOUT, false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
*************** ProcQueueInit(PROC_QUEUE *queue)
*** 885,891 ****
* 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).
*
* ASSUME: that no one will fiddle with the queue until after
* we release the partition lock.
--- 869,878 ----
* 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)
! * STATUS_WAITING if not (timeout)
*
* ASSUME: that no one will fiddle with the queue until after
* we release the partition lock.
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 907,912 ****
--- 894,900 ----
LOCKMASK myHeldLocks = MyProc->heldLocks;
bool early_deadlock = false;
bool allow_autovacuum_cancel = true;
+ bool timeout_detected;
int myWaitStatus;
PGPROC *proc;
int i;
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1044,1055 ****
* 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 (!enable_sig_alarm(DeadlockTimeout, false))
elog(FATAL, "could not set timer for process wakeup");
/*
! * 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
* saved wakeup might be leftover from a previous operation (for example,
* we aborted ProcWaitForSignal just before someone did ProcSendSignal).
--- 1032,1051 ----
* 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 (!enable_timeout(DEADLOCK_TIMEOUT, DeadlockTimeout))
elog(FATAL, "could not set timer for process wakeup");
/*
! * Reset timer so we are awaken in case of lock timeout.
! * This doesn't modify the timer for deadlock check in case
! * the deadlock check happens earlier.
! */
! if (!enable_timeout(LOCK_TIMEOUT, LockTimeout))
! elog(FATAL, "could not set timer for process wakeup");
!
! /*
! * 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).
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1066,1072 ****
*/
do
{
! PGSemaphoreLock(&MyProc->sem, true);
/*
* waitStatus could change from STATUS_WAITING to something else
--- 1062,1071 ----
*/
do
{
! PGSemaphoreTimedLock(&MyProc->sem, true);
!
! if (get_timeout_indicator(LOCK_TIMEOUT))
! break;
/*
* waitStatus could change from STATUS_WAITING to something else
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1134,1140 ****
DescribeLockTag(&buf, &locallock->tag.lock);
modename = GetLockmodeName(locallock->tag.lock.locktag_lockmethodid,
lockmode);
! TimestampDifference(timeout_start_time, GetCurrentTimestamp(),
&secs, &usecs);
msecs = secs * 1000 + usecs / 1000;
usecs = usecs % 1000;
--- 1133,1140 ----
DescribeLockTag(&buf, &locallock->tag.lock);
modename = GetLockmodeName(locallock->tag.lock.locktag_lockmethodid,
lockmode);
! TimestampDifference(get_timeout_start(DEADLOCK_TIMEOUT),
! GetCurrentTimestamp(),
&secs, &usecs);
msecs = secs * 1000 + usecs / 1000;
usecs = usecs % 1000;
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1194,1202 ****
} while (myWaitStatus == STATUS_WAITING);
/*
! * Disable the timer, if it's still running
*/
! if (!disable_sig_alarm(false))
elog(FATAL, "could not disable timer for process wakeup");
/*
--- 1194,1210 ----
} while (myWaitStatus == STATUS_WAITING);
/*
! * Disable the deadlock timer, if it's still running
*/
! if (!disable_timeout(DEADLOCK_TIMEOUT, false))
! elog(FATAL, "could not disable timer for process wakeup");
!
! /*
! * Disable the lock timeout timer, if it's still running
! * but keep the indicator for later checks.
! */
! timeout_detected = get_timeout_indicator(LOCK_TIMEOUT);
! if (!disable_timeout(LOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for process wakeup");
/*
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1207,1212 ****
--- 1215,1229 ----
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 LockWaitCancel to do anything.
*/
lockAwaited = NULL;
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1220,1227 ****
/*
* We don't have to do anything else, because the awaker did all the
* necessary update of the lock table and MyProc.
*/
! return MyProc->waitStatus;
}
--- 1237,1246 ----
/*
* 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 (timeout_detected ? STATUS_WAITING : MyProc->waitStatus);
}
*************** ProcLockWakeup(LockMethod lockMethodTabl
*** 1328,1448 ****
}
/*
- * CheckDeadLock
- *
- * We only get to this routine if we got SIGALRM after DeadlockTimeout
- * while waiting for a lock to be released by some other process. Look
- * to see if there's a deadlock; if not, just return and continue waiting.
- * (But signal ProcSleep to log a message, if log_lock_waits is true.)
- * If we have a real deadlock, remove ourselves from the lock's wait queue
- * and signal an error to ProcSleep.
- *
- * NB: this is run inside a signal handler, so be very wary about what is done
- * here or in called routines.
- */
- static void
- CheckDeadLock(void)
- {
- int i;
-
- /*
- * Acquire exclusive lock on the entire shared lock data structures. Must
- * grab LWLocks in partition-number order to avoid LWLock deadlock.
- *
- * Note that the deadlock check interrupt had better not be enabled
- * anywhere that this process itself holds lock partition locks, else this
- * will wait forever. Also note that LWLockAcquire creates a critical
- * section, so that this routine cannot be interrupted by cancel/die
- * interrupts.
- */
- for (i = 0; i < NUM_LOCK_PARTITIONS; i++)
- LWLockAcquire(FirstLockMgrLock + i, LW_EXCLUSIVE);
-
- /*
- * Check to see if we've been awoken by anyone in the interim.
- *
- * If we have, we can return and resume our transaction -- happy day.
- * Before we are awoken the process releasing the lock grants it to us so
- * we know that we don't have to wait anymore.
- *
- * We check by looking to see if we've been unlinked from the wait queue.
- * This is quicker than checking our semaphore's state, since no kernel
- * call is needed, and it is safe because we hold the lock partition lock.
- */
- if (MyProc->links.prev == NULL ||
- MyProc->links.next == NULL)
- goto check_done;
-
- #ifdef LOCK_DEBUG
- if (Debug_deadlocks)
- DumpAllLocks();
- #endif
-
- /* Run the deadlock check, and set deadlock_state for use by ProcSleep */
- deadlock_state = DeadLockCheck(MyProc);
-
- if (deadlock_state == DS_HARD_DEADLOCK)
- {
- /*
- * Oops. We have a deadlock.
- *
- * Get this process out of wait state. (Note: we could do this more
- * efficiently by relying on lockAwaited, but use this coding to
- * preserve the flexibility to kill some other transaction than the
- * one detecting the deadlock.)
- *
- * RemoveFromWaitQueue sets MyProc->waitStatus to STATUS_ERROR, so
- * ProcSleep will report an error after we return from the signal
- * handler.
- */
- Assert(MyProc->waitLock != NULL);
- RemoveFromWaitQueue(MyProc, LockTagHashCode(&(MyProc->waitLock->tag)));
-
- /*
- * Unlock my semaphore so that the interrupted ProcSleep() call can
- * finish.
- */
- PGSemaphoreUnlock(&MyProc->sem);
-
- /*
- * We're done here. Transaction abort caused by the error that
- * ProcSleep will raise will cause any other locks we hold to be
- * released, thus allowing other processes to wake up; we don't need
- * to do that here. NOTE: an exception is that releasing locks we
- * hold doesn't consider the possibility of waiters that were blocked
- * behind us on the lock we just failed to get, and might now be
- * wakable because we're not in front of them anymore. However,
- * RemoveFromWaitQueue took care of waking up any such processes.
- */
- }
- else if (log_lock_waits || deadlock_state == DS_BLOCKED_BY_AUTOVACUUM)
- {
- /*
- * Unlock my semaphore so that the interrupted ProcSleep() call can
- * print the log message (we daren't do it here because we are inside
- * a signal handler). It will then sleep again until someone releases
- * the lock.
- *
- * If blocked by autovacuum, this wakeup will enable ProcSleep to send
- * the canceling signal to the autovacuum worker.
- */
- PGSemaphoreUnlock(&MyProc->sem);
- }
-
- /*
- * And release locks. We do this in reverse order for two reasons: (1)
- * Anyone else who needs more than one of the locks will be trying to lock
- * them in increasing order; we don't want to release the other process
- * until it can get all the locks it needs. (2) This avoids O(N^2)
- * behavior inside LWLockRelease.
- */
- check_done:
- for (i = NUM_LOCK_PARTITIONS; --i >= 0;)
- LWLockRelease(FirstLockMgrLock + i);
- }
-
-
- /*
* ProcWaitForSignal - wait for a signal from another backend.
*
* This can share the semaphore normally used for waiting for locks,
--- 1347,1352 ----
*************** ProcSendSignal(int pid)
*** 1494,1894 ****
if (proc != NULL)
PGSemaphoreUnlock(&proc->sem);
}
-
-
- /*****************************************************************************
- * SIGALRM interrupt support
- *
- * Maybe these should be in pqsignal.c?
- *****************************************************************************/
-
- /*
- * Enable the SIGALRM interrupt to fire after the specified delay
- *
- * Delay is given in milliseconds. Caller should be sure a SIGALRM
- * signal handler is installed before this is called.
- *
- * This code properly handles nesting of deadlock timeout alarms within
- * statement timeout alarms.
- *
- * Returns TRUE if okay, FALSE on failure.
- */
- bool
- enable_sig_alarm(int delayms, bool is_statement_timeout)
- {
- TimestampTz fin_time;
- struct itimerval timeval;
-
- if (is_statement_timeout)
- {
- /*
- * Begin statement-level timeout
- *
- * Note that we compute statement_fin_time with reference to the
- * statement_timestamp, but apply the specified delay without any
- * correction; that is, we ignore whatever time has elapsed since
- * statement_timestamp was set. In the normal case only a small
- * interval will have elapsed and so this doesn't matter, but there
- * are corner cases (involving multi-statement query strings with
- * embedded COMMIT or ROLLBACK) where we might re-initialize the
- * statement timeout long after initial receipt of the message. In
- * such cases the enforcement of the statement timeout will be a bit
- * inconsistent. This annoyance is judged not worth the cost of
- * performing an additional gettimeofday() here.
- */
- Assert(!deadlock_timeout_active);
- fin_time = GetCurrentStatementStartTimestamp();
- fin_time = TimestampTzPlusMilliseconds(fin_time, delayms);
- statement_fin_time = fin_time;
- cancel_from_timeout = false;
- statement_timeout_active = true;
- }
- else if (statement_timeout_active)
- {
- /*
- * Begin deadlock timeout with statement-level timeout active
- *
- * Here, we want to interrupt at the closer of the two timeout times.
- * If fin_time >= statement_fin_time then we need not touch the
- * existing timer setting; else set up to interrupt at the deadlock
- * timeout time.
- *
- * NOTE: in this case it is possible that this routine will be
- * interrupted by the previously-set timer alarm. This is okay
- * because the signal handler will do only what it should do according
- * to the state variables. The deadlock checker may get run earlier
- * than normal, but that does no harm.
- */
- timeout_start_time = GetCurrentTimestamp();
- fin_time = TimestampTzPlusMilliseconds(timeout_start_time, delayms);
- deadlock_timeout_active = true;
- if (fin_time >= statement_fin_time)
- return true;
- }
- else
- {
- /* Begin deadlock timeout with no statement-level timeout */
- deadlock_timeout_active = true;
- /* GetCurrentTimestamp can be expensive, so only do it if we must */
- if (log_lock_waits)
- timeout_start_time = GetCurrentTimestamp();
- }
-
- /* If we reach here, okay to set the timer interrupt */
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = delayms / 1000;
- timeval.it_value.tv_usec = (delayms % 1000) * 1000;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- return true;
- }
-
- /*
- * Cancel the SIGALRM timer, either for a deadlock timeout or a statement
- * timeout. If a deadlock timeout is canceled, any active statement timeout
- * remains in force.
- *
- * Returns TRUE if okay, FALSE on failure.
- */
- bool
- disable_sig_alarm(bool is_statement_timeout)
- {
- /*
- * Always disable the interrupt if it is active; this avoids being
- * interrupted by the signal handler and thereby possibly getting
- * confused.
- *
- * We will re-enable the interrupt if necessary in CheckStatementTimeout.
- */
- if (statement_timeout_active || deadlock_timeout_active)
- {
- struct itimerval timeval;
-
- MemSet(&timeval, 0, sizeof(struct itimerval));
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- {
- statement_timeout_active = false;
- cancel_from_timeout = false;
- deadlock_timeout_active = false;
- return false;
- }
- }
-
- /* Always cancel deadlock timeout, in case this is error cleanup */
- deadlock_timeout_active = false;
-
- /* Cancel or reschedule statement timeout */
- if (is_statement_timeout)
- {
- statement_timeout_active = false;
- cancel_from_timeout = false;
- }
- else if (statement_timeout_active)
- {
- if (!CheckStatementTimeout())
- return false;
- }
- return true;
- }
-
-
- /*
- * Check for statement timeout. If the timeout time has come,
- * trigger a query-cancel interrupt; if not, reschedule the SIGALRM
- * interrupt to occur at the right time.
- *
- * Returns true if okay, false if failed to set the interrupt.
- */
- static bool
- CheckStatementTimeout(void)
- {
- TimestampTz now;
-
- if (!statement_timeout_active)
- return true; /* do nothing if not active */
-
- now = GetCurrentTimestamp();
-
- if (now >= statement_fin_time)
- {
- /* Time to die */
- statement_timeout_active = false;
- cancel_from_timeout = true;
- #ifdef HAVE_SETSID
- /* try to signal whole process group */
- kill(-MyProcPid, SIGINT);
- #endif
- kill(MyProcPid, SIGINT);
- }
- else
- {
- /* Not time yet, so (re)schedule the interrupt */
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
-
- /*
- * It's possible that the difference is less than a microsecond;
- * ensure we don't cancel, rather than set, the interrupt.
- */
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- }
-
- return true;
- }
-
-
- /*
- * Signal handler for SIGALRM for normal user backends
- *
- * Process deadlock check and/or statement timeout check, as needed.
- * To avoid various edge cases, we must be careful to do nothing
- * when there is nothing to be done. We also need to be able to
- * reschedule the timer interrupt if called before end of statement.
- */
- void
- handle_sig_alarm(SIGNAL_ARGS)
- {
- int save_errno = errno;
-
- /* SIGALRM is cause for waking anything waiting on the process latch */
- if (MyProc)
- SetLatch(&MyProc->procLatch);
-
- if (deadlock_timeout_active)
- {
- deadlock_timeout_active = false;
- CheckDeadLock();
- }
-
- if (statement_timeout_active)
- (void) CheckStatementTimeout();
-
- errno = save_errno;
- }
-
- /*
- * Signal handler for SIGALRM in Startup process
- *
- * To avoid various edge cases, we must be careful to do nothing
- * when there is nothing to be done. We also need to be able to
- * reschedule the timer interrupt if called before end of statement.
- *
- * We set either deadlock_timeout_active or statement_timeout_active
- * or both. Interrupts are enabled if standby_timeout_active.
- */
- bool
- enable_standby_sig_alarm(TimestampTz now, TimestampTz fin_time, bool deadlock_only)
- {
- TimestampTz deadlock_time = TimestampTzPlusMilliseconds(now,
- DeadlockTimeout);
-
- if (deadlock_only)
- {
- /*
- * Wake up at deadlock_time only, then wait forever
- */
- statement_fin_time = deadlock_time;
- deadlock_timeout_active = true;
- statement_timeout_active = false;
- }
- else if (fin_time > deadlock_time)
- {
- /*
- * Wake up at deadlock_time, then again at fin_time
- */
- statement_fin_time = deadlock_time;
- statement_fin_time2 = fin_time;
- deadlock_timeout_active = true;
- statement_timeout_active = true;
- }
- else
- {
- /*
- * Wake only at fin_time because its fairly soon
- */
- statement_fin_time = fin_time;
- deadlock_timeout_active = false;
- statement_timeout_active = true;
- }
-
- if (deadlock_timeout_active || statement_timeout_active)
- {
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- standby_timeout_active = true;
- }
-
- return true;
- }
-
- bool
- disable_standby_sig_alarm(void)
- {
- /*
- * Always disable the interrupt if it is active; this avoids being
- * interrupted by the signal handler and thereby possibly getting
- * confused.
- *
- * We will re-enable the interrupt if necessary in CheckStandbyTimeout.
- */
- if (standby_timeout_active)
- {
- struct itimerval timeval;
-
- MemSet(&timeval, 0, sizeof(struct itimerval));
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- {
- standby_timeout_active = false;
- return false;
- }
- }
-
- standby_timeout_active = false;
-
- return true;
- }
-
- /*
- * CheckStandbyTimeout() runs unconditionally in the Startup process
- * SIGALRM handler. Timers will only be set when InHotStandby.
- * We simply ignore any signals unless the timer has been set.
- */
- static bool
- CheckStandbyTimeout(void)
- {
- TimestampTz now;
- bool reschedule = false;
-
- standby_timeout_active = false;
-
- now = GetCurrentTimestamp();
-
- /*
- * Reschedule the timer if its not time to wake yet, or if we have both
- * timers set and the first one has just been reached.
- */
- if (now >= statement_fin_time)
- {
- if (deadlock_timeout_active)
- {
- /*
- * We're still waiting when we reach deadlock timeout, so send out
- * a request to have other backends check themselves for deadlock.
- * Then continue waiting until statement_fin_time, if that's set.
- */
- SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
- deadlock_timeout_active = false;
-
- /*
- * Begin second waiting period if required.
- */
- if (statement_timeout_active)
- {
- reschedule = true;
- statement_fin_time = statement_fin_time2;
- }
- }
- else
- {
- /*
- * We've now reached statement_fin_time, so ask all conflicts to
- * leave, so we can press ahead with applying changes in recovery.
- */
- SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
- }
- }
- else
- reschedule = true;
-
- if (reschedule)
- {
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- standby_timeout_active = true;
- }
-
- return true;
- }
-
- void
- handle_standby_sig_alarm(SIGNAL_ARGS)
- {
- int save_errno = errno;
-
- if (standby_timeout_active)
- (void) CheckStandbyTimeout();
-
- errno = save_errno;
- }
--- 1398,1400 ----
diff -dcrpN postgresql.orig/src/backend/storage/lmgr/timeout.c postgresql/src/backend/storage/lmgr/timeout.c
*** postgresql.orig/src/backend/storage/lmgr/timeout.c 1970-01-01 01:00:00.000000000 +0100
--- postgresql/src/backend/storage/lmgr/timeout.c 2012-04-04 16:11:36.110068311 +0200
***************
*** 0 ****
--- 1,722 ----
+ /*-------------------------------------------------------------------------
+ *
+ * timeout.c
+ * routines to manage timeout sources handled by SIGALRM
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/storage/lmgr/timeout.c
+ *
+ *-------------------------------------------------------------------------
+ */
+ /*
+ * Interface:
+ * enable_timeout(),
+ * disable_timeout(), disable_all_timeouts()
+ * get_timeout_indicator(), get_timeout_start()
+ * and
+ * handle_sig_alarm()
+ */
+ #include "postgres.h"
+
+ #include <sys/time.h>
+
+ #include "access/xact.h"
+ #include "miscadmin.h"
+ #include "storage/proc.h"
+ #include "storage/standby.h"
+ #include "storage/timeout.h"
+ #include "utils/timestamp.h"
+
+ /* GUC variables */
+ int DeadlockTimeout = 1000;
+ int StatementTimeout = 0;
+ int LockTimeout = 0;
+
+ /*
+ * This is used by ProcSleep() in proc.c
+ * Mark this volatile because it can be changed by the signal handler.
+ */
+ volatile DeadLockState deadlock_state = DS_NOT_YET_CHECKED;
+
+
+ /*
+ * Infrastructure for timeouts
+ */
+
+ static void InitDeadLock(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyDeadLock(bool keep_indicator);
+ static bool CheckDeadLock(void);
+
+ static void InitLockTimeout(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyLockTimeout(bool keep_indicator);
+ static bool CheckLockTimeout(void);
+
+ static void InitStatementTimeout(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyStatementTimeout(bool keep_indicator);
+ static bool CheckStatementTimeout(void);
+
+ static void InitStandbyDeadLock(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyStandbyDeadLock(bool keep_indicator);
+ static bool CheckStandbyDeadLock(void);
+
+ static void InitStandbyTimeout(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyStandbyTimeout(bool keep_indicator);
+ static bool CheckStandbyTimeout(void);
+
+ typedef void (*timeout_init)(TimestampTz, TimestampTz);
+ typedef void (*timeout_destroy)(bool);
+ typedef bool (*timeout_check)(void);
+ typedef TimestampTz (*timeout_start)(void);
+
+ typedef struct {
+ TimeoutName index;
+ bool resched_next;
+
+ /* volatile because it may be changed from the signal handler */
+ volatile bool indicator;
+
+ timeout_init timeout_init;
+ timeout_destroy timeout_destroy;
+ timeout_check timeout_check;
+ timeout_start timeout_start;
+
+ TimestampTz start_time;
+ TimestampTz fin_time;
+ } timeout_params;
+
+ /*
+ * List of possible timeout reasons in the order of enum TimeoutName.
+ * The priority of timeouts (in case two of them would trigger at the
+ * same time) is determined by this order: the earlier one in the list
+ * has higher priority.
+ */
+ static timeout_params base_timeouts[TIMEOUT_MAX] = {
+ {
+ DEADLOCK_TIMEOUT, true, false,
+ InitDeadLock, DestroyDeadLock,
+ CheckDeadLock, GetCurrentTimestamp,
+ 0
+ },
+
+ {
+ LOCK_TIMEOUT, false, false,
+ InitLockTimeout, DestroyLockTimeout,
+ CheckLockTimeout, GetCurrentTimestamp,
+ 0
+ },
+
+ {
+ STATEMENT_TIMEOUT, false, false,
+ InitStatementTimeout, DestroyStatementTimeout,
+ CheckStatementTimeout, GetCurrentStatementStartTimestamp,
+ 0
+ },
+
+ {
+ STANDBY_DEADLOCK_TIMEOUT, true, false,
+ InitStandbyDeadLock, DestroyStandbyDeadLock,
+ CheckStandbyDeadLock, GetCurrentTimestamp,
+ 0
+ },
+
+ {
+ STANDBY_TIMEOUT, false, false,
+ InitStandbyTimeout, DestroyStandbyTimeout,
+ CheckStandbyTimeout, GetCurrentTimestamp,
+ 0
+ }
+ };
+
+ /*
+ * List of active timeouts ordered by their fin_time and priority.
+ */
+ static int n_timeouts = 0;
+ static timeout_params *timeouts[TIMEOUT_MAX];
+
+ /*****************************************************************************
+ * Internal helper functions
+ *****************************************************************************/
+
+ /*
+ * Find the index of a given timeout type in the active array
+ */
+ static int
+ find_active_timeout(TimeoutName tn)
+ {
+ int i;
+
+ for (i = 0; i < n_timeouts; i++)
+ {
+ if (timeouts[i]->index == tn)
+ return i;
+ }
+
+ return -1;
+ }
+
+ #define is_timeout_active(tn) (find_active_timeout(tn) >= 0)
+
+ /*
+ * Insert tn'th timeout into the list of timeouts at the given index
+ * if the previous timeout allows rescheduling the next one in the list.
+ */
+ static void
+ insert_timeout(TimeoutName tn, int index)
+ {
+ int i;
+
+ if (index > 0 && !timeouts[index-1]->resched_next)
+ return;
+
+ for (i = n_timeouts - 1; i >= index; i--)
+ timeouts[i+1] = timeouts[i];
+
+ timeouts[index] = &base_timeouts[tn];
+ n_timeouts++;
+ }
+
+ /*
+ * Remove the index'th element from the timeout list.
+ */
+ static void
+ remove_timeout_index(int index)
+ {
+ int i;
+
+ if (index < 0)
+ return;
+
+ for (i = index + 1; i < n_timeouts; i++)
+ timeouts[i-1] = timeouts[i];
+
+ if (n_timeouts > 0 && index < n_timeouts)
+ n_timeouts--;
+ }
+
+ /*
+ * (Re)schedule the next active timeout
+ */
+ static bool
+ schedule_timeout(TimestampTz now)
+ {
+ long secs;
+ int usecs;
+ struct itimerval timeval;
+
+ /* There is no active timeout, do nothing. */
+ if (n_timeouts == 0)
+ return true;
+
+ TimestampDifference(now, timeouts[0]->fin_time,
+ &secs, &usecs);
+
+ /*
+ * It's possible that the difference is less than a microsecond;
+ * ensure we don't cancel, rather than set, the interrupt.
+ */
+ if (secs == 0 && usecs == 0)
+ usecs = 1;
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ timeval.it_value.tv_sec = secs;
+ timeval.it_value.tv_usec = usecs;
+ if (setitimer(ITIMER_REAL, &timeval, NULL))
+ return false;
+ return true;
+ }
+
+ /*****************************************************************************
+ * Init, Destroy and Check functions for different timeouts.
+ *
+ * NB: all Check* functions are run inside a signal handler, so be very wary
+ * about what is done in them or in called routines.
+ *****************************************************************************/
+
+ /*
+ * Common Init and Destroy functions
+ */
+
+ static void
+ InitTimeout(TimeoutName tn, TimestampTz start_time, TimestampTz fin_time)
+ {
+ base_timeouts[tn].indicator = false;
+ base_timeouts[tn].start_time = start_time;
+ base_timeouts[tn].fin_time = fin_time;
+ }
+
+ static void
+ DestroyTimeout(TimeoutName tn, bool keep_indicator)
+ {
+ if (!keep_indicator)
+ base_timeouts[tn].indicator = false;
+ base_timeouts[tn].start_time = 0;
+ base_timeouts[tn].fin_time = 0;
+ }
+
+ /*
+ * Functions to manage deadlock
+ */
+
+ static void
+ InitDeadLock(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(DEADLOCK_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyDeadLock(bool keep_indicator)
+ {
+ DestroyTimeout(DEADLOCK_TIMEOUT, keep_indicator);
+ }
+
+ /*
+ * CheckDeadLock
+ *
+ * Look to see if there's a deadlock; if not, just return.
+ * (But signal ProcSleep to log a message, if log_lock_waits is true.)
+ * If we have a real deadlock, remove ourselves from the lock's wait queue
+ * and signal an error to ProcSleep.
+ */
+ static bool
+ CheckDeadLock(void)
+ {
+ TimestampTz now;
+ int i;
+
+ now = GetCurrentTimestamp();
+
+ /* If our time has not come yet, do nothing. */
+ if (now < base_timeouts[DEADLOCK_TIMEOUT].fin_time)
+ return false;
+
+ /*
+ * Acquire exclusive lock on the entire shared lock data structures. Must
+ * grab LWLocks in partition-number order to avoid LWLock deadlock.
+ *
+ * Note that the deadlock check interrupt had better not be enabled
+ * anywhere that this process itself holds lock partition locks, else this
+ * will wait forever. Also note that LWLockAcquire creates a critical
+ * section, so that this routine cannot be interrupted by cancel/die
+ * interrupts.
+ */
+ for (i = 0; i < NUM_LOCK_PARTITIONS; i++)
+ LWLockAcquire(FirstLockMgrLock + i, LW_EXCLUSIVE);
+
+ /*
+ * Check to see if we've been awoken by anyone in the interim.
+ *
+ * If we have, we can return and resume our transaction -- happy day.
+ * Before we are awoken the process releasing the lock grants it to us so
+ * we know that we don't have to wait anymore.
+ *
+ * We check by looking to see if we've been unlinked from the wait queue.
+ * This is quicker than checking our semaphore's state, since no kernel
+ * call is needed, and it is safe because we hold the lock partition lock.
+ */
+ if (MyProc->links.prev == NULL ||
+ MyProc->links.next == NULL)
+ goto check_done;
+
+ #ifdef LOCK_DEBUG
+ if (Debug_deadlocks)
+ DumpAllLocks();
+ #endif
+
+ /* Run the deadlock check, and set deadlock_state for use by ProcSleep */
+ deadlock_state = DeadLockCheck(MyProc);
+
+ if (deadlock_state == DS_HARD_DEADLOCK)
+ {
+ /*
+ * Oops. We have a deadlock.
+ *
+ * Get this process out of wait state. (Note: we could do this more
+ * efficiently by relying on lockAwaited, but use this coding to
+ * preserve the flexibility to kill some other transaction than the
+ * one detecting the deadlock.)
+ *
+ * RemoveFromWaitQueue sets MyProc->waitStatus to STATUS_ERROR, so
+ * ProcSleep will report an error after we return from the signal
+ * handler.
+ */
+ Assert(MyProc->waitLock != NULL);
+ RemoveFromWaitQueue(MyProc, LockTagHashCode(&(MyProc->waitLock->tag)));
+
+ /*
+ * Unlock my semaphore so that the interrupted ProcSleep() call can
+ * finish.
+ */
+ PGSemaphoreUnlock(&MyProc->sem);
+
+ /*
+ * We're done here. Transaction abort caused by the error that
+ * ProcSleep will raise will cause any other locks we hold to be
+ * released, thus allowing other processes to wake up; we don't need
+ * to do that here. NOTE: an exception is that releasing locks we
+ * hold doesn't consider the possibility of waiters that were blocked
+ * behind us on the lock we just failed to get, and might now be
+ * wakable because we're not in front of them anymore. However,
+ * RemoveFromWaitQueue took care of waking up any such processes.
+ */
+ }
+ else if (log_lock_waits || deadlock_state == DS_BLOCKED_BY_AUTOVACUUM)
+ {
+ /*
+ * Unlock my semaphore so that the interrupted ProcSleep() call can
+ * print the log message (we daren't do it here because we are inside
+ * a signal handler). It will then sleep again until someone releases
+ * the lock.
+ *
+ * If blocked by autovacuum, this wakeup will enable ProcSleep to send
+ * the canceling signal to the autovacuum worker.
+ */
+ PGSemaphoreUnlock(&MyProc->sem);
+ }
+
+ /*
+ * And release locks. We do this in reverse order for two reasons: (1)
+ * Anyone else who needs more than one of the locks will be trying to lock
+ * them in increasing order; we don't want to release the other process
+ * until it can get all the locks it needs. (2) This avoids O(N^2)
+ * behavior inside LWLockRelease.
+ */
+ check_done:
+ for (i = NUM_LOCK_PARTITIONS; --i >= 0;)
+ LWLockRelease(FirstLockMgrLock + i);
+
+ base_timeouts[DEADLOCK_TIMEOUT].indicator = true;
+
+ return true;
+ }
+
+ /*
+ * Functions to manage lock timeout
+ */
+
+ static void
+ InitLockTimeout(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(LOCK_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyLockTimeout(bool keep_indicator)
+ {
+ DestroyTimeout(LOCK_TIMEOUT, keep_indicator);
+ }
+
+ static bool
+ CheckLockTimeout(void)
+ {
+ TimestampTz now;
+
+ now = GetCurrentTimestamp();
+
+ if (now < base_timeouts[LOCK_TIMEOUT].fin_time)
+ return false;
+
+ base_timeouts[LOCK_TIMEOUT].indicator = true;
+ return true;
+ }
+
+ /*
+ * Functions to manage statement timeout
+ */
+
+ static void
+ InitStatementTimeout(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(STATEMENT_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyStatementTimeout(bool keep_indicator)
+ {
+ DestroyTimeout(STATEMENT_TIMEOUT, keep_indicator);
+ }
+
+ static bool
+ CheckStatementTimeout(void)
+ {
+ TimestampTz now;
+
+ now = GetCurrentTimestamp();
+
+ if (now < base_timeouts[STATEMENT_TIMEOUT].fin_time)
+ return false;
+
+ base_timeouts[STATEMENT_TIMEOUT].indicator = true;
+ #ifdef HAVE_SETSID
+ /* try to signal whole process group */
+ kill(-MyProcPid, SIGINT);
+ #endif
+ kill(MyProcPid, SIGINT);
+
+ return true;
+ }
+
+ /*
+ * Functions to manage standby deadlock functions
+ */
+
+ static void
+ InitStandbyDeadLock(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(STANDBY_DEADLOCK_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyStandbyDeadLock(bool keep_indicator)
+ {
+ DestroyTimeout(STANDBY_DEADLOCK_TIMEOUT, keep_indicator);
+ }
+
+ static bool
+ CheckStandbyDeadLock(void)
+ {
+ TimestampTz now;
+
+ now = GetCurrentTimestamp();
+
+ if (now < base_timeouts[STANDBY_DEADLOCK_TIMEOUT].fin_time)
+ return false;
+
+ base_timeouts[STANDBY_DEADLOCK_TIMEOUT].indicator = true;
+ SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
+ return true;
+ }
+
+ /*
+ * Functions to manage standby timeout
+ */
+
+ static void
+ InitStandbyTimeout(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(STANDBY_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyStandbyTimeout(bool keep_indicator)
+ {
+ DestroyTimeout(STANDBY_TIMEOUT, keep_indicator);
+ }
+
+ static bool
+ CheckStandbyTimeout(void)
+ {
+ TimestampTz now;
+
+ now = GetCurrentTimestamp();
+
+ if (now < base_timeouts[STANDBY_TIMEOUT].fin_time)
+ return false;
+
+ base_timeouts[STANDBY_TIMEOUT].indicator = true;
+ SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
+ return true;
+ }
+
+ /*****************************************************************************
+ * Public API
+ *****************************************************************************/
+
+
+ /*
+ * Enable the SIGALRM interrupt to fire after the specified delay
+ *
+ * Delay is given in milliseconds. Caller should be sure a SIGALRM
+ * signal handler is installed before this is called.
+ *
+ * This code properly handles nesting of different timeout alarms.
+ *
+ * Returns TRUE if okay, FALSE on failure to set the timer.
+ */
+ bool
+ enable_timeout(TimeoutName tn, int delayms)
+ {
+ TimestampTz start_time;
+ TimestampTz fin_time;
+ int i;
+
+ Assert(!is_timeout_active(tn));
+
+ if (delayms <= 0)
+ return true;
+
+ start_time = base_timeouts[tn].timeout_start();
+ fin_time = TimestampTzPlusMilliseconds(start_time, delayms);
+
+ /* Find out the index where to insert the new timeout. */
+ for (i = 0; i < n_timeouts; i++)
+ {
+ /*
+ * The new timeout triggers earlier than
+ * a previously added one: insert here.
+ */
+ if (fin_time < timeouts[i]->fin_time)
+ break;
+ /*
+ * The new timeout triggers at the same time
+ * as the previously added one but has greater priority.
+ */
+ if (fin_time == timeouts[i]->fin_time && tn < timeouts[i]->index)
+ break;
+ }
+
+ /*
+ * Initialize the timeout parameters
+ */
+ base_timeouts[tn].timeout_init(start_time, fin_time);
+
+ insert_timeout(tn, i);
+
+ if (i > 0)
+ return true;
+
+ /* If we reach here, okay to set the timer interrupt */
+ if (!schedule_timeout(start_time))
+ return false;
+ return true;
+ }
+
+ /*
+ * Cancel the SIGALRM timer for the specific timeout.
+ * If a timeout is canceled, any other active timeout remains in force.
+ *
+ * Returns TRUE if okay, FALSE on failure to set the timer for
+ * the next timeout source.
+ */
+ bool
+ disable_timeout(TimeoutName tn, bool keep_indicator)
+ {
+ int i;
+
+ /*
+ * Always disable the interrupt if it is active; this avoids being
+ * interrupted by the signal handler and thereby possibly getting
+ * confused.
+ *
+ * We will re-enable the interrupt if necessary in ->check_timeout().
+ */
+ if (n_timeouts > 0)
+ {
+ struct itimerval timeval;
+
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ if (setitimer(ITIMER_REAL, &timeval, NULL))
+ {
+ n_timeouts = 0;
+ for (i = 0; i < TIMEOUT_MAX; i++)
+ base_timeouts[i].timeout_destroy(false);
+
+ return false;
+ }
+ }
+
+ /* Find the timeout and remove from the list. */
+ i = find_active_timeout(tn);
+ remove_timeout_index(i);
+
+ /* Do cleanup. */
+ base_timeouts[tn].timeout_destroy(keep_indicator);
+
+ /*
+ * If the first timeout was removed from the list and there is
+ * at least one active left, reschedule it.
+ */
+ if (i == 0 && n_timeouts > 0)
+ if (!schedule_timeout(GetCurrentTimestamp()))
+ return false;
+
+ return true;
+ }
+
+ /*
+ * Disable SIGALRM and remove all timeouts from the list and
+ * reset the timeout indicators.
+ */
+ bool
+ disable_all_timeouts(bool keep_indicators)
+ {
+ struct itimerval timeval;
+ int i;
+ bool ret;
+
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ ret = (setitimer(ITIMER_REAL, &timeval, NULL) == 0);
+
+ n_timeouts = 0;
+ for (i = 0; i < TIMEOUT_MAX; i++)
+ base_timeouts[i].timeout_destroy(keep_indicators);
+
+ return ret;
+ }
+
+ /*
+ * Return the timeout indicator
+ */
+ bool
+ get_timeout_indicator(TimeoutName tn)
+ {
+ return base_timeouts[tn].indicator;
+ }
+
+ /*
+ * Return the start of the timer for this timeout
+ */
+ TimestampTz
+ get_timeout_start(TimeoutName tn)
+ {
+ return base_timeouts[tn].start_time;
+ }
+
+ /*
+ * Signal handler for SIGALRM
+ *
+ * Process the check for the currently active timeout source and
+ * reschedule the next as needed. To avoid various edge cases,
+ * we must be careful to do nothing when there is nothing to be done.
+ */
+ void
+ handle_sig_alarm(SIGNAL_ARGS)
+ {
+ int save_errno = errno;
+
+ /*
+ * SIGALRM is cause for waking anything waiting on the process latch.
+ * Recovery (the startup process) doesn't have MyProc set so
+ * it can also use this signal handler.
+ */
+ if (MyProc)
+ SetLatch(&MyProc->procLatch);
+
+ if (n_timeouts > 0)
+ {
+ bool ret;
+ bool reschedule = true;
+
+ ret = timeouts[0]->timeout_check();
+
+ /* Shift timeouts if the timeout was triggered */
+ if (ret)
+ {
+ reschedule = timeouts[0]->resched_next;
+ /*
+ * Short circuit disable_timeout(..., true) for the
+ * timeout source that just triggered.
+ */
+ remove_timeout_index(0);
+ }
+
+ if (reschedule)
+ schedule_timeout(GetCurrentTimestamp());
+ else
+ disable_all_timeouts(true);
+ }
+
+ errno = save_errno;
+ }
diff -dcrpN postgresql.orig/src/backend/tcop/postgres.c postgresql/src/backend/tcop/postgres.c
*** postgresql.orig/src/backend/tcop/postgres.c 2012-03-28 10:54:25.195368447 +0200
--- postgresql/src/backend/tcop/postgres.c 2012-04-04 15:07:43.152700939 +0200
***************
*** 64,69 ****
--- 64,70 ----
#include "storage/proc.h"
#include "storage/procsignal.h"
#include "storage/sinval.h"
+ #include "storage/timeout.h"
#include "tcop/fastpath.h"
#include "tcop/pquery.h"
#include "tcop/tcopprot.h"
*************** start_xact_command(void)
*** 2368,2376 ****
/* Set statement timeout running, if any */
/* NB: this mustn't be enabled until we are within an xact */
if (StatementTimeout > 0)
! enable_sig_alarm(StatementTimeout, true);
else
! cancel_from_timeout = false;
xact_started = true;
}
--- 2369,2377 ----
/* Set statement timeout running, if any */
/* NB: this mustn't be enabled until we are within an xact */
if (StatementTimeout > 0)
! enable_timeout(STATEMENT_TIMEOUT, StatementTimeout);
else
! disable_timeout(STATEMENT_TIMEOUT, false);
xact_started = true;
}
*************** finish_xact_command(void)
*** 2382,2388 ****
if (xact_started)
{
/* Cancel any active statement timeout before committing */
! disable_sig_alarm(true);
/* Now commit the command */
ereport(DEBUG3,
--- 2383,2389 ----
if (xact_started)
{
/* Cancel any active statement timeout before committing */
! disable_all_timeouts(false);
/* Now commit the command */
ereport(DEBUG3,
*************** ProcessInterrupts(void)
*** 2863,2869 ****
(errcode(ERRCODE_QUERY_CANCELED),
errmsg("canceling authentication due to timeout")));
}
! if (cancel_from_timeout)
{
ImmediateInterruptOK = false; /* not idle anymore */
DisableNotifyInterrupt();
--- 2864,2870 ----
(errcode(ERRCODE_QUERY_CANCELED),
errmsg("canceling authentication due to timeout")));
}
! if (get_timeout_indicator(STATEMENT_TIMEOUT))
{
ImmediateInterruptOK = false; /* not idle anymore */
DisableNotifyInterrupt();
*************** PostgresMain(int argc, char *argv[], con
*** 3728,3737 ****
/*
* Forget any pending QueryCancel request, since we're returning to
! * the idle loop anyway, and cancel the statement timer if running.
*/
QueryCancelPending = false;
! disable_sig_alarm(true);
QueryCancelPending = false; /* again in case timeout occurred */
/*
--- 3729,3738 ----
/*
* Forget any pending QueryCancel request, since we're returning to
! * the idle loop anyway, and cancel the timer if running.
*/
QueryCancelPending = false;
! disable_all_timeouts(false);
QueryCancelPending = false; /* again in case timeout occurred */
/*
diff -dcrpN postgresql.orig/src/backend/utils/adt/lockfuncs.c postgresql/src/backend/utils/adt/lockfuncs.c
*** postgresql.orig/src/backend/utils/adt/lockfuncs.c 2012-01-02 12:35:11.533185089 +0100
--- postgresql/src/backend/utils/adt/lockfuncs.c 2012-04-04 11:24:59.796965393 +0200
*************** pg_advisory_lock_int8(PG_FUNCTION_ARGS)
*** 421,427 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ExclusiveLock, true, false);
PG_RETURN_VOID();
}
--- 421,431 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ExclusiveLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_int8(PG_FUNCTION_A
*** 438,444 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
PG_RETURN_VOID();
}
--- 442,452 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_shared_int8(PG_FUNCTION
*** 454,460 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ShareLock, true, false);
PG_RETURN_VOID();
}
--- 462,472 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ShareLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_shared_int8(PG_FUN
*** 471,477 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ShareLock, false, false);
PG_RETURN_VOID();
}
--- 483,493 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_int4(PG_FUNCTION_ARGS)
*** 604,610 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ExclusiveLock, true, false);
PG_RETURN_VOID();
}
--- 620,630 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ExclusiveLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_int4(PG_FUNCTION_A
*** 622,628 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
PG_RETURN_VOID();
}
--- 642,652 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_shared_int4(PG_FUNCTION
*** 639,645 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ShareLock, true, false);
PG_RETURN_VOID();
}
--- 663,673 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ShareLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_shared_int4(PG_FUN
*** 657,663 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ShareLock, false, false);
PG_RETURN_VOID();
}
--- 685,695 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
diff -dcrpN postgresql.orig/src/backend/utils/init/postinit.c postgresql/src/backend/utils/init/postinit.c
*** postgresql.orig/src/backend/utils/init/postinit.c 2012-01-20 13:59:24.913240794 +0100
--- postgresql/src/backend/utils/init/postinit.c 2012-04-04 15:08:31.671944981 +0200
***************
*** 41,51 ****
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lmgr.h"
- #include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "storage/smgr.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
#include "utils/fmgroids.h"
--- 41,51 ----
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lmgr.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "storage/smgr.h"
+ #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
#include "utils/fmgroids.h"
*************** PerformAuthentication(Port *port)
*** 204,210 ****
* during authentication. Since we're inside a transaction and might do
* database access, we have to use the statement_timeout infrastructure.
*/
! if (!enable_sig_alarm(AuthenticationTimeout * 1000, true))
elog(FATAL, "could not set timer for authorization timeout");
/*
--- 204,210 ----
* during authentication. Since we're inside a transaction and might do
* database access, we have to use the statement_timeout infrastructure.
*/
! if (!enable_timeout(STATEMENT_TIMEOUT, AuthenticationTimeout * 1000))
elog(FATAL, "could not set timer for authorization timeout");
/*
*************** PerformAuthentication(Port *port)
*** 215,221 ****
/*
* Done with authentication. Disable the timeout, and log if needed.
*/
! if (!disable_sig_alarm(true))
elog(FATAL, "could not disable timer for authorization timeout");
if (Log_connections)
--- 215,221 ----
/*
* Done with authentication. Disable the timeout, and log if needed.
*/
! if (!disable_timeout(STATEMENT_TIMEOUT, false))
elog(FATAL, "could not disable timer for authorization timeout");
if (Log_connections)
diff -dcrpN postgresql.orig/src/backend/utils/misc/guc.c postgresql/src/backend/utils/misc/guc.c
*** postgresql.orig/src/backend/utils/misc/guc.c 2012-03-28 10:54:25.196368452 +0200
--- postgresql/src/backend/utils/misc/guc.c 2012-04-04 15:09:07.395124656 +0200
***************
*** 63,68 ****
--- 63,69 ----
#include "storage/standby.h"
#include "storage/fd.h"
#include "storage/predicate.h"
+ #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "tsearch/ts_cache.h"
#include "utils/builtins.h"
*************** static struct config_int ConfigureNamesI
*** 1858,1863 ****
--- 1859,1875 ----
0, 0, INT_MAX,
NULL, NULL, NULL
},
+
+ {
+ {"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,
diff -dcrpN postgresql.orig/src/backend/utils/misc/postgresql.conf.sample postgresql/src/backend/utils/misc/postgresql.conf.sample
*** postgresql.orig/src/backend/utils/misc/postgresql.conf.sample 2012-03-28 10:54:25.197368458 +0200
--- postgresql/src/backend/utils/misc/postgresql.conf.sample 2012-04-03 15:29:03.725060295 +0200
***************
*** 527,532 ****
--- 527,535 ----
#------------------------------------------------------------------------------
#deadlock_timeout = 1s
+ #lock_timeout = 0 # timeout value for heavy-weight locks
+ # taken by statements. 0 disables timeout
+ # unit in milliseconds, default is 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 -dcrpN postgresql.orig/src/include/storage/pg_sema.h postgresql/src/include/storage/pg_sema.h
*** postgresql.orig/src/include/storage/pg_sema.h 2012-01-02 12:35:11.640178818 +0100
--- postgresql/src/include/storage/pg_sema.h 2012-04-03 15:30:52.716621135 +0200
*************** extern void PGSemaphoreUnlock(PGSemaphor
*** 80,83 ****
--- 80,86 ----
/* 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 */
+ extern void PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK);
+
#endif /* PG_SEMA_H */
diff -dcrpN postgresql.orig/src/include/storage/proc.h postgresql/src/include/storage/proc.h
*** postgresql.orig/src/include/storage/proc.h 2012-01-31 09:43:43.936392561 +0100
--- postgresql/src/include/storage/proc.h 2012-04-04 15:40:27.595781818 +0200
*************** extern PGPROC *PreparedXactProcs;
*** 215,227 ****
/* configurable options */
- extern int DeadlockTimeout;
- extern int StatementTimeout;
extern bool log_lock_waits;
- extern volatile bool cancel_from_timeout;
-
-
/*
* Function Prototypes
*/
--- 215,222 ----
*************** extern void LockWaitCancel(void);
*** 249,261 ****
extern void ProcWaitForSignal(void);
extern void ProcSendSignal(int pid);
- extern bool enable_sig_alarm(int delayms, bool is_statement_timeout);
- extern bool disable_sig_alarm(bool is_statement_timeout);
- extern void handle_sig_alarm(SIGNAL_ARGS);
-
- extern bool enable_standby_sig_alarm(TimestampTz now,
- TimestampTz fin_time, bool deadlock_only);
- extern bool disable_standby_sig_alarm(void);
- extern void handle_standby_sig_alarm(SIGNAL_ARGS);
-
#endif /* PROC_H */
--- 244,247 ----
diff -dcrpN postgresql.orig/src/include/storage/timeout.h postgresql/src/include/storage/timeout.h
*** postgresql.orig/src/include/storage/timeout.h 1970-01-01 01:00:00.000000000 +0100
--- postgresql/src/include/storage/timeout.h 2012-04-04 15:02:21.255081715 +0200
***************
*** 0 ****
--- 1,40 ----
+ /*-------------------------------------------------------------------------
+ *
+ * timeout.h
+ * SIGALRM timeout API
+ *
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/storage/timeout.h
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef _TIMEOUT_H_
+ #define _TIMEOUT_H_
+
+ #include "datatype/timestamp.h"
+
+ /* configurable options */
+ extern int DeadlockTimeout;
+ extern int StatementTimeout;
+ extern int LockTimeout;
+
+ typedef enum TimeoutName {
+ DEADLOCK_TIMEOUT,
+ LOCK_TIMEOUT,
+ STATEMENT_TIMEOUT,
+ STANDBY_DEADLOCK_TIMEOUT,
+ STANDBY_TIMEOUT,
+ TIMEOUT_MAX
+ } TimeoutName;
+
+ extern bool enable_timeout(TimeoutName tn, int delayms);
+ extern bool disable_timeout(TimeoutName tn, bool keep_indicator);
+ extern bool disable_all_timeouts(bool keep_indicators);
+ extern bool get_timeout_indicator(TimeoutName tn);
+ extern TimestampTz get_timeout_start(TimeoutName tn);
+ extern void handle_sig_alarm(SIGNAL_ARGS);
+
+ #endif /* _TIMEOUT_H_ */
diff -dcrpN postgresql.orig/src/test/regress/expected/prepared_xacts.out postgresql/src/test/regress/expected/prepared_xacts.out
*** postgresql.orig/src/test/regress/expected/prepared_xacts.out 2011-08-16 09:39:01.117194748 +0200
--- postgresql/src/test/regress/expected/prepared_xacts.out 2012-04-04 12:22:18.719786919 +0200
*************** set statement_timeout to 2000;
*** 198,203 ****
--- 198,210 ----
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: could not obtain lock on relation "pxtest3"
+ LINE 1: SELECT * FROM pxtest3;
+ ^
+ reset lock_timeout;
-- Disconnect, we will continue testing in a different backend
\c -
-- There should still be two prepared transactions
*************** set statement_timeout to 2000;
*** 213,218 ****
--- 220,232 ----
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: could not obtain lock on relation "pxtest3"
+ LINE 1: SELECT * FROM pxtest3;
+ ^
+ reset lock_timeout;
-- Commit table creation
COMMIT PREPARED 'regress-one';
\d pxtest2
diff -dcrpN postgresql.orig/src/test/regress/sql/prepared_xacts.sql postgresql/src/test/regress/sql/prepared_xacts.sql
*** postgresql.orig/src/test/regress/sql/prepared_xacts.sql 2011-07-18 15:42:00.117371861 +0200
--- postgresql/src/test/regress/sql/prepared_xacts.sql 2012-04-04 12:20:31.456241386 +0200
*************** set statement_timeout to 2000;
*** 126,131 ****
--- 126,136 ----
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 -
*************** set statement_timeout to 2000;
*** 137,142 ****
--- 142,152 ----
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
2012-04-04 17:12 keltezéssel, Boszormenyi Zoltan írta:
2012-04-04 16:22 keltezéssel, Boszormenyi Zoltan írta:
2012-04-04 15:17 keltezéssel, Boszormenyi Zoltan írta:
Hi,
2012-04-04 12:30 keltezéssel, Boszormenyi Zoltan írta:
Hi,
attached is a patch to implement a framework to simplify and
correctly nest multiplexing more than two timeout sources
into the same SIGALRM signal handler.The framework is using a new internal API for timeouts:
bool enable_timeout(TimeoutName tn, int delayms);
bool disable_timeout(TimeoutName tn, bool keep_indicator);
bool disable_all_timeouts(bool keep_indicators);A timeout source has these features to allow different initialization,
cleanup and check functions and rescheduling:typedef void (*timeout_init)(TimestampTz, TimestampTz);
typedef void (*timeout_destroy)(bool);
typedef bool (*timeout_check)(void);
typedef TimestampTz (*timeout_start)(void);typedef struct {
TimeoutName index;
bool resched_next;
timeout_init timeout_init;
timeout_destroy timeout_destroy;
timeout_check timeout_check;
timeout_start timeout_start;
TimestampTz fin_time;
} timeout_params;This makes it possible to differentiate between the standby and
statement timeouts, regular deadlock and standby deadlock using
the same signal handler function.And finally, this makes it possible to implement the lock_timeout
feature that we at Cybertec implemented more than 2 years ago.The patch also adds two new tests into prepared_xacts.sql to trigger
the lock_timeout instead of statement_timeout.Documentation and extensive comments are included.
Second version. Every timeout-related functions are now in a separate
source file, src/backend/storage/timeout.c with accessor functions.
There are no related global variables anymore, only the GUCs.3rd and (for now) final version.
I lied. This is the final one. I fixed a typo in the documentation
and replaced timeout_start_time (previously static to proc.c)
with get_timeout_start(DEADLOCK_TIMEOUT). This one should
have happened in the second version.Tidied comments, the time checks in
Check*() functions and function order in timeout.c.Best regards,
Zoltán Böszörményi
One comment for testers: all the timeout GUC values are given in
milliseconds, the kernel interface (setitimer) and TimestampTz uses
microseconds.
The transaction that locks is inherently a read/write one and by the time
the code reaches ProcSleep(), at least a few tens of microseconds has
already passed since the start of the statement even on 3GHz+ CPUs.
Not to mention that computers nowadays have high precision timers
and OSs running on them utilitize those. So, the time returned by
GetCurrentStatementStartTimestamp() will certainly be different from
GetCurrentTimestamp(). This means that the timeouts' fin_time will also
be different.
Long story short, using the same value for statement_timeout and
lock_timeout (or deadlock_timeout for that matter) means that
statement_timeout will trigger first. The story is different only on
a combination of a fast CPU and an OS with greater-then-millisecond
timer resolution.
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/
I think this patch is doing two things: first touching infrastructure
stuff and then adding lock_timeout on top of that. Would it work to
split the patch in two pieces?
--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support
2012-04-04 21:30 keltezéssel, Alvaro Herrera írta:
I think this patch is doing two things: first touching infrastructure
stuff and then adding lock_timeout on top of that. Would it work to
split the patch in two pieces?
Sure. Attached is the split version.
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/
Attachments:
1-timeout-framework.patchtext/x-patch; name=1-timeout-framework.patchDownload
diff -dcrpN postgresql.orig/src/backend/postmaster/autovacuum.c postgresql.newtimeout/src/backend/postmaster/autovacuum.c
*** postgresql.orig/src/backend/postmaster/autovacuum.c 2012-01-27 10:29:06.483649830 +0100
--- postgresql.newtimeout/src/backend/postmaster/autovacuum.c 2012-04-05 07:13:04.274287858 +0200
***************
*** 86,94 ****
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
- #include "storage/proc.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "tcop/tcopprot.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
--- 86,94 ----
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
+ #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
*************** AutoVacLauncherMain(int argc, char *argv
*** 484,490 ****
/* Forget any pending QueryCancel request */
QueryCancelPending = false;
! disable_sig_alarm(true);
QueryCancelPending = false; /* again in case timeout occurred */
/* Report the error to the server log */
--- 484,490 ----
/* Forget any pending QueryCancel request */
QueryCancelPending = false;
! disable_all_timeouts(false);
QueryCancelPending = false; /* again in case timeout occurred */
/* Report the error to the server log */
diff -dcrpN postgresql.orig/src/backend/postmaster/postmaster.c postgresql.newtimeout/src/backend/postmaster/postmaster.c
*** postgresql.orig/src/backend/postmaster/postmaster.c 2012-03-29 08:36:59.775468454 +0200
--- postgresql.newtimeout/src/backend/postmaster/postmaster.c 2012-04-05 07:13:04.277287873 +0200
***************
*** 112,118 ****
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
! #include "storage/proc.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/datetime.h"
--- 112,118 ----
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
! #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/datetime.h"
*************** BackendInitialize(Port *port)
*** 3466,3474 ****
* Ready to begin client interaction. We will give up and exit(1) after a
* time delay, so that a broken client can't hog a connection
* indefinitely. PreAuthDelay and any DNS interactions above don't count
! * against the time limit.
*/
! if (!enable_sig_alarm(AuthenticationTimeout * 1000, false))
elog(FATAL, "could not set timer for startup packet timeout");
/*
--- 3466,3474 ----
* Ready to begin client interaction. We will give up and exit(1) after a
* time delay, so that a broken client can't hog a connection
* indefinitely. PreAuthDelay and any DNS interactions above don't count
! * against the time limit. Use the deadlock timeout interface.
*/
! if (!enable_timeout(DEADLOCK_TIMEOUT, AuthenticationTimeout * 1000))
elog(FATAL, "could not set timer for startup packet timeout");
/*
*************** BackendInitialize(Port *port)
*** 3506,3512 ****
/*
* Disable the timeout, and prevent SIGTERM/SIGQUIT again.
*/
! if (!disable_sig_alarm(false))
elog(FATAL, "could not disable timer for startup packet timeout");
PG_SETMASK(&BlockSig);
}
--- 3506,3512 ----
/*
* Disable the timeout, and prevent SIGTERM/SIGQUIT again.
*/
! if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for startup packet timeout");
PG_SETMASK(&BlockSig);
}
diff -dcrpN postgresql.orig/src/backend/postmaster/startup.c postgresql.newtimeout/src/backend/postmaster/startup.c
*** postgresql.orig/src/backend/postmaster/startup.c 2012-01-02 12:35:11.508186555 +0100
--- postgresql.newtimeout/src/backend/postmaster/startup.c 2012-04-05 07:13:04.278287879 +0200
***************
*** 27,33 ****
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
! #include "storage/proc.h"
#include "utils/guc.h"
--- 27,33 ----
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
! #include "storage/timeout.h"
#include "utils/guc.h"
*************** StartupProcessMain(void)
*** 195,202 ****
pqsignal(SIGTERM, StartupProcShutdownHandler); /* request shutdown */
pqsignal(SIGQUIT, startupproc_quickdie); /* hard crash time */
if (EnableHotStandby)
! pqsignal(SIGALRM, handle_standby_sig_alarm); /* ignored unless
! * InHotStandby */
else
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
--- 195,202 ----
pqsignal(SIGTERM, StartupProcShutdownHandler); /* request shutdown */
pqsignal(SIGQUIT, startupproc_quickdie); /* hard crash time */
if (EnableHotStandby)
! pqsignal(SIGALRM, handle_sig_alarm); /* ignored unless
! * InHotStandby */
else
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
diff -dcrpN postgresql.orig/src/backend/storage/ipc/standby.c postgresql.newtimeout/src/backend/storage/ipc/standby.c
*** postgresql.orig/src/backend/storage/ipc/standby.c 2012-02-05 12:28:36.003281960 +0100
--- postgresql.newtimeout/src/backend/storage/ipc/standby.c 2012-04-05 07:13:04.279287884 +0200
***************
*** 23,32 ****
#include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
- #include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/sinvaladt.h"
#include "storage/standby.h"
#include "utils/ps_status.h"
#include "utils/timestamp.h"
--- 23,32 ----
#include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
#include "storage/procarray.h"
#include "storage/sinvaladt.h"
#include "storage/standby.h"
+ #include "storage/timeout.h"
#include "utils/ps_status.h"
#include "utils/timestamp.h"
*************** ResolveRecoveryConflictWithLock(Oid dbOi
*** 394,400 ****
void
ResolveRecoveryConflictWithBufferPin(void)
{
- bool sig_alarm_enabled = false;
TimestampTz ltime;
TimestampTz now;
--- 394,399 ----
*************** ResolveRecoveryConflictWithBufferPin(voi
*** 409,417 ****
* We're willing to wait forever for conflicts, so set timeout for
* deadlock check (only)
*/
! if (enable_standby_sig_alarm(now, now, true))
! sig_alarm_enabled = true;
! else
elog(FATAL, "could not set timer for process wakeup");
}
else if (now >= ltime)
--- 408,414 ----
* We're willing to wait forever for conflicts, so set timeout for
* deadlock check (only)
*/
! if (!enable_timeout(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout))
elog(FATAL, "could not set timer for process wakeup");
}
else if (now >= ltime)
*************** ResolveRecoveryConflictWithBufferPin(voi
*** 423,446 ****
}
else
{
/*
* Wake up at ltime, and check for deadlocks as well if we will be
* waiting longer than deadlock_timeout
*/
! if (enable_standby_sig_alarm(now, ltime, false))
! sig_alarm_enabled = true;
! else
elog(FATAL, "could not set timer for process wakeup");
}
/* Wait to be signaled by UnpinBuffer() */
ProcWaitForSignal();
! if (sig_alarm_enabled)
! {
! if (!disable_standby_sig_alarm())
! elog(FATAL, "could not disable timer for process wakeup");
! }
}
void
--- 420,447 ----
}
else
{
+ long secs, msecs;
+ int usecs;
+
/*
* Wake up at ltime, and check for deadlocks as well if we will be
* waiting longer than deadlock_timeout
*/
! if (!enable_timeout(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout))
! elog(FATAL, "could not set timer for process wakeup");
!
! TimestampDifference(now, ltime, &secs, &usecs);
! msecs = secs * 1000 + usecs / 1000;
!
! if (!enable_timeout(STANDBY_TIMEOUT, msecs))
elog(FATAL, "could not set timer for process wakeup");
}
/* Wait to be signaled by UnpinBuffer() */
ProcWaitForSignal();
! if (!disable_all_timeouts(false))
! elog(FATAL, "could not disable timer for process wakeup");
}
void
diff -dcrpN postgresql.orig/src/backend/storage/lmgr/Makefile postgresql.newtimeout/src/backend/storage/lmgr/Makefile
*** postgresql.orig/src/backend/storage/lmgr/Makefile 2011-02-10 10:36:32.248685484 +0100
--- postgresql.newtimeout/src/backend/storage/lmgr/Makefile 2012-04-05 07:13:04.283287904 +0200
*************** subdir = src/backend/storage/lmgr
*** 12,18 ****
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
! OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o predicate.o
include $(top_srcdir)/src/backend/common.mk
--- 12,18 ----
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
! OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o predicate.o timeout.o
include $(top_srcdir)/src/backend/common.mk
diff -dcrpN postgresql.orig/src/backend/storage/lmgr/proc.c postgresql.newtimeout/src/backend/storage/lmgr/proc.c
*** postgresql.orig/src/backend/storage/lmgr/proc.c 2012-03-22 11:00:51.723002056 +0100
--- postgresql.newtimeout/src/backend/storage/lmgr/proc.c 2012-04-05 07:23:21.664367565 +0200
***************
*** 48,59 ****
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/spin.h"
#include "utils/timestamp.h"
/* GUC variables */
- int DeadlockTimeout = 1000;
- int StatementTimeout = 0;
bool log_lock_waits = false;
/* Pointer to this process's PGPROC struct, if any */
--- 48,58 ----
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/spin.h"
+ #include "storage/timeout.h"
#include "utils/timestamp.h"
/* GUC variables */
bool log_lock_waits = false;
/* Pointer to this process's PGPROC struct, if any */
*************** PGPROC *PreparedXactProcs = NULL;
*** 77,103 ****
/* If we are waiting for a lock, this points to the associated LOCALLOCK */
static LOCALLOCK *lockAwaited = NULL;
! /* Mark these volatile because they can be changed by signal handler */
! static volatile bool standby_timeout_active = false;
! static volatile bool statement_timeout_active = false;
! static volatile bool deadlock_timeout_active = false;
! static volatile DeadLockState deadlock_state = DS_NOT_YET_CHECKED;
! volatile bool cancel_from_timeout = false;
!
! /* timeout_start_time is set when log_lock_waits is true */
! static TimestampTz timeout_start_time;
!
! /* statement_fin_time is valid only if statement_timeout_active is true */
! static TimestampTz statement_fin_time;
! static TimestampTz statement_fin_time2; /* valid only in recovery */
!
static void RemoveProcFromArray(int code, Datum arg);
static void ProcKill(int code, Datum arg);
static void AuxiliaryProcKill(int code, Datum arg);
- static bool CheckStatementTimeout(void);
- static bool CheckStandbyTimeout(void);
-
/*
* Report shared-memory space needed by InitProcGlobal.
--- 76,87 ----
/* If we are waiting for a lock, this points to the associated LOCALLOCK */
static LOCALLOCK *lockAwaited = NULL;
! /* Declared in timeout.c */
! extern volatile DeadLockState deadlock_state;
static void RemoveProcFromArray(int code, Datum arg);
static void ProcKill(int code, Datum arg);
static void AuxiliaryProcKill(int code, Datum arg);
/*
* Report shared-memory space needed by InitProcGlobal.
*************** LockWaitCancel(void)
*** 651,657 ****
return;
/* Turn off the deadlock timer, if it's still running (see ProcSleep) */
! disable_sig_alarm(false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
--- 635,641 ----
return;
/* Turn off the deadlock timer, if it's still running (see ProcSleep) */
! disable_timeout(DEADLOCK_TIMEOUT, false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1044,1050 ****
* 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 (!enable_sig_alarm(DeadlockTimeout, false))
elog(FATAL, "could not set timer for process wakeup");
/*
--- 1028,1034 ----
* 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 (!enable_timeout(DEADLOCK_TIMEOUT, DeadlockTimeout))
elog(FATAL, "could not set timer for process wakeup");
/*
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1134,1140 ****
DescribeLockTag(&buf, &locallock->tag.lock);
modename = GetLockmodeName(locallock->tag.lock.locktag_lockmethodid,
lockmode);
! TimestampDifference(timeout_start_time, GetCurrentTimestamp(),
&secs, &usecs);
msecs = secs * 1000 + usecs / 1000;
usecs = usecs % 1000;
--- 1118,1125 ----
DescribeLockTag(&buf, &locallock->tag.lock);
modename = GetLockmodeName(locallock->tag.lock.locktag_lockmethodid,
lockmode);
! TimestampDifference(get_timeout_start(DEADLOCK_TIMEOUT),
! GetCurrentTimestamp(),
&secs, &usecs);
msecs = secs * 1000 + usecs / 1000;
usecs = usecs % 1000;
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1196,1202 ****
/*
* Disable the timer, if it's still running
*/
! if (!disable_sig_alarm(false))
elog(FATAL, "could not disable timer for process wakeup");
/*
--- 1181,1187 ----
/*
* Disable the timer, if it's still running
*/
! if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for process wakeup");
/*
*************** ProcLockWakeup(LockMethod lockMethodTabl
*** 1328,1448 ****
}
/*
- * CheckDeadLock
- *
- * We only get to this routine if we got SIGALRM after DeadlockTimeout
- * while waiting for a lock to be released by some other process. Look
- * to see if there's a deadlock; if not, just return and continue waiting.
- * (But signal ProcSleep to log a message, if log_lock_waits is true.)
- * If we have a real deadlock, remove ourselves from the lock's wait queue
- * and signal an error to ProcSleep.
- *
- * NB: this is run inside a signal handler, so be very wary about what is done
- * here or in called routines.
- */
- static void
- CheckDeadLock(void)
- {
- int i;
-
- /*
- * Acquire exclusive lock on the entire shared lock data structures. Must
- * grab LWLocks in partition-number order to avoid LWLock deadlock.
- *
- * Note that the deadlock check interrupt had better not be enabled
- * anywhere that this process itself holds lock partition locks, else this
- * will wait forever. Also note that LWLockAcquire creates a critical
- * section, so that this routine cannot be interrupted by cancel/die
- * interrupts.
- */
- for (i = 0; i < NUM_LOCK_PARTITIONS; i++)
- LWLockAcquire(FirstLockMgrLock + i, LW_EXCLUSIVE);
-
- /*
- * Check to see if we've been awoken by anyone in the interim.
- *
- * If we have, we can return and resume our transaction -- happy day.
- * Before we are awoken the process releasing the lock grants it to us so
- * we know that we don't have to wait anymore.
- *
- * We check by looking to see if we've been unlinked from the wait queue.
- * This is quicker than checking our semaphore's state, since no kernel
- * call is needed, and it is safe because we hold the lock partition lock.
- */
- if (MyProc->links.prev == NULL ||
- MyProc->links.next == NULL)
- goto check_done;
-
- #ifdef LOCK_DEBUG
- if (Debug_deadlocks)
- DumpAllLocks();
- #endif
-
- /* Run the deadlock check, and set deadlock_state for use by ProcSleep */
- deadlock_state = DeadLockCheck(MyProc);
-
- if (deadlock_state == DS_HARD_DEADLOCK)
- {
- /*
- * Oops. We have a deadlock.
- *
- * Get this process out of wait state. (Note: we could do this more
- * efficiently by relying on lockAwaited, but use this coding to
- * preserve the flexibility to kill some other transaction than the
- * one detecting the deadlock.)
- *
- * RemoveFromWaitQueue sets MyProc->waitStatus to STATUS_ERROR, so
- * ProcSleep will report an error after we return from the signal
- * handler.
- */
- Assert(MyProc->waitLock != NULL);
- RemoveFromWaitQueue(MyProc, LockTagHashCode(&(MyProc->waitLock->tag)));
-
- /*
- * Unlock my semaphore so that the interrupted ProcSleep() call can
- * finish.
- */
- PGSemaphoreUnlock(&MyProc->sem);
-
- /*
- * We're done here. Transaction abort caused by the error that
- * ProcSleep will raise will cause any other locks we hold to be
- * released, thus allowing other processes to wake up; we don't need
- * to do that here. NOTE: an exception is that releasing locks we
- * hold doesn't consider the possibility of waiters that were blocked
- * behind us on the lock we just failed to get, and might now be
- * wakable because we're not in front of them anymore. However,
- * RemoveFromWaitQueue took care of waking up any such processes.
- */
- }
- else if (log_lock_waits || deadlock_state == DS_BLOCKED_BY_AUTOVACUUM)
- {
- /*
- * Unlock my semaphore so that the interrupted ProcSleep() call can
- * print the log message (we daren't do it here because we are inside
- * a signal handler). It will then sleep again until someone releases
- * the lock.
- *
- * If blocked by autovacuum, this wakeup will enable ProcSleep to send
- * the canceling signal to the autovacuum worker.
- */
- PGSemaphoreUnlock(&MyProc->sem);
- }
-
- /*
- * And release locks. We do this in reverse order for two reasons: (1)
- * Anyone else who needs more than one of the locks will be trying to lock
- * them in increasing order; we don't want to release the other process
- * until it can get all the locks it needs. (2) This avoids O(N^2)
- * behavior inside LWLockRelease.
- */
- check_done:
- for (i = NUM_LOCK_PARTITIONS; --i >= 0;)
- LWLockRelease(FirstLockMgrLock + i);
- }
-
-
- /*
* ProcWaitForSignal - wait for a signal from another backend.
*
* This can share the semaphore normally used for waiting for locks,
--- 1313,1318 ----
*************** ProcSendSignal(int pid)
*** 1494,1894 ****
if (proc != NULL)
PGSemaphoreUnlock(&proc->sem);
}
-
-
- /*****************************************************************************
- * SIGALRM interrupt support
- *
- * Maybe these should be in pqsignal.c?
- *****************************************************************************/
-
- /*
- * Enable the SIGALRM interrupt to fire after the specified delay
- *
- * Delay is given in milliseconds. Caller should be sure a SIGALRM
- * signal handler is installed before this is called.
- *
- * This code properly handles nesting of deadlock timeout alarms within
- * statement timeout alarms.
- *
- * Returns TRUE if okay, FALSE on failure.
- */
- bool
- enable_sig_alarm(int delayms, bool is_statement_timeout)
- {
- TimestampTz fin_time;
- struct itimerval timeval;
-
- if (is_statement_timeout)
- {
- /*
- * Begin statement-level timeout
- *
- * Note that we compute statement_fin_time with reference to the
- * statement_timestamp, but apply the specified delay without any
- * correction; that is, we ignore whatever time has elapsed since
- * statement_timestamp was set. In the normal case only a small
- * interval will have elapsed and so this doesn't matter, but there
- * are corner cases (involving multi-statement query strings with
- * embedded COMMIT or ROLLBACK) where we might re-initialize the
- * statement timeout long after initial receipt of the message. In
- * such cases the enforcement of the statement timeout will be a bit
- * inconsistent. This annoyance is judged not worth the cost of
- * performing an additional gettimeofday() here.
- */
- Assert(!deadlock_timeout_active);
- fin_time = GetCurrentStatementStartTimestamp();
- fin_time = TimestampTzPlusMilliseconds(fin_time, delayms);
- statement_fin_time = fin_time;
- cancel_from_timeout = false;
- statement_timeout_active = true;
- }
- else if (statement_timeout_active)
- {
- /*
- * Begin deadlock timeout with statement-level timeout active
- *
- * Here, we want to interrupt at the closer of the two timeout times.
- * If fin_time >= statement_fin_time then we need not touch the
- * existing timer setting; else set up to interrupt at the deadlock
- * timeout time.
- *
- * NOTE: in this case it is possible that this routine will be
- * interrupted by the previously-set timer alarm. This is okay
- * because the signal handler will do only what it should do according
- * to the state variables. The deadlock checker may get run earlier
- * than normal, but that does no harm.
- */
- timeout_start_time = GetCurrentTimestamp();
- fin_time = TimestampTzPlusMilliseconds(timeout_start_time, delayms);
- deadlock_timeout_active = true;
- if (fin_time >= statement_fin_time)
- return true;
- }
- else
- {
- /* Begin deadlock timeout with no statement-level timeout */
- deadlock_timeout_active = true;
- /* GetCurrentTimestamp can be expensive, so only do it if we must */
- if (log_lock_waits)
- timeout_start_time = GetCurrentTimestamp();
- }
-
- /* If we reach here, okay to set the timer interrupt */
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = delayms / 1000;
- timeval.it_value.tv_usec = (delayms % 1000) * 1000;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- return true;
- }
-
- /*
- * Cancel the SIGALRM timer, either for a deadlock timeout or a statement
- * timeout. If a deadlock timeout is canceled, any active statement timeout
- * remains in force.
- *
- * Returns TRUE if okay, FALSE on failure.
- */
- bool
- disable_sig_alarm(bool is_statement_timeout)
- {
- /*
- * Always disable the interrupt if it is active; this avoids being
- * interrupted by the signal handler and thereby possibly getting
- * confused.
- *
- * We will re-enable the interrupt if necessary in CheckStatementTimeout.
- */
- if (statement_timeout_active || deadlock_timeout_active)
- {
- struct itimerval timeval;
-
- MemSet(&timeval, 0, sizeof(struct itimerval));
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- {
- statement_timeout_active = false;
- cancel_from_timeout = false;
- deadlock_timeout_active = false;
- return false;
- }
- }
-
- /* Always cancel deadlock timeout, in case this is error cleanup */
- deadlock_timeout_active = false;
-
- /* Cancel or reschedule statement timeout */
- if (is_statement_timeout)
- {
- statement_timeout_active = false;
- cancel_from_timeout = false;
- }
- else if (statement_timeout_active)
- {
- if (!CheckStatementTimeout())
- return false;
- }
- return true;
- }
-
-
- /*
- * Check for statement timeout. If the timeout time has come,
- * trigger a query-cancel interrupt; if not, reschedule the SIGALRM
- * interrupt to occur at the right time.
- *
- * Returns true if okay, false if failed to set the interrupt.
- */
- static bool
- CheckStatementTimeout(void)
- {
- TimestampTz now;
-
- if (!statement_timeout_active)
- return true; /* do nothing if not active */
-
- now = GetCurrentTimestamp();
-
- if (now >= statement_fin_time)
- {
- /* Time to die */
- statement_timeout_active = false;
- cancel_from_timeout = true;
- #ifdef HAVE_SETSID
- /* try to signal whole process group */
- kill(-MyProcPid, SIGINT);
- #endif
- kill(MyProcPid, SIGINT);
- }
- else
- {
- /* Not time yet, so (re)schedule the interrupt */
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
-
- /*
- * It's possible that the difference is less than a microsecond;
- * ensure we don't cancel, rather than set, the interrupt.
- */
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- }
-
- return true;
- }
-
-
- /*
- * Signal handler for SIGALRM for normal user backends
- *
- * Process deadlock check and/or statement timeout check, as needed.
- * To avoid various edge cases, we must be careful to do nothing
- * when there is nothing to be done. We also need to be able to
- * reschedule the timer interrupt if called before end of statement.
- */
- void
- handle_sig_alarm(SIGNAL_ARGS)
- {
- int save_errno = errno;
-
- /* SIGALRM is cause for waking anything waiting on the process latch */
- if (MyProc)
- SetLatch(&MyProc->procLatch);
-
- if (deadlock_timeout_active)
- {
- deadlock_timeout_active = false;
- CheckDeadLock();
- }
-
- if (statement_timeout_active)
- (void) CheckStatementTimeout();
-
- errno = save_errno;
- }
-
- /*
- * Signal handler for SIGALRM in Startup process
- *
- * To avoid various edge cases, we must be careful to do nothing
- * when there is nothing to be done. We also need to be able to
- * reschedule the timer interrupt if called before end of statement.
- *
- * We set either deadlock_timeout_active or statement_timeout_active
- * or both. Interrupts are enabled if standby_timeout_active.
- */
- bool
- enable_standby_sig_alarm(TimestampTz now, TimestampTz fin_time, bool deadlock_only)
- {
- TimestampTz deadlock_time = TimestampTzPlusMilliseconds(now,
- DeadlockTimeout);
-
- if (deadlock_only)
- {
- /*
- * Wake up at deadlock_time only, then wait forever
- */
- statement_fin_time = deadlock_time;
- deadlock_timeout_active = true;
- statement_timeout_active = false;
- }
- else if (fin_time > deadlock_time)
- {
- /*
- * Wake up at deadlock_time, then again at fin_time
- */
- statement_fin_time = deadlock_time;
- statement_fin_time2 = fin_time;
- deadlock_timeout_active = true;
- statement_timeout_active = true;
- }
- else
- {
- /*
- * Wake only at fin_time because its fairly soon
- */
- statement_fin_time = fin_time;
- deadlock_timeout_active = false;
- statement_timeout_active = true;
- }
-
- if (deadlock_timeout_active || statement_timeout_active)
- {
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- standby_timeout_active = true;
- }
-
- return true;
- }
-
- bool
- disable_standby_sig_alarm(void)
- {
- /*
- * Always disable the interrupt if it is active; this avoids being
- * interrupted by the signal handler and thereby possibly getting
- * confused.
- *
- * We will re-enable the interrupt if necessary in CheckStandbyTimeout.
- */
- if (standby_timeout_active)
- {
- struct itimerval timeval;
-
- MemSet(&timeval, 0, sizeof(struct itimerval));
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- {
- standby_timeout_active = false;
- return false;
- }
- }
-
- standby_timeout_active = false;
-
- return true;
- }
-
- /*
- * CheckStandbyTimeout() runs unconditionally in the Startup process
- * SIGALRM handler. Timers will only be set when InHotStandby.
- * We simply ignore any signals unless the timer has been set.
- */
- static bool
- CheckStandbyTimeout(void)
- {
- TimestampTz now;
- bool reschedule = false;
-
- standby_timeout_active = false;
-
- now = GetCurrentTimestamp();
-
- /*
- * Reschedule the timer if its not time to wake yet, or if we have both
- * timers set and the first one has just been reached.
- */
- if (now >= statement_fin_time)
- {
- if (deadlock_timeout_active)
- {
- /*
- * We're still waiting when we reach deadlock timeout, so send out
- * a request to have other backends check themselves for deadlock.
- * Then continue waiting until statement_fin_time, if that's set.
- */
- SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
- deadlock_timeout_active = false;
-
- /*
- * Begin second waiting period if required.
- */
- if (statement_timeout_active)
- {
- reschedule = true;
- statement_fin_time = statement_fin_time2;
- }
- }
- else
- {
- /*
- * We've now reached statement_fin_time, so ask all conflicts to
- * leave, so we can press ahead with applying changes in recovery.
- */
- SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
- }
- }
- else
- reschedule = true;
-
- if (reschedule)
- {
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- standby_timeout_active = true;
- }
-
- return true;
- }
-
- void
- handle_standby_sig_alarm(SIGNAL_ARGS)
- {
- int save_errno = errno;
-
- if (standby_timeout_active)
- (void) CheckStandbyTimeout();
-
- errno = save_errno;
- }
--- 1364,1366 ----
diff -dcrpN postgresql.orig/src/backend/storage/lmgr/timeout.c postgresql.newtimeout/src/backend/storage/lmgr/timeout.c
*** postgresql.orig/src/backend/storage/lmgr/timeout.c 1970-01-01 01:00:00.000000000 +0100
--- postgresql.newtimeout/src/backend/storage/lmgr/timeout.c 2012-04-05 07:24:05.672585848 +0200
***************
*** 0 ****
--- 1,680 ----
+ /*-------------------------------------------------------------------------
+ *
+ * timeout.c
+ * routines to manage timeout sources handled by SIGALRM
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/storage/lmgr/timeout.c
+ *
+ *-------------------------------------------------------------------------
+ */
+ /*
+ * Interface:
+ * enable_timeout(),
+ * disable_timeout(), disable_all_timeouts()
+ * get_timeout_indicator(), get_timeout_start()
+ * and
+ * handle_sig_alarm()
+ */
+ #include "postgres.h"
+
+ #include <sys/time.h>
+
+ #include "access/xact.h"
+ #include "miscadmin.h"
+ #include "storage/proc.h"
+ #include "storage/standby.h"
+ #include "storage/timeout.h"
+ #include "utils/timestamp.h"
+
+ /* GUC variables */
+ int DeadlockTimeout = 1000;
+ int StatementTimeout = 0;
+
+ /*
+ * This is used by ProcSleep() in proc.c
+ * Mark this volatile because it can be changed by the signal handler.
+ */
+ volatile DeadLockState deadlock_state = DS_NOT_YET_CHECKED;
+
+
+ /*
+ * Infrastructure for timeouts
+ */
+
+ static void InitDeadLock(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyDeadLock(bool keep_indicator);
+ static bool CheckDeadLock(void);
+
+ static void InitStatementTimeout(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyStatementTimeout(bool keep_indicator);
+ static bool CheckStatementTimeout(void);
+
+ static void InitStandbyDeadLock(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyStandbyDeadLock(bool keep_indicator);
+ static bool CheckStandbyDeadLock(void);
+
+ static void InitStandbyTimeout(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyStandbyTimeout(bool keep_indicator);
+ static bool CheckStandbyTimeout(void);
+
+ typedef void (*timeout_init)(TimestampTz, TimestampTz);
+ typedef void (*timeout_destroy)(bool);
+ typedef bool (*timeout_check)(void);
+ typedef TimestampTz (*timeout_start)(void);
+
+ typedef struct {
+ TimeoutName index;
+ bool resched_next;
+
+ /* volatile because it may be changed from the signal handler */
+ volatile bool indicator;
+
+ timeout_init timeout_init;
+ timeout_destroy timeout_destroy;
+ timeout_check timeout_check;
+ timeout_start timeout_start;
+
+ TimestampTz start_time;
+ TimestampTz fin_time;
+ } timeout_params;
+
+ /*
+ * List of possible timeout reasons in the order of enum TimeoutName.
+ * The priority of timeouts (in case two of them would trigger at the
+ * same time) is determined by this order: the earlier one in the list
+ * has higher priority.
+ */
+ static timeout_params base_timeouts[TIMEOUT_MAX] = {
+ {
+ DEADLOCK_TIMEOUT, true, false,
+ InitDeadLock, DestroyDeadLock,
+ CheckDeadLock, GetCurrentTimestamp,
+ 0
+ },
+
+ {
+ STATEMENT_TIMEOUT, false, false,
+ InitStatementTimeout, DestroyStatementTimeout,
+ CheckStatementTimeout, GetCurrentStatementStartTimestamp,
+ 0
+ },
+
+ {
+ STANDBY_DEADLOCK_TIMEOUT, true, false,
+ InitStandbyDeadLock, DestroyStandbyDeadLock,
+ CheckStandbyDeadLock, GetCurrentTimestamp,
+ 0
+ },
+
+ {
+ STANDBY_TIMEOUT, false, false,
+ InitStandbyTimeout, DestroyStandbyTimeout,
+ CheckStandbyTimeout, GetCurrentTimestamp,
+ 0
+ }
+ };
+
+ /*
+ * List of active timeouts ordered by their fin_time and priority.
+ */
+ static int n_timeouts = 0;
+ static timeout_params *timeouts[TIMEOUT_MAX];
+
+ /*****************************************************************************
+ * Internal helper functions
+ *****************************************************************************/
+
+ /*
+ * Find the index of a given timeout type in the active array
+ */
+ static int
+ find_active_timeout(TimeoutName tn)
+ {
+ int i;
+
+ for (i = 0; i < n_timeouts; i++)
+ {
+ if (timeouts[i]->index == tn)
+ return i;
+ }
+
+ return -1;
+ }
+
+ #define is_timeout_active(tn) (find_active_timeout(tn) >= 0)
+
+ /*
+ * Insert tn'th timeout into the list of timeouts at the given index
+ * if the previous timeout allows rescheduling the next one in the list.
+ */
+ static void
+ insert_timeout(TimeoutName tn, int index)
+ {
+ int i;
+
+ if (index > 0 && !timeouts[index-1]->resched_next)
+ return;
+
+ for (i = n_timeouts - 1; i >= index; i--)
+ timeouts[i+1] = timeouts[i];
+
+ timeouts[index] = &base_timeouts[tn];
+ n_timeouts++;
+ }
+
+ /*
+ * Remove the index'th element from the timeout list.
+ */
+ static void
+ remove_timeout_index(int index)
+ {
+ int i;
+
+ if (index < 0)
+ return;
+
+ for (i = index + 1; i < n_timeouts; i++)
+ timeouts[i-1] = timeouts[i];
+
+ if (n_timeouts > 0 && index < n_timeouts)
+ n_timeouts--;
+ }
+
+ /*
+ * (Re)schedule the next active timeout
+ */
+ static bool
+ schedule_timeout(TimestampTz now)
+ {
+ long secs;
+ int usecs;
+ struct itimerval timeval;
+
+ /* There is no active timeout, do nothing. */
+ if (n_timeouts == 0)
+ return true;
+
+ TimestampDifference(now, timeouts[0]->fin_time,
+ &secs, &usecs);
+
+ /*
+ * It's possible that the difference is less than a microsecond;
+ * ensure we don't cancel, rather than set, the interrupt.
+ */
+ if (secs == 0 && usecs == 0)
+ usecs = 1;
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ timeval.it_value.tv_sec = secs;
+ timeval.it_value.tv_usec = usecs;
+ if (setitimer(ITIMER_REAL, &timeval, NULL))
+ return false;
+ return true;
+ }
+
+ /*****************************************************************************
+ * Init, Destroy and Check functions for different timeouts.
+ *
+ * NB: all Check* functions are run inside a signal handler, so be very wary
+ * about what is done in them or in called routines.
+ *****************************************************************************/
+
+ /*
+ * Common Init and Destroy functions
+ */
+
+ static void
+ InitTimeout(TimeoutName tn, TimestampTz start_time, TimestampTz fin_time)
+ {
+ base_timeouts[tn].indicator = false;
+ base_timeouts[tn].start_time = start_time;
+ base_timeouts[tn].fin_time = fin_time;
+ }
+
+ static void
+ DestroyTimeout(TimeoutName tn, bool keep_indicator)
+ {
+ if (!keep_indicator)
+ base_timeouts[tn].indicator = false;
+ base_timeouts[tn].start_time = 0;
+ base_timeouts[tn].fin_time = 0;
+ }
+
+ /*
+ * Functions to manage deadlock
+ */
+
+ static void
+ InitDeadLock(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(DEADLOCK_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyDeadLock(bool keep_indicator)
+ {
+ DestroyTimeout(DEADLOCK_TIMEOUT, keep_indicator);
+ }
+
+ /*
+ * CheckDeadLock
+ *
+ * Look to see if there's a deadlock; if not, just return.
+ * (But signal ProcSleep to log a message, if log_lock_waits is true.)
+ * If we have a real deadlock, remove ourselves from the lock's wait queue
+ * and signal an error to ProcSleep.
+ */
+ static bool
+ CheckDeadLock(void)
+ {
+ TimestampTz now;
+ int i;
+
+ now = GetCurrentTimestamp();
+
+ /* If our time has not come yet, do nothing. */
+ if (now < base_timeouts[DEADLOCK_TIMEOUT].fin_time)
+ return false;
+
+ /*
+ * Acquire exclusive lock on the entire shared lock data structures. Must
+ * grab LWLocks in partition-number order to avoid LWLock deadlock.
+ *
+ * Note that the deadlock check interrupt had better not be enabled
+ * anywhere that this process itself holds lock partition locks, else this
+ * will wait forever. Also note that LWLockAcquire creates a critical
+ * section, so that this routine cannot be interrupted by cancel/die
+ * interrupts.
+ */
+ for (i = 0; i < NUM_LOCK_PARTITIONS; i++)
+ LWLockAcquire(FirstLockMgrLock + i, LW_EXCLUSIVE);
+
+ /*
+ * Check to see if we've been awoken by anyone in the interim.
+ *
+ * If we have, we can return and resume our transaction -- happy day.
+ * Before we are awoken the process releasing the lock grants it to us so
+ * we know that we don't have to wait anymore.
+ *
+ * We check by looking to see if we've been unlinked from the wait queue.
+ * This is quicker than checking our semaphore's state, since no kernel
+ * call is needed, and it is safe because we hold the lock partition lock.
+ */
+ if (MyProc->links.prev == NULL ||
+ MyProc->links.next == NULL)
+ goto check_done;
+
+ #ifdef LOCK_DEBUG
+ if (Debug_deadlocks)
+ DumpAllLocks();
+ #endif
+
+ /* Run the deadlock check, and set deadlock_state for use by ProcSleep */
+ deadlock_state = DeadLockCheck(MyProc);
+
+ if (deadlock_state == DS_HARD_DEADLOCK)
+ {
+ /*
+ * Oops. We have a deadlock.
+ *
+ * Get this process out of wait state. (Note: we could do this more
+ * efficiently by relying on lockAwaited, but use this coding to
+ * preserve the flexibility to kill some other transaction than the
+ * one detecting the deadlock.)
+ *
+ * RemoveFromWaitQueue sets MyProc->waitStatus to STATUS_ERROR, so
+ * ProcSleep will report an error after we return from the signal
+ * handler.
+ */
+ Assert(MyProc->waitLock != NULL);
+ RemoveFromWaitQueue(MyProc, LockTagHashCode(&(MyProc->waitLock->tag)));
+
+ /*
+ * Unlock my semaphore so that the interrupted ProcSleep() call can
+ * finish.
+ */
+ PGSemaphoreUnlock(&MyProc->sem);
+
+ /*
+ * We're done here. Transaction abort caused by the error that
+ * ProcSleep will raise will cause any other locks we hold to be
+ * released, thus allowing other processes to wake up; we don't need
+ * to do that here. NOTE: an exception is that releasing locks we
+ * hold doesn't consider the possibility of waiters that were blocked
+ * behind us on the lock we just failed to get, and might now be
+ * wakable because we're not in front of them anymore. However,
+ * RemoveFromWaitQueue took care of waking up any such processes.
+ */
+ }
+ else if (log_lock_waits || deadlock_state == DS_BLOCKED_BY_AUTOVACUUM)
+ {
+ /*
+ * Unlock my semaphore so that the interrupted ProcSleep() call can
+ * print the log message (we daren't do it here because we are inside
+ * a signal handler). It will then sleep again until someone releases
+ * the lock.
+ *
+ * If blocked by autovacuum, this wakeup will enable ProcSleep to send
+ * the canceling signal to the autovacuum worker.
+ */
+ PGSemaphoreUnlock(&MyProc->sem);
+ }
+
+ /*
+ * And release locks. We do this in reverse order for two reasons: (1)
+ * Anyone else who needs more than one of the locks will be trying to lock
+ * them in increasing order; we don't want to release the other process
+ * until it can get all the locks it needs. (2) This avoids O(N^2)
+ * behavior inside LWLockRelease.
+ */
+ check_done:
+ for (i = NUM_LOCK_PARTITIONS; --i >= 0;)
+ LWLockRelease(FirstLockMgrLock + i);
+
+ base_timeouts[DEADLOCK_TIMEOUT].indicator = true;
+
+ return true;
+ }
+
+ /*
+ * Functions to manage statement timeout
+ */
+
+ static void
+ InitStatementTimeout(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(STATEMENT_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyStatementTimeout(bool keep_indicator)
+ {
+ DestroyTimeout(STATEMENT_TIMEOUT, keep_indicator);
+ }
+
+ static bool
+ CheckStatementTimeout(void)
+ {
+ TimestampTz now;
+
+ now = GetCurrentTimestamp();
+
+ if (now < base_timeouts[STATEMENT_TIMEOUT].fin_time)
+ return false;
+
+ base_timeouts[STATEMENT_TIMEOUT].indicator = true;
+ #ifdef HAVE_SETSID
+ /* try to signal whole process group */
+ kill(-MyProcPid, SIGINT);
+ #endif
+ kill(MyProcPid, SIGINT);
+
+ return true;
+ }
+
+ /*
+ * Functions to manage standby deadlock functions
+ */
+
+ static void
+ InitStandbyDeadLock(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(STANDBY_DEADLOCK_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyStandbyDeadLock(bool keep_indicator)
+ {
+ DestroyTimeout(STANDBY_DEADLOCK_TIMEOUT, keep_indicator);
+ }
+
+ static bool
+ CheckStandbyDeadLock(void)
+ {
+ TimestampTz now;
+
+ now = GetCurrentTimestamp();
+
+ if (now < base_timeouts[STANDBY_DEADLOCK_TIMEOUT].fin_time)
+ return false;
+
+ base_timeouts[STANDBY_DEADLOCK_TIMEOUT].indicator = true;
+ SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
+ return true;
+ }
+
+ /*
+ * Functions to manage standby timeout
+ */
+
+ static void
+ InitStandbyTimeout(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(STANDBY_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyStandbyTimeout(bool keep_indicator)
+ {
+ DestroyTimeout(STANDBY_TIMEOUT, keep_indicator);
+ }
+
+ static bool
+ CheckStandbyTimeout(void)
+ {
+ TimestampTz now;
+
+ now = GetCurrentTimestamp();
+
+ if (now < base_timeouts[STANDBY_TIMEOUT].fin_time)
+ return false;
+
+ base_timeouts[STANDBY_TIMEOUT].indicator = true;
+ SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
+ return true;
+ }
+
+ /*****************************************************************************
+ * Public API
+ *****************************************************************************/
+
+
+ /*
+ * Enable the SIGALRM interrupt to fire after the specified delay
+ *
+ * Delay is given in milliseconds. Caller should be sure a SIGALRM
+ * signal handler is installed before this is called.
+ *
+ * This code properly handles nesting of different timeout alarms.
+ *
+ * Returns TRUE if okay, FALSE on failure to set the timer.
+ */
+ bool
+ enable_timeout(TimeoutName tn, int delayms)
+ {
+ TimestampTz start_time;
+ TimestampTz fin_time;
+ int i;
+
+ Assert(!is_timeout_active(tn));
+
+ if (delayms <= 0)
+ return true;
+
+ start_time = base_timeouts[tn].timeout_start();
+ fin_time = TimestampTzPlusMilliseconds(start_time, delayms);
+
+ /* Find out the index where to insert the new timeout. */
+ for (i = 0; i < n_timeouts; i++)
+ {
+ /*
+ * The new timeout triggers earlier than
+ * a previously added one: insert here.
+ */
+ if (fin_time < timeouts[i]->fin_time)
+ break;
+ /*
+ * The new timeout triggers at the same time
+ * as the previously added one but has greater priority.
+ */
+ if (fin_time == timeouts[i]->fin_time && tn < timeouts[i]->index)
+ break;
+ }
+
+ /*
+ * Initialize the timeout parameters
+ */
+ base_timeouts[tn].timeout_init(start_time, fin_time);
+
+ insert_timeout(tn, i);
+
+ if (i > 0)
+ return true;
+
+ /* If we reach here, okay to set the timer interrupt */
+ if (!schedule_timeout(start_time))
+ return false;
+ return true;
+ }
+
+ /*
+ * Cancel the SIGALRM timer for the specific timeout.
+ * If a timeout is canceled, any other active timeout remains in force.
+ *
+ * Returns TRUE if okay, FALSE on failure to set the timer for
+ * the next timeout source.
+ */
+ bool
+ disable_timeout(TimeoutName tn, bool keep_indicator)
+ {
+ int i;
+
+ /*
+ * Always disable the interrupt if it is active; this avoids being
+ * interrupted by the signal handler and thereby possibly getting
+ * confused.
+ *
+ * We will re-enable the interrupt if necessary in ->check_timeout().
+ */
+ if (n_timeouts > 0)
+ {
+ struct itimerval timeval;
+
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ if (setitimer(ITIMER_REAL, &timeval, NULL))
+ {
+ n_timeouts = 0;
+ for (i = 0; i < TIMEOUT_MAX; i++)
+ base_timeouts[i].timeout_destroy(false);
+
+ return false;
+ }
+ }
+
+ /* Find the timeout and remove from the list. */
+ i = find_active_timeout(tn);
+ remove_timeout_index(i);
+
+ /* Do cleanup. */
+ base_timeouts[tn].timeout_destroy(keep_indicator);
+
+ /*
+ * If the first timeout was removed from the list and there is
+ * at least one active left, reschedule it.
+ */
+ if (i == 0 && n_timeouts > 0)
+ if (!schedule_timeout(GetCurrentTimestamp()))
+ return false;
+
+ return true;
+ }
+
+ /*
+ * Disable SIGALRM and remove all timeouts from the list and
+ * reset the timeout indicators.
+ */
+ bool
+ disable_all_timeouts(bool keep_indicators)
+ {
+ struct itimerval timeval;
+ int i;
+ bool ret;
+
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ ret = (setitimer(ITIMER_REAL, &timeval, NULL) == 0);
+
+ n_timeouts = 0;
+ for (i = 0; i < TIMEOUT_MAX; i++)
+ base_timeouts[i].timeout_destroy(keep_indicators);
+
+ return ret;
+ }
+
+ /*
+ * Return the timeout indicator
+ */
+ bool
+ get_timeout_indicator(TimeoutName tn)
+ {
+ return base_timeouts[tn].indicator;
+ }
+
+ /*
+ * Return the start of the timer for this timeout
+ */
+ TimestampTz
+ get_timeout_start(TimeoutName tn)
+ {
+ return base_timeouts[tn].start_time;
+ }
+
+ /*
+ * Signal handler for SIGALRM
+ *
+ * Process the check for the currently active timeout source and
+ * reschedule the next as needed. To avoid various edge cases,
+ * we must be careful to do nothing when there is nothing to be done.
+ */
+ void
+ handle_sig_alarm(SIGNAL_ARGS)
+ {
+ int save_errno = errno;
+
+ /*
+ * SIGALRM is cause for waking anything waiting on the process latch.
+ * Recovery (the startup process) doesn't have MyProc set so
+ * it can also use this signal handler.
+ */
+ if (MyProc)
+ SetLatch(&MyProc->procLatch);
+
+ if (n_timeouts > 0)
+ {
+ bool ret;
+ bool reschedule = true;
+
+ ret = timeouts[0]->timeout_check();
+
+ /* Shift timeouts if the timeout was triggered */
+ if (ret)
+ {
+ reschedule = timeouts[0]->resched_next;
+ /*
+ * Short circuit disable_timeout(..., true) for the
+ * timeout source that just triggered.
+ */
+ remove_timeout_index(0);
+ }
+
+ if (reschedule)
+ schedule_timeout(GetCurrentTimestamp());
+ else
+ disable_all_timeouts(true);
+ }
+
+ errno = save_errno;
+ }
diff -dcrpN postgresql.orig/src/backend/tcop/postgres.c postgresql.newtimeout/src/backend/tcop/postgres.c
*** postgresql.orig/src/backend/tcop/postgres.c 2012-03-28 10:54:25.195368447 +0200
--- postgresql.newtimeout/src/backend/tcop/postgres.c 2012-04-05 07:13:04.286287919 +0200
***************
*** 64,69 ****
--- 64,70 ----
#include "storage/proc.h"
#include "storage/procsignal.h"
#include "storage/sinval.h"
+ #include "storage/timeout.h"
#include "tcop/fastpath.h"
#include "tcop/pquery.h"
#include "tcop/tcopprot.h"
*************** start_xact_command(void)
*** 2368,2376 ****
/* Set statement timeout running, if any */
/* NB: this mustn't be enabled until we are within an xact */
if (StatementTimeout > 0)
! enable_sig_alarm(StatementTimeout, true);
else
! cancel_from_timeout = false;
xact_started = true;
}
--- 2369,2377 ----
/* Set statement timeout running, if any */
/* NB: this mustn't be enabled until we are within an xact */
if (StatementTimeout > 0)
! enable_timeout(STATEMENT_TIMEOUT, StatementTimeout);
else
! disable_timeout(STATEMENT_TIMEOUT, false);
xact_started = true;
}
*************** finish_xact_command(void)
*** 2382,2388 ****
if (xact_started)
{
/* Cancel any active statement timeout before committing */
! disable_sig_alarm(true);
/* Now commit the command */
ereport(DEBUG3,
--- 2383,2389 ----
if (xact_started)
{
/* Cancel any active statement timeout before committing */
! disable_all_timeouts(false);
/* Now commit the command */
ereport(DEBUG3,
*************** ProcessInterrupts(void)
*** 2863,2869 ****
(errcode(ERRCODE_QUERY_CANCELED),
errmsg("canceling authentication due to timeout")));
}
! if (cancel_from_timeout)
{
ImmediateInterruptOK = false; /* not idle anymore */
DisableNotifyInterrupt();
--- 2864,2870 ----
(errcode(ERRCODE_QUERY_CANCELED),
errmsg("canceling authentication due to timeout")));
}
! if (get_timeout_indicator(STATEMENT_TIMEOUT))
{
ImmediateInterruptOK = false; /* not idle anymore */
DisableNotifyInterrupt();
*************** PostgresMain(int argc, char *argv[], con
*** 3728,3737 ****
/*
* Forget any pending QueryCancel request, since we're returning to
! * the idle loop anyway, and cancel the statement timer if running.
*/
QueryCancelPending = false;
! disable_sig_alarm(true);
QueryCancelPending = false; /* again in case timeout occurred */
/*
--- 3729,3738 ----
/*
* Forget any pending QueryCancel request, since we're returning to
! * the idle loop anyway, and cancel the timer if running.
*/
QueryCancelPending = false;
! disable_all_timeouts(false);
QueryCancelPending = false; /* again in case timeout occurred */
/*
diff -dcrpN postgresql.orig/src/backend/utils/init/postinit.c postgresql.newtimeout/src/backend/utils/init/postinit.c
*** postgresql.orig/src/backend/utils/init/postinit.c 2012-01-20 13:59:24.913240794 +0100
--- postgresql.newtimeout/src/backend/utils/init/postinit.c 2012-04-05 07:13:04.287287924 +0200
***************
*** 41,51 ****
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lmgr.h"
- #include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "storage/smgr.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
#include "utils/fmgroids.h"
--- 41,51 ----
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lmgr.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "storage/smgr.h"
+ #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
#include "utils/fmgroids.h"
*************** PerformAuthentication(Port *port)
*** 204,210 ****
* during authentication. Since we're inside a transaction and might do
* database access, we have to use the statement_timeout infrastructure.
*/
! if (!enable_sig_alarm(AuthenticationTimeout * 1000, true))
elog(FATAL, "could not set timer for authorization timeout");
/*
--- 204,210 ----
* during authentication. Since we're inside a transaction and might do
* database access, we have to use the statement_timeout infrastructure.
*/
! if (!enable_timeout(STATEMENT_TIMEOUT, AuthenticationTimeout * 1000))
elog(FATAL, "could not set timer for authorization timeout");
/*
*************** PerformAuthentication(Port *port)
*** 215,221 ****
/*
* Done with authentication. Disable the timeout, and log if needed.
*/
! if (!disable_sig_alarm(true))
elog(FATAL, "could not disable timer for authorization timeout");
if (Log_connections)
--- 215,221 ----
/*
* Done with authentication. Disable the timeout, and log if needed.
*/
! if (!disable_timeout(STATEMENT_TIMEOUT, false))
elog(FATAL, "could not disable timer for authorization timeout");
if (Log_connections)
diff -dcrpN postgresql.orig/src/backend/utils/misc/guc.c postgresql.newtimeout/src/backend/utils/misc/guc.c
*** postgresql.orig/src/backend/utils/misc/guc.c 2012-03-28 10:54:25.196368452 +0200
--- postgresql.newtimeout/src/backend/utils/misc/guc.c 2012-04-05 07:26:33.578320052 +0200
***************
*** 63,68 ****
--- 63,69 ----
#include "storage/standby.h"
#include "storage/fd.h"
#include "storage/predicate.h"
+ #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "tsearch/ts_cache.h"
#include "utils/builtins.h"
diff -dcrpN postgresql.orig/src/include/storage/proc.h postgresql.newtimeout/src/include/storage/proc.h
*** postgresql.orig/src/include/storage/proc.h 2012-01-31 09:43:43.936392561 +0100
--- postgresql.newtimeout/src/include/storage/proc.h 2012-04-05 07:13:04.293287954 +0200
*************** extern PGPROC *PreparedXactProcs;
*** 215,227 ****
/* configurable options */
- extern int DeadlockTimeout;
- extern int StatementTimeout;
extern bool log_lock_waits;
- extern volatile bool cancel_from_timeout;
-
-
/*
* Function Prototypes
*/
--- 215,222 ----
*************** extern void LockWaitCancel(void);
*** 249,261 ****
extern void ProcWaitForSignal(void);
extern void ProcSendSignal(int pid);
- extern bool enable_sig_alarm(int delayms, bool is_statement_timeout);
- extern bool disable_sig_alarm(bool is_statement_timeout);
- extern void handle_sig_alarm(SIGNAL_ARGS);
-
- extern bool enable_standby_sig_alarm(TimestampTz now,
- TimestampTz fin_time, bool deadlock_only);
- extern bool disable_standby_sig_alarm(void);
- extern void handle_standby_sig_alarm(SIGNAL_ARGS);
-
#endif /* PROC_H */
--- 244,247 ----
diff -dcrpN postgresql.orig/src/include/storage/timeout.h postgresql.newtimeout/src/include/storage/timeout.h
*** postgresql.orig/src/include/storage/timeout.h 1970-01-01 01:00:00.000000000 +0100
--- postgresql.newtimeout/src/include/storage/timeout.h 2012-04-05 07:24:44.111776574 +0200
***************
*** 0 ****
--- 1,38 ----
+ /*-------------------------------------------------------------------------
+ *
+ * timeout.h
+ * SIGALRM timeout API
+ *
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/storage/timeout.h
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef _TIMEOUT_H_
+ #define _TIMEOUT_H_
+
+ #include "datatype/timestamp.h"
+
+ /* configurable options */
+ extern int DeadlockTimeout;
+ extern int StatementTimeout;
+
+ typedef enum TimeoutName {
+ DEADLOCK_TIMEOUT,
+ STATEMENT_TIMEOUT,
+ STANDBY_DEADLOCK_TIMEOUT,
+ STANDBY_TIMEOUT,
+ TIMEOUT_MAX
+ } TimeoutName;
+
+ extern bool enable_timeout(TimeoutName tn, int delayms);
+ extern bool disable_timeout(TimeoutName tn, bool keep_indicator);
+ extern bool disable_all_timeouts(bool keep_indicators);
+ extern bool get_timeout_indicator(TimeoutName tn);
+ extern TimestampTz get_timeout_start(TimeoutName tn);
+ extern void handle_sig_alarm(SIGNAL_ARGS);
+
+ #endif /* _TIMEOUT_H_ */
2-lock_timeout.patchtext/x-patch; name=2-lock_timeout.patchDownload
diff -dcrpN postgresql.newtimeout/doc/src/sgml/config.sgml postgresql.lock_timeout/doc/src/sgml/config.sgml
*** postgresql.newtimeout/doc/src/sgml/config.sgml 2012-04-05 07:14:21.580672964 +0200
--- postgresql.lock_timeout/doc/src/sgml/config.sgml 2012-04-05 07:13:04.270287839 +0200
*************** COPY postgres_log FROM '/full/path/to/lo
*** 4944,4950 ****
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.
</para>
<para>
--- 4944,4953 ----
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. 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>
*************** COPY postgres_log FROM '/full/path/to/lo
*** 4955,4960 ****
--- 4958,4991 ----
</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 has to wait more
+ than the specified number of milliseconds, starting from the time the
+ command arrives at the server from the client. If the statement involves
+ more than one such lock, the timeout applies to every one of them.
+ 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 to acquire.
+ 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 off the limitation.
+ </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 -dcrpN postgresql.newtimeout/doc/src/sgml/ref/lock.sgml postgresql.lock_timeout/doc/src/sgml/ref/lock.sgml
*** postgresql.newtimeout/doc/src/sgml/ref/lock.sgml 2012-04-05 07:14:36.531747468 +0200
--- postgresql.lock_timeout/doc/src/sgml/ref/lock.sgml 2012-04-05 07:13:04.271287844 +0200
*************** LOCK [ TABLE ] [ ONLY ] <replaceable cla
*** 39,46 ****
<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.)
</para>
--- 39,49 ----
<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. 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 -dcrpN postgresql.newtimeout/doc/src/sgml/ref/select.sgml postgresql.lock_timeout/doc/src/sgml/ref/select.sgml
*** postgresql.newtimeout/doc/src/sgml/ref/select.sgml 2012-04-05 07:14:48.635807787 +0200
--- postgresql.lock_timeout/doc/src/sgml/ref/select.sgml 2012-04-05 07:13:04.272287849 +0200
*************** FOR SHARE [ OF <replaceable class="param
*** 1199,1204 ****
--- 1199,1211 ----
</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 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 -dcrpN postgresql.newtimeout/src/backend/port/posix_sema.c postgresql.lock_timeout/src/backend/port/posix_sema.c
*** postgresql.newtimeout/src/backend/port/posix_sema.c 2012-04-05 07:15:52.964128475 +0200
--- postgresql.lock_timeout/src/backend/port/posix_sema.c 2012-04-05 07:13:04.273287854 +0200
***************
*** 24,29 ****
--- 24,30 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/timeout.h"
#ifdef USE_NAMED_POSIX_SEMAPHORES
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 313,315 ****
--- 314,341 ----
return true;
}
+
+ /*
+ * PGSemaphoreTimedLock
+ *
+ * Lock a semaphore (decrement count), blocking if count would be < 0
+ * Return if lock_timeout expired
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ int errStatus;
+
+ do
+ {
+ ImmediateInterruptOK = interruptOK;
+ CHECK_FOR_INTERRUPTS();
+ errStatus = sem_wait(PG_SEM_REF(sema));
+ ImmediateInterruptOK = false;
+ } while (errStatus < 0 && errno == EINTR && !get_timeout_indicator(LOCK_TIMEOUT));
+
+ if (get_timeout_indicator(LOCK_TIMEOUT))
+ return;
+ if (errStatus < 0)
+ elog(FATAL, "sem_wait failed: %m");
+ }
diff -dcrpN postgresql.newtimeout/src/backend/port/sysv_sema.c postgresql.lock_timeout/src/backend/port/sysv_sema.c
*** postgresql.newtimeout/src/backend/port/sysv_sema.c 2012-04-05 07:15:53.380130550 +0200
--- postgresql.lock_timeout/src/backend/port/sysv_sema.c 2012-04-05 07:13:04.273287854 +0200
***************
*** 30,35 ****
--- 30,36 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/timeout.h"
#ifndef HAVE_UNION_SEMUN
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 495,497 ****
--- 496,528 ----
return true;
}
+
+ /*
+ * PGSemaphoreTimedLock
+ *
+ * Lock a semaphore (decrement count), blocking if count would be < 0
+ * Return if lock_timeout expired
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ int errStatus;
+ 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;
+ } while (errStatus < 0 && errno == EINTR && !get_timeout_indicator(LOCK_TIMEOUT));
+
+ if (get_timeout_indicator(LOCK_TIMEOUT))
+ return;
+ if (errStatus < 0)
+ elog(FATAL, "semop(id=%d) failed: %m", sema->semId);
+ }
diff -dcrpN postgresql.newtimeout/src/backend/port/win32_sema.c postgresql.lock_timeout/src/backend/port/win32_sema.c
*** postgresql.newtimeout/src/backend/port/win32_sema.c 2012-04-05 07:15:53.740132345 +0200
--- postgresql.lock_timeout/src/backend/port/win32_sema.c 2012-04-05 07:13:04.274287858 +0200
***************
*** 16,21 ****
--- 16,22 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/timeout.h"
static HANDLE *mySemSet; /* IDs of sema sets acquired so far */
static int numSems; /* number of sema sets acquired so far */
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 205,207 ****
--- 206,263 ----
/* 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.
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ DWORD ret;
+ HANDLE wh[2];
+
+ wh[0] = *sema;
+ wh[1] = pgwin32_signal_event;
+
+ /*
+ * 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();
+
+ errno = 0;
+ ret = WaitForMultipleObjectsEx(2, wh, FALSE, INFINITE, TRUE);
+
+ if (ret == WAIT_OBJECT_0)
+ {
+ /* We got it! */
+ return;
+ }
+ else if (ret == WAIT_OBJECT_0 + 1)
+ {
+ /* Signal event is set - we have a signal to deliver */
+ pgwin32_dispatch_queued_signals();
+ errno = EINTR;
+ }
+ else
+ /* Otherwise we are in trouble */
+ errno = EIDRM;
+
+ ImmediateInterruptOK = false;
+ } while (errno == EINTR && !get_timeout_indicator(LOCK_TIMEOUT));
+
+ if (get_timeout_indicator(LOCK_TIMEOUT))
+ return;
+ if (errno != 0)
+ ereport(FATAL,
+ (errmsg("could not lock semaphore: error code %d", (int) GetLastError())));
+ }
diff -dcrpN postgresql.newtimeout/src/backend/storage/lmgr/lmgr.c postgresql.lock_timeout/src/backend/storage/lmgr/lmgr.c
*** postgresql.newtimeout/src/backend/storage/lmgr/lmgr.c 2012-04-05 07:17:57.196748213 +0200
--- postgresql.lock_timeout/src/backend/storage/lmgr/lmgr.c 2012-04-05 07:13:04.280287889 +0200
***************
*** 19,26 ****
--- 19,29 ----
#include "access/transam.h"
#include "access/xact.h"
#include "catalog/catalog.h"
+ #include "catalog/pg_database.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
+ #include "utils/lsyscache.h"
+ #include "storage/proc.h"
#include "storage/procarray.h"
#include "utils/inval.h"
*************** LockRelationOid(Oid relid, LOCKMODE lock
*** 78,83 ****
--- 81,101 ----
res = LockAcquire(&tag, lockmode, false, false);
+ if (res == LOCKACQUIRE_NOT_AVAIL)
+ {
+ char *relname = get_rel_name(relid);
+ if (relname)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ relname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation with OID %u",
+ relid)));
+ }
+
/*
* Now that we have the lock, check for invalidation messages, so that we
* will update or flush any stale relcache entry before we try to use it.
*************** LockRelation(Relation relation, LOCKMODE
*** 174,179 ****
--- 192,203 ----
res = LockAcquire(&tag, lockmode, false, false);
+ if (res == LOCKACQUIRE_NOT_AVAIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ RelationGetRelationName(relation))));
+
/*
* Now that we have the lock, check for invalidation messages; see notes
* in LockRelationOid.
*************** LockRelationIdForSession(LockRelId *reli
*** 251,257 ****
SET_LOCKTAG_RELATION(tag, relid->dbId, relid->relId);
! (void) LockAcquire(&tag, lockmode, true, false);
}
/*
--- 275,294 ----
SET_LOCKTAG_RELATION(tag, relid->dbId, relid->relId);
! if (LockAcquire(&tag, lockmode, true, false) == LOCKACQUIRE_NOT_AVAIL)
! {
! char *relname = get_rel_name(relid->relId);
! if (relname)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on relation \"%s\"",
! relname)));
! else
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on relation with OID %u",
! relid->relId)));
! }
}
/*
*************** LockRelationForExtension(Relation relati
*** 286,292 ****
relation->rd_lockInfo.lockRelId.dbId,
relation->rd_lockInfo.lockRelId.relId);
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 323,333 ----
relation->rd_lockInfo.lockRelId.dbId,
relation->rd_lockInfo.lockRelId.relId);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on index \"%s\"",
! RelationGetRelationName(relation))));
}
/*
*************** LockPage(Relation relation, BlockNumber
*** 320,326 ****
relation->rd_lockInfo.lockRelId.relId,
blkno);
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 361,371 ----
relation->rd_lockInfo.lockRelId.relId,
blkno);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on page %u of relation \"%s\"",
! blkno, RelationGetRelationName(relation))));
}
/*
*************** LockTuple(Relation relation, ItemPointer
*** 376,382 ****
ItemPointerGetBlockNumber(tid),
ItemPointerGetOffsetNumber(tid));
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 421,431 ----
ItemPointerGetBlockNumber(tid),
ItemPointerGetOffsetNumber(tid));
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on row in relation \"%s\"",
! RelationGetRelationName(relation))));
}
/*
*************** XactLockTableInsert(TransactionId xid)
*** 430,436 ****
SET_LOCKTAG_TRANSACTION(tag, xid);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
}
/*
--- 479,488 ----
SET_LOCKTAG_TRANSACTION(tag, xid);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on transaction with ID %u", xid)));
}
/*
*************** XactLockTableWait(TransactionId xid)
*** 474,480 ****
SET_LOCKTAG_TRANSACTION(tag, xid);
! (void) LockAcquire(&tag, ShareLock, false, false);
LockRelease(&tag, ShareLock, false);
--- 526,535 ----
SET_LOCKTAG_TRANSACTION(tag, xid);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on transaction with ID %u", xid)));
LockRelease(&tag, ShareLock, false);
*************** LockDatabaseObject(Oid classid, Oid obji
*** 535,541 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, false, false);
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
--- 590,600 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
*************** LockSharedObject(Oid classid, Oid objid,
*** 576,582 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, false, false);
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
--- 635,645 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
*************** LockSharedObjectForSession(Oid classid,
*** 618,624 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, true, false);
}
/*
--- 681,702 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, true, false) == LOCKACQUIRE_NOT_AVAIL)
! switch(classid)
! {
! case DatabaseRelationId:
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on database with ID %u",
! objid)));
! break;
! default:
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
! break;
! }
}
/*
diff -dcrpN postgresql.newtimeout/src/backend/storage/lmgr/lock.c postgresql.lock_timeout/src/backend/storage/lmgr/lock.c
*** postgresql.newtimeout/src/backend/storage/lmgr/lock.c 2012-04-05 07:19:27.702200029 +0200
--- postgresql.lock_timeout/src/backend/storage/lmgr/lock.c 2012-04-05 07:13:04.283287904 +0200
*************** static void RemoveLocalLock(LOCALLOCK *l
*** 338,344 ****
static PROCLOCK *SetupLockInTable(LockMethod lockMethodTable, PGPROC *proc,
const LOCKTAG *locktag, uint32 hashcode, LOCKMODE lockmode);
static void GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner);
! static void WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner);
static void ReleaseLockForOwner(LOCALLOCK *locallock, ResourceOwner owner);
static bool UnGrantLock(LOCK *lock, LOCKMODE lockmode,
PROCLOCK *proclock, LockMethod lockMethodTable);
--- 338,344 ----
static PROCLOCK *SetupLockInTable(LockMethod lockMethodTable, PGPROC *proc,
const LOCKTAG *locktag, uint32 hashcode, LOCKMODE lockmode);
static void GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner);
! static int WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner);
static void ReleaseLockForOwner(LOCALLOCK *locallock, ResourceOwner owner);
static bool UnGrantLock(LOCK *lock, LOCKMODE lockmode,
PROCLOCK *proclock, LockMethod lockMethodTable);
*************** ProcLockHashCode(const PROCLOCKTAG *proc
*** 544,550 ****
* dontWait: if true, don't wait to acquire lock
*
* Returns one of:
! * LOCKACQUIRE_NOT_AVAIL lock not available, and dontWait=true
* LOCKACQUIRE_OK lock successfully acquired
* LOCKACQUIRE_ALREADY_HELD incremented count for lock already held
*
--- 544,550 ----
* dontWait: if true, don't wait to acquire lock
*
* Returns one of:
! * LOCKACQUIRE_NOT_AVAIL lock not available, either dontWait=true or timeout
* LOCKACQUIRE_OK lock successfully acquired
* LOCKACQUIRE_ALREADY_HELD incremented count for lock already held
*
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 863,869 ****
locktag->locktag_type,
lockmode);
! WaitOnLock(locallock, owner);
TRACE_POSTGRESQL_LOCK_WAIT_DONE(locktag->locktag_field1,
locktag->locktag_field2,
--- 863,869 ----
locktag->locktag_type,
lockmode);
! status = WaitOnLock(locallock, owner);
TRACE_POSTGRESQL_LOCK_WAIT_DONE(locktag->locktag_field1,
locktag->locktag_field2,
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 878,897 ****
* done when the lock was granted to us --- see notes in WaitOnLock.
*/
! /*
! * Check the proclock entry status, in case something in the ipc
! * communication doesn't work correctly.
! */
! if (!(proclock->holdMask & LOCKBIT_ON(lockmode)))
{
! PROCLOCK_PRINT("LockAcquire: INCONSISTENT", proclock);
! LOCK_PRINT("LockAcquire: INCONSISTENT", lock, lockmode);
! /* Should we retry ? */
! LWLockRelease(partitionLock);
! elog(ERROR, "LockAcquire failed");
}
- PROCLOCK_PRINT("LockAcquire: granted", proclock);
- LOCK_PRINT("LockAcquire: granted", lock, lockmode);
}
LWLockRelease(partitionLock);
--- 878,909 ----
* done when the lock was granted to us --- see notes in WaitOnLock.
*/
! switch (status)
{
! case STATUS_OK:
! /*
! * Check the proclock entry status, in case something in the ipc
! * communication doesn't work correctly.
! */
! if (!(proclock->holdMask & LOCKBIT_ON(lockmode)))
! {
! PROCLOCK_PRINT("LockAcquire: INCONSISTENT", proclock);
! LOCK_PRINT("LockAcquire: INCONSISTENT", lock, lockmode);
! /* Should we retry ? */
! LWLockRelease(partitionLock);
! elog(ERROR, "LockAcquire failed");
! }
! PROCLOCK_PRINT("LockAcquire: granted", proclock);
! LOCK_PRINT("LockAcquire: granted", lock, lockmode);
! break;
! case STATUS_WAITING:
! PROCLOCK_PRINT("LockAcquire: timed out", proclock);
! LOCK_PRINT("LockAcquire: timed out", lock, lockmode);
! break;
! default:
! elog(ERROR, "LockAcquire invalid status");
! break;
}
}
LWLockRelease(partitionLock);
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 911,917 ****
locktag->locktag_field2);
}
! return LOCKACQUIRE_OK;
}
/*
--- 923,929 ----
locktag->locktag_field2);
}
! return (status == STATUS_OK ? LOCKACQUIRE_OK : LOCKACQUIRE_NOT_AVAIL);
}
/*
*************** GrantAwaitedLock(void)
*** 1371,1384 ****
* Caller must have set MyProc->heldLocks to reflect locks already held
* on the lockable object by this process.
*
* The appropriate partition lock must be held at entry.
*/
! static void
WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner)
{
LOCKMETHODID lockmethodid = LOCALLOCK_LOCKMETHOD(*locallock);
LockMethod lockMethodTable = LockMethods[lockmethodid];
char *volatile new_status = NULL;
LOCK_PRINT("WaitOnLock: sleeping on lock",
locallock->lock, locallock->tag.mode);
--- 1383,1402 ----
* Caller must have set MyProc->heldLocks to reflect locks already held
* on the lockable object by this process.
*
+ * Result: returns value of ProcSleep()
+ * STATUS_OK if we acquired the lock
+ * STATUS_ERROR if not (deadlock)
+ * STATUS_WAITING if not (timeout)
+ *
* The appropriate partition lock must be held at entry.
*/
! static int
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);
*************** WaitOnLock(LOCALLOCK *locallock, Resourc
*** 1420,1427 ****
*/
PG_TRY();
{
! if (ProcSleep(locallock, lockMethodTable) != STATUS_OK)
{
/*
* We failed as a result of a deadlock, see CheckDeadLock(). Quit
* now.
--- 1438,1450 ----
*/
PG_TRY();
{
! wait_status = ProcSleep(locallock, lockMethodTable);
! switch (wait_status)
{
+ case STATUS_OK:
+ case STATUS_WAITING:
+ break;
+ default:
/*
* We failed as a result of a deadlock, see CheckDeadLock(). Quit
* now.
*************** WaitOnLock(LOCALLOCK *locallock, Resourc
*** 1466,1473 ****
pfree(new_status);
}
! LOCK_PRINT("WaitOnLock: wakeup on lock",
locallock->lock, locallock->tag.mode);
}
/*
--- 1489,1502 ----
pfree(new_status);
}
! 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);
+
+ return wait_status;
}
/*
*************** VirtualXactLock(VirtualTransactionId vxi
*** 3739,3745 ****
LWLockRelease(proc->backendLock);
/* Time to wait. */
! (void) LockAcquire(&tag, ShareLock, false, false);
LockRelease(&tag, ShareLock, false);
return true;
--- 3768,3778 ----
LWLockRelease(proc->backendLock);
/* Time to wait. */
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on virtual transaction with ID %u",
! vxid.localTransactionId)));
LockRelease(&tag, ShareLock, false);
return true;
diff -dcrpN postgresql.newtimeout/src/backend/storage/lmgr/proc.c postgresql.lock_timeout/src/backend/storage/lmgr/proc.c
*** postgresql.newtimeout/src/backend/storage/lmgr/proc.c 2012-04-05 07:23:21.664367565 +0200
--- postgresql.lock_timeout/src/backend/storage/lmgr/proc.c 2012-04-05 07:57:03.555467394 +0200
*************** LockWaitCancel(void)
*** 634,641 ****
if (lockAwaited == NULL)
return;
! /* Turn off the deadlock timer, if it's still running (see ProcSleep) */
disable_timeout(DEADLOCK_TIMEOUT, false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
--- 634,645 ----
if (lockAwaited == NULL)
return;
! /*
! * Turn off the deadlock and lock timeout timers,
! * if they are still running (see ProcSleep)
! */
disable_timeout(DEADLOCK_TIMEOUT, false);
+ disable_timeout(LOCK_TIMEOUT, false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
*************** ProcQueueInit(PROC_QUEUE *queue)
*** 869,875 ****
* 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).
*
* ASSUME: that no one will fiddle with the queue until after
* we release the partition lock.
--- 873,882 ----
* 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)
! * STATUS_WAITING if not (timeout)
*
* ASSUME: that no one will fiddle with the queue until after
* we release the partition lock.
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 891,896 ****
--- 898,904 ----
LOCKMASK myHeldLocks = MyProc->heldLocks;
bool early_deadlock = false;
bool allow_autovacuum_cancel = true;
+ bool timeout_detected = false;
int myWaitStatus;
PGPROC *proc;
int i;
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1032,1039 ****
elog(FATAL, "could not set timer for process wakeup");
/*
! * 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
* saved wakeup might be leftover from a previous operation (for example,
* we aborted ProcWaitForSignal just before someone did ProcSendSignal).
--- 1040,1053 ----
elog(FATAL, "could not set timer for process wakeup");
/*
! * Queue the timer for lock timeout, too.
! */
! if (!enable_timeout(LOCK_TIMEOUT, LockTimeout))
! elog(FATAL, "could not set timer for process wakeup");
!
! /*
! * 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).
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1050,1056 ****
*/
do
{
! PGSemaphoreLock(&MyProc->sem, true);
/*
* waitStatus could change from STATUS_WAITING to something else
--- 1064,1075 ----
*/
do
{
! PGSemaphoreTimedLock(&MyProc->sem, true);
!
! /* Check and keep the lock timeout indicator for later checks */
! timeout_detected = get_timeout_indicator(LOCK_TIMEOUT);
! if (timeout_detected)
! break;
/*
* waitStatus could change from STATUS_WAITING to something else
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1179,1190 ****
} while (myWaitStatus == STATUS_WAITING);
/*
! * Disable the timer, if it's still running
*/
if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for process wakeup");
/*
* Re-acquire the lock table's partition lock. We have to do this to hold
* off cancel/die interrupts before we can mess with lockAwaited (else we
* might have a missed or duplicated locallock update).
--- 1198,1215 ----
} while (myWaitStatus == STATUS_WAITING);
/*
! * Disable the deadlock timer, if it's still running
*/
if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for process wakeup");
/*
+ * Disable the lock timeout timer, if it's still running
+ */
+ if (!disable_timeout(LOCK_TIMEOUT, false))
+ elog(FATAL, "could not disable timer for process wakeup");
+
+ /*
* Re-acquire the lock table's partition lock. We have to do this to hold
* off cancel/die interrupts before we can mess with lockAwaited (else we
* might have a missed or duplicated locallock update).
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1192,1197 ****
--- 1217,1231 ----
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 LockWaitCancel to do anything.
*/
lockAwaited = NULL;
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1205,1212 ****
/*
* We don't have to do anything else, because the awaker did all the
* necessary update of the lock table and MyProc.
*/
! return MyProc->waitStatus;
}
--- 1239,1248 ----
/*
* 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 (timeout_detected ? STATUS_WAITING : MyProc->waitStatus);
}
diff -dcrpN postgresql.newtimeout/src/backend/storage/lmgr/timeout.c postgresql.lock_timeout/src/backend/storage/lmgr/timeout.c
*** postgresql.newtimeout/src/backend/storage/lmgr/timeout.c 2012-04-05 07:24:05.672585848 +0200
--- postgresql.lock_timeout/src/backend/storage/lmgr/timeout.c 2012-04-05 07:13:04.285287914 +0200
***************
*** 34,39 ****
--- 34,40 ----
/* GUC variables */
int DeadlockTimeout = 1000;
int StatementTimeout = 0;
+ int LockTimeout = 0;
/*
* This is used by ProcSleep() in proc.c
*************** static void InitDeadLock(TimestampTz sta
*** 50,55 ****
--- 51,60 ----
static void DestroyDeadLock(bool keep_indicator);
static bool CheckDeadLock(void);
+ static void InitLockTimeout(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyLockTimeout(bool keep_indicator);
+ static bool CheckLockTimeout(void);
+
static void InitStatementTimeout(TimestampTz start_time, TimestampTz fin_time);
static void DestroyStatementTimeout(bool keep_indicator);
static bool CheckStatementTimeout(void);
*************** static timeout_params base_timeouts[TIME
*** 98,103 ****
--- 103,115 ----
},
{
+ LOCK_TIMEOUT, false, false,
+ InitLockTimeout, DestroyLockTimeout,
+ CheckLockTimeout, GetCurrentTimestamp,
+ 0
+ },
+
+ {
STATEMENT_TIMEOUT, false, false,
InitStatementTimeout, DestroyStatementTimeout,
CheckStatementTimeout, GetCurrentStatementStartTimestamp,
*************** check_done:
*** 380,385 ****
--- 392,427 ----
return true;
}
+ /*
+ * Functions to manage lock timeout
+ */
+
+ static void
+ InitLockTimeout(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(LOCK_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyLockTimeout(bool keep_indicator)
+ {
+ DestroyTimeout(LOCK_TIMEOUT, keep_indicator);
+ }
+
+ static bool
+ CheckLockTimeout(void)
+ {
+ TimestampTz now;
+
+ now = GetCurrentTimestamp();
+
+ if (now < base_timeouts[LOCK_TIMEOUT].fin_time)
+ return false;
+
+ base_timeouts[LOCK_TIMEOUT].indicator = true;
+ return true;
+ }
+
/*
* Functions to manage statement timeout
*/
diff -dcrpN postgresql.newtimeout/src/backend/utils/adt/lockfuncs.c postgresql.lock_timeout/src/backend/utils/adt/lockfuncs.c
*** postgresql.newtimeout/src/backend/utils/adt/lockfuncs.c 2012-04-05 07:25:45.047079046 +0200
--- postgresql.lock_timeout/src/backend/utils/adt/lockfuncs.c 2012-04-05 07:13:04.286287919 +0200
*************** pg_advisory_lock_int8(PG_FUNCTION_ARGS)
*** 421,427 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ExclusiveLock, true, false);
PG_RETURN_VOID();
}
--- 421,431 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ExclusiveLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_int8(PG_FUNCTION_A
*** 438,444 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
PG_RETURN_VOID();
}
--- 442,452 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_shared_int8(PG_FUNCTION
*** 454,460 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ShareLock, true, false);
PG_RETURN_VOID();
}
--- 462,472 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ShareLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_shared_int8(PG_FUN
*** 471,477 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ShareLock, false, false);
PG_RETURN_VOID();
}
--- 483,493 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_int4(PG_FUNCTION_ARGS)
*** 604,610 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ExclusiveLock, true, false);
PG_RETURN_VOID();
}
--- 620,630 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ExclusiveLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_int4(PG_FUNCTION_A
*** 622,628 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
PG_RETURN_VOID();
}
--- 642,652 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_shared_int4(PG_FUNCTION
*** 639,645 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ShareLock, true, false);
PG_RETURN_VOID();
}
--- 663,673 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ShareLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_shared_int4(PG_FUN
*** 657,663 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ShareLock, false, false);
PG_RETURN_VOID();
}
--- 685,695 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
diff -dcrpN postgresql.newtimeout/src/backend/utils/misc/guc.c postgresql.lock_timeout/src/backend/utils/misc/guc.c
*** postgresql.newtimeout/src/backend/utils/misc/guc.c 2012-04-05 07:26:33.578320052 +0200
--- postgresql.lock_timeout/src/backend/utils/misc/guc.c 2012-04-05 07:13:04.291287944 +0200
*************** static struct config_int ConfigureNamesI
*** 1861,1866 ****
--- 1861,1877 ----
},
{
+ {"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 -dcrpN postgresql.newtimeout/src/backend/utils/misc/postgresql.conf.sample postgresql.lock_timeout/src/backend/utils/misc/postgresql.conf.sample
*** postgresql.newtimeout/src/backend/utils/misc/postgresql.conf.sample 2012-04-05 07:27:01.737459935 +0200
--- postgresql.lock_timeout/src/backend/utils/misc/postgresql.conf.sample 2012-04-05 07:13:04.292287949 +0200
***************
*** 527,532 ****
--- 527,535 ----
#------------------------------------------------------------------------------
#deadlock_timeout = 1s
+ #lock_timeout = 0 # timeout value for heavy-weight locks
+ # taken by statements. 0 disables timeout
+ # unit in milliseconds, default is 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 -dcrpN postgresql.newtimeout/src/include/storage/pg_sema.h postgresql.lock_timeout/src/include/storage/pg_sema.h
*** postgresql.newtimeout/src/include/storage/pg_sema.h 2012-04-05 07:27:33.872619609 +0200
--- postgresql.lock_timeout/src/include/storage/pg_sema.h 2012-04-05 07:31:59.969943305 +0200
*************** extern void PGSemaphoreUnlock(PGSemaphor
*** 80,83 ****
--- 80,89 ----
/* 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 void PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK);
+
#endif /* PG_SEMA_H */
diff -dcrpN postgresql.newtimeout/src/include/storage/timeout.h postgresql.lock_timeout/src/include/storage/timeout.h
*** postgresql.newtimeout/src/include/storage/timeout.h 2012-04-05 07:24:44.111776574 +0200
--- postgresql.lock_timeout/src/include/storage/timeout.h 2012-04-05 07:13:04.294287959 +0200
***************
*** 19,27 ****
--- 19,29 ----
/* configurable options */
extern int DeadlockTimeout;
extern int StatementTimeout;
+ extern int LockTimeout;
typedef enum TimeoutName {
DEADLOCK_TIMEOUT,
+ LOCK_TIMEOUT,
STATEMENT_TIMEOUT,
STANDBY_DEADLOCK_TIMEOUT,
STANDBY_TIMEOUT,
diff -dcrpN postgresql.newtimeout/src/test/regress/expected/prepared_xacts.out postgresql.lock_timeout/src/test/regress/expected/prepared_xacts.out
*** postgresql.newtimeout/src/test/regress/expected/prepared_xacts.out 2012-04-05 07:29:32.681210283 +0200
--- postgresql.lock_timeout/src/test/regress/expected/prepared_xacts.out 2012-04-05 07:13:04.294287959 +0200
*************** set statement_timeout to 2000;
*** 198,203 ****
--- 198,210 ----
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: could not obtain lock on relation "pxtest3"
+ LINE 1: SELECT * FROM pxtest3;
+ ^
+ reset lock_timeout;
-- Disconnect, we will continue testing in a different backend
\c -
-- There should still be two prepared transactions
*************** set statement_timeout to 2000;
*** 213,218 ****
--- 220,232 ----
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: could not obtain lock on relation "pxtest3"
+ LINE 1: SELECT * FROM pxtest3;
+ ^
+ reset lock_timeout;
-- Commit table creation
COMMIT PREPARED 'regress-one';
\d pxtest2
diff -dcrpN postgresql.newtimeout/src/test/regress/sql/prepared_xacts.sql postgresql.lock_timeout/src/test/regress/sql/prepared_xacts.sql
*** postgresql.newtimeout/src/test/regress/sql/prepared_xacts.sql 2012-04-05 07:28:25.880878108 +0200
--- postgresql.lock_timeout/src/test/regress/sql/prepared_xacts.sql 2012-04-05 07:13:04.295287964 +0200
*************** set statement_timeout to 2000;
*** 126,131 ****
--- 126,136 ----
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 -
*************** set statement_timeout to 2000;
*** 137,142 ****
--- 142,152 ----
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
On 05/04/12 08:02, Boszormenyi Zoltan wrote:
2012-04-04 21:30 keltezéssel, Alvaro Herrera írta:
I think this patch is doing two things: first touching infrastructure
stuff and then adding lock_timeout on top of that. Would it work to
split the patch in two pieces?Sure. Attached is the split version.
Best regards,
Zoltán Böszörményi
Hi,
I've started looking at and testing both patches.
Technically speaking, I think the source looks much better than the
first version of lock timeout, and may help adding other timeouts in the
future. I haven't tested it in depth though, because I encountered the
following problem:
While testing the patch, I found a way to crash PG. But what's weird is
that it crashes also with an unpatched git version.
Here is the way to reproduce it (I have done it with a pgbench schema):
- Set a small statement_timeout (just to save time during the tests)
Session1:
=#BEGIN;
=#lock TABLE pgbench_accounts ;
Session 2:
=#BEGIN;
=#lock TABLE pgbench_accounts ;
ERROR: canceling statement due to statement timeout
=# lock TABLE pgbench_accounts ;
I'm using \set ON_ERROR_ROLLBACK INTERACTIVE by the way. It can also be
done with a rollback to savepoint of course.
Session 2 crashes with this : TRAP : FailedAssertion(«
!(locallock->holdsStrongLockCount == 0) », fichier : « lock.c », ligne :
749).
It can also be done without a statement_timeout, and a control-C on the
second lock table.
I didn't touch anything but this. It occurs everytime, when asserts are
activated.
I tried it on 9.1.3, and I couldn't make it crash with the same sequence
of events. So maybe it's something introduced since ? Or is the assert
still valid ?
Cheers
2012-04-06 14:47 keltezéssel, Cousin Marc írta:
On 05/04/12 08:02, Boszormenyi Zoltan wrote:
2012-04-04 21:30 keltezéssel, Alvaro Herrera írta:
I think this patch is doing two things: first touching infrastructure
stuff and then adding lock_timeout on top of that. Would it work to
split the patch in two pieces?Sure. Attached is the split version.
Best regards,
Zoltán BöszörményiHi,
I've started looking at and testing both patches.
Technically speaking, I think the source looks much better than the
first version of lock timeout, and may help adding other timeouts in the
future.
Thanks.
I haven't tested it in depth though, because I encountered the
following problem:While testing the patch, I found a way to crash PG. But what's weird is
that it crashes also with an unpatched git version.Here is the way to reproduce it (I have done it with a pgbench schema):
- Set a small statement_timeout (just to save time during the tests)
Session1:
=#BEGIN;
=#lock TABLE pgbench_accounts ;Session 2:
=#BEGIN;
=#lock TABLE pgbench_accounts ;
ERROR: canceling statement due to statement timeout
=# lock TABLE pgbench_accounts ;I'm using \set ON_ERROR_ROLLBACK INTERACTIVE by the way. It can also be
done with a rollback to savepoint of course.Session 2 crashes with this : TRAP : FailedAssertion(«
!(locallock->holdsStrongLockCount == 0) », fichier : « lock.c », ligne :
749).It can also be done without a statement_timeout, and a control-C on the
second lock table.
Indeed, the unpatched GIT version crashes if you enter
=#lock TABLE pgbench_accounts ;
the second time in session 2 after the first one failed. Also,
manually spelling it out:
Session 1:
$ psql
psql (9.2devel)
Type "help" for help.
zozo=# begin;
BEGIN
zozo=# lock table pgbench_accounts;
LOCK TABLE
zozo=#
Session 2:
zozo=# begin;
BEGIN
zozo=# savepoint a;
SAVEPOINT
zozo=# lock table pgbench_accounts;
ERROR: canceling statement due to statement timeout
zozo=# rollback to a;
ROLLBACK
zozo=# savepoint b;
SAVEPOINT
zozo=# lock table pgbench_accounts;
The connection to the server was lost. Attempting reset: Failed.
!>
Server log after the second lock table:
TRAP: FailedAssertion("!(locallock->holdsStrongLockCount == 0)", File: "lock.c", Line: 749)
LOG: server process (PID 12978) was terminated by signal 6: Aborted
Best regards,
Zoltán Böszörményi
I didn't touch anything but this. It occurs everytime, when asserts are
activated.I tried it on 9.1.3, and I couldn't make it crash with the same sequence
of events. So maybe it's something introduced since ? Or is the assert
still valid ?Cheers
--
----------------------------------
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/
2012-04-08 11:24 keltezéssel, Boszormenyi Zoltan írta:
2012-04-06 14:47 keltezéssel, Cousin Marc írta:
On 05/04/12 08:02, Boszormenyi Zoltan wrote:
2012-04-04 21:30 keltezéssel, Alvaro Herrera írta:
I think this patch is doing two things: first touching infrastructure
stuff and then adding lock_timeout on top of that. Would it work to
split the patch in two pieces?Sure. Attached is the split version.
Best regards,
Zoltán BöszörményiHi,
I've started looking at and testing both patches.
Technically speaking, I think the source looks much better than the
first version of lock timeout, and may help adding other timeouts in the
future.Thanks.
I haven't tested it in depth though, because I encountered the
following problem:While testing the patch, I found a way to crash PG. But what's weird is
that it crashes also with an unpatched git version.Here is the way to reproduce it (I have done it with a pgbench schema):
- Set a small statement_timeout (just to save time during the tests)
Session1:
=#BEGIN;
=#lock TABLE pgbench_accounts ;Session 2:
=#BEGIN;
=#lock TABLE pgbench_accounts ;
ERROR: canceling statement due to statement timeout
=# lock TABLE pgbench_accounts ;I'm using \set ON_ERROR_ROLLBACK INTERACTIVE by the way. It can also be
done with a rollback to savepoint of course.Session 2 crashes with this : TRAP : FailedAssertion(«
!(locallock->holdsStrongLockCount == 0) », fichier : « lock.c », ligne :
749).It can also be done without a statement_timeout, and a control-C on the
second lock table.Indeed, the unpatched GIT version crashes if you enter
=#lock TABLE pgbench_accounts ;
the second time in session 2 after the first one failed. Also,
manually spelling it out:Session 1:
$ psql
psql (9.2devel)
Type "help" for help.zozo=# begin;
BEGIN
zozo=# lock table pgbench_accounts;
LOCK TABLE
zozo=#Session 2:
zozo=# begin;
BEGIN
zozo=# savepoint a;
SAVEPOINT
zozo=# lock table pgbench_accounts;
ERROR: canceling statement due to statement timeout
zozo=# rollback to a;
ROLLBACK
zozo=# savepoint b;
SAVEPOINT
zozo=# lock table pgbench_accounts;
The connection to the server was lost. Attempting reset: Failed.
!>Server log after the second lock table:
TRAP: FailedAssertion("!(locallock->holdsStrongLockCount == 0)", File: "lock.c", Line: 749)
LOG: server process (PID 12978) was terminated by signal 6: AbortedBest regards,
Zoltán Böszörményi
Robert, the Assert triggering with the above procedure
is in your "fast path" locking code with current GIT.
Best regards,
Zoltán Böszörményi
I didn't touch anything but this. It occurs everytime, when asserts are
activated.I tried it on 9.1.3, and I couldn't make it crash with the same sequence
of events. So maybe it's something introduced since ? Or is the assert
still valid ?Cheers
--
----------------------------------
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/
2012-04-06 14:47 keltezéssel, Cousin Marc írta:
On 05/04/12 08:02, Boszormenyi Zoltan wrote:
2012-04-04 21:30 keltezéssel, Alvaro Herrera írta:
I think this patch is doing two things: first touching infrastructure
stuff and then adding lock_timeout on top of that. Would it work to
split the patch in two pieces?Sure. Attached is the split version.
Best regards,
Zoltán BöszörményiHi,
I've started looking at and testing both patches.
Technically speaking, I think the source looks much better than the
first version of lock timeout, and may help adding other timeouts in the
future. I haven't tested it in depth though, because I encountered the
following problem:While testing the patch, I found a way to crash PG. But what's weird is
that it crashes also with an unpatched git version.Here is the way to reproduce it (I have done it with a pgbench schema):
- Set a small statement_timeout (just to save time during the tests)
Session1:
=#BEGIN;
=#lock TABLE pgbench_accounts ;Session 2:
=#BEGIN;
=#lock TABLE pgbench_accounts ;
ERROR: canceling statement due to statement timeout
=# lock TABLE pgbench_accounts ;I'm using \set ON_ERROR_ROLLBACK INTERACTIVE by the way. It can also be
done with a rollback to savepoint of course.Session 2 crashes with this : TRAP : FailedAssertion(«
!(locallock->holdsStrongLockCount == 0) », fichier : « lock.c », ligne :
749).It can also be done without a statement_timeout, and a control-C on the
second lock table.I didn't touch anything but this. It occurs everytime, when asserts are
activated.I tried it on 9.1.3, and I couldn't make it crash with the same sequence
of events. So maybe it's something introduced since ? Or is the assert
still valid ?Cheers
Attached are the new patches. I rebased them to current GIT and
they are expected to be applied after Robert Haas' patch in the
"bug in fast-path locking" thread.
Now it survives the above scenario.
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/
Attachments:
1-timeout-framework-v2.patchtext/x-patch; name=1-timeout-framework-v2.patchDownload
diff -dcrpN postgresql.1/src/backend/postmaster/autovacuum.c postgresql.2/src/backend/postmaster/autovacuum.c
*** postgresql.1/src/backend/postmaster/autovacuum.c 2012-04-09 07:18:15.332825138 +0200
--- postgresql.2/src/backend/postmaster/autovacuum.c 2012-04-10 08:23:29.663356606 +0200
***************
*** 86,94 ****
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
- #include "storage/proc.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "tcop/tcopprot.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
--- 86,94 ----
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
+ #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
*************** AutoVacLauncherMain(int argc, char *argv
*** 484,490 ****
/* Forget any pending QueryCancel request */
QueryCancelPending = false;
! disable_sig_alarm(true);
QueryCancelPending = false; /* again in case timeout occurred */
/* Report the error to the server log */
--- 484,490 ----
/* Forget any pending QueryCancel request */
QueryCancelPending = false;
! disable_all_timeouts(false);
QueryCancelPending = false; /* again in case timeout occurred */
/* Report the error to the server log */
diff -dcrpN postgresql.1/src/backend/postmaster/postmaster.c postgresql.2/src/backend/postmaster/postmaster.c
*** postgresql.1/src/backend/postmaster/postmaster.c 2012-04-09 07:18:15.336825302 +0200
--- postgresql.2/src/backend/postmaster/postmaster.c 2012-04-10 08:23:29.673357026 +0200
***************
*** 112,118 ****
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
! #include "storage/proc.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/datetime.h"
--- 112,118 ----
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
! #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/datetime.h"
*************** BackendInitialize(Port *port)
*** 3471,3479 ****
* Ready to begin client interaction. We will give up and exit(1) after a
* time delay, so that a broken client can't hog a connection
* indefinitely. PreAuthDelay and any DNS interactions above don't count
! * against the time limit.
*/
! if (!enable_sig_alarm(AuthenticationTimeout * 1000, false))
elog(FATAL, "could not set timer for startup packet timeout");
/*
--- 3471,3479 ----
* Ready to begin client interaction. We will give up and exit(1) after a
* time delay, so that a broken client can't hog a connection
* indefinitely. PreAuthDelay and any DNS interactions above don't count
! * against the time limit. Use the deadlock timeout interface.
*/
! if (!enable_timeout(DEADLOCK_TIMEOUT, AuthenticationTimeout * 1000))
elog(FATAL, "could not set timer for startup packet timeout");
/*
*************** BackendInitialize(Port *port)
*** 3511,3517 ****
/*
* Disable the timeout, and prevent SIGTERM/SIGQUIT again.
*/
! if (!disable_sig_alarm(false))
elog(FATAL, "could not disable timer for startup packet timeout");
PG_SETMASK(&BlockSig);
}
--- 3511,3517 ----
/*
* Disable the timeout, and prevent SIGTERM/SIGQUIT again.
*/
! if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for startup packet timeout");
PG_SETMASK(&BlockSig);
}
diff -dcrpN postgresql.1/src/backend/postmaster/startup.c postgresql.2/src/backend/postmaster/startup.c
*** postgresql.1/src/backend/postmaster/startup.c 2012-04-09 07:18:15.336825302 +0200
--- postgresql.2/src/backend/postmaster/startup.c 2012-04-10 08:23:29.683357446 +0200
***************
*** 27,33 ****
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
! #include "storage/proc.h"
#include "utils/guc.h"
--- 27,33 ----
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
! #include "storage/timeout.h"
#include "utils/guc.h"
*************** StartupProcessMain(void)
*** 195,202 ****
pqsignal(SIGTERM, StartupProcShutdownHandler); /* request shutdown */
pqsignal(SIGQUIT, startupproc_quickdie); /* hard crash time */
if (EnableHotStandby)
! pqsignal(SIGALRM, handle_standby_sig_alarm); /* ignored unless
! * InHotStandby */
else
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
--- 195,202 ----
pqsignal(SIGTERM, StartupProcShutdownHandler); /* request shutdown */
pqsignal(SIGQUIT, startupproc_quickdie); /* hard crash time */
if (EnableHotStandby)
! pqsignal(SIGALRM, handle_sig_alarm); /* ignored unless
! * InHotStandby */
else
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
diff -dcrpN postgresql.1/src/backend/storage/ipc/standby.c postgresql.2/src/backend/storage/ipc/standby.c
*** postgresql.1/src/backend/storage/ipc/standby.c 2012-04-09 07:18:15.366826532 +0200
--- postgresql.2/src/backend/storage/ipc/standby.c 2012-04-10 08:23:29.685357530 +0200
***************
*** 23,32 ****
#include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
- #include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/sinvaladt.h"
#include "storage/standby.h"
#include "utils/ps_status.h"
#include "utils/timestamp.h"
--- 23,32 ----
#include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
#include "storage/procarray.h"
#include "storage/sinvaladt.h"
#include "storage/standby.h"
+ #include "storage/timeout.h"
#include "utils/ps_status.h"
#include "utils/timestamp.h"
*************** ResolveRecoveryConflictWithLock(Oid dbOi
*** 394,400 ****
void
ResolveRecoveryConflictWithBufferPin(void)
{
- bool sig_alarm_enabled = false;
TimestampTz ltime;
TimestampTz now;
--- 394,399 ----
*************** ResolveRecoveryConflictWithBufferPin(voi
*** 409,417 ****
* We're willing to wait forever for conflicts, so set timeout for
* deadlock check (only)
*/
! if (enable_standby_sig_alarm(now, now, true))
! sig_alarm_enabled = true;
! else
elog(FATAL, "could not set timer for process wakeup");
}
else if (now >= ltime)
--- 408,414 ----
* We're willing to wait forever for conflicts, so set timeout for
* deadlock check (only)
*/
! if (!enable_timeout(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout))
elog(FATAL, "could not set timer for process wakeup");
}
else if (now >= ltime)
*************** ResolveRecoveryConflictWithBufferPin(voi
*** 423,446 ****
}
else
{
/*
* Wake up at ltime, and check for deadlocks as well if we will be
* waiting longer than deadlock_timeout
*/
! if (enable_standby_sig_alarm(now, ltime, false))
! sig_alarm_enabled = true;
! else
elog(FATAL, "could not set timer for process wakeup");
}
/* Wait to be signaled by UnpinBuffer() */
ProcWaitForSignal();
! if (sig_alarm_enabled)
! {
! if (!disable_standby_sig_alarm())
! elog(FATAL, "could not disable timer for process wakeup");
! }
}
void
--- 420,447 ----
}
else
{
+ long secs, msecs;
+ int usecs;
+
/*
* Wake up at ltime, and check for deadlocks as well if we will be
* waiting longer than deadlock_timeout
*/
! if (!enable_timeout(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout))
! elog(FATAL, "could not set timer for process wakeup");
!
! TimestampDifference(now, ltime, &secs, &usecs);
! msecs = secs * 1000 + usecs / 1000;
!
! if (!enable_timeout(STANDBY_TIMEOUT, msecs))
elog(FATAL, "could not set timer for process wakeup");
}
/* Wait to be signaled by UnpinBuffer() */
ProcWaitForSignal();
! if (!disable_all_timeouts(false))
! elog(FATAL, "could not disable timer for process wakeup");
}
void
diff -dcrpN postgresql.1/src/backend/storage/lmgr/Makefile postgresql.2/src/backend/storage/lmgr/Makefile
*** postgresql.1/src/backend/storage/lmgr/Makefile 2012-04-09 07:18:15.366826532 +0200
--- postgresql.2/src/backend/storage/lmgr/Makefile 2012-04-10 08:23:29.690357740 +0200
*************** subdir = src/backend/storage/lmgr
*** 12,18 ****
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
! OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o predicate.o
include $(top_srcdir)/src/backend/common.mk
--- 12,18 ----
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
! OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o predicate.o timeout.o
include $(top_srcdir)/src/backend/common.mk
diff -dcrpN 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-04-10 08:13:00.077488177 +0200
--- postgresql.2/src/backend/storage/lmgr/proc.c 2012-04-10 08:23:29.697358034 +0200
***************
*** 48,59 ****
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/spin.h"
#include "utils/timestamp.h"
/* GUC variables */
- int DeadlockTimeout = 1000;
- int StatementTimeout = 0;
bool log_lock_waits = false;
/* Pointer to this process's PGPROC struct, if any */
--- 48,58 ----
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/spin.h"
+ #include "storage/timeout.h"
#include "utils/timestamp.h"
/* GUC variables */
bool log_lock_waits = false;
/* Pointer to this process's PGPROC struct, if any */
*************** PGPROC *PreparedXactProcs = NULL;
*** 77,103 ****
/* If we are waiting for a lock, this points to the associated LOCALLOCK */
static LOCALLOCK *lockAwaited = NULL;
! /* Mark these volatile because they can be changed by signal handler */
! static volatile bool standby_timeout_active = false;
! static volatile bool statement_timeout_active = false;
! static volatile bool deadlock_timeout_active = false;
! static volatile DeadLockState deadlock_state = DS_NOT_YET_CHECKED;
! volatile bool cancel_from_timeout = false;
!
! /* timeout_start_time is set when log_lock_waits is true */
! static TimestampTz timeout_start_time;
!
! /* statement_fin_time is valid only if statement_timeout_active is true */
! static TimestampTz statement_fin_time;
! static TimestampTz statement_fin_time2; /* valid only in recovery */
!
static void RemoveProcFromArray(int code, Datum arg);
static void ProcKill(int code, Datum arg);
static void AuxiliaryProcKill(int code, Datum arg);
- static bool CheckStatementTimeout(void);
- static bool CheckStandbyTimeout(void);
-
/*
* Report shared-memory space needed by InitProcGlobal.
--- 76,87 ----
/* If we are waiting for a lock, this points to the associated LOCALLOCK */
static LOCALLOCK *lockAwaited = NULL;
! /* Declared in timeout.c */
! extern volatile DeadLockState deadlock_state;
static void RemoveProcFromArray(int code, Datum arg);
static void ProcKill(int code, Datum arg);
static void AuxiliaryProcKill(int code, Datum arg);
/*
* Report shared-memory space needed by InitProcGlobal.
*************** LockErrorCleanup(void)
*** 654,660 ****
return;
/* Turn off the deadlock timer, if it's still running (see ProcSleep) */
! disable_sig_alarm(false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
--- 638,644 ----
return;
/* Turn off the deadlock timer, if it's still running (see ProcSleep) */
! disable_timeout(DEADLOCK_TIMEOUT, false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1047,1053 ****
* 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 (!enable_sig_alarm(DeadlockTimeout, false))
elog(FATAL, "could not set timer for process wakeup");
/*
--- 1031,1037 ----
* 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 (!enable_timeout(DEADLOCK_TIMEOUT, DeadlockTimeout))
elog(FATAL, "could not set timer for process wakeup");
/*
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1137,1143 ****
DescribeLockTag(&buf, &locallock->tag.lock);
modename = GetLockmodeName(locallock->tag.lock.locktag_lockmethodid,
lockmode);
! TimestampDifference(timeout_start_time, GetCurrentTimestamp(),
&secs, &usecs);
msecs = secs * 1000 + usecs / 1000;
usecs = usecs % 1000;
--- 1121,1128 ----
DescribeLockTag(&buf, &locallock->tag.lock);
modename = GetLockmodeName(locallock->tag.lock.locktag_lockmethodid,
lockmode);
! TimestampDifference(get_timeout_start(DEADLOCK_TIMEOUT),
! GetCurrentTimestamp(),
&secs, &usecs);
msecs = secs * 1000 + usecs / 1000;
usecs = usecs % 1000;
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1199,1205 ****
/*
* Disable the timer, if it's still running
*/
! if (!disable_sig_alarm(false))
elog(FATAL, "could not disable timer for process wakeup");
/*
--- 1184,1190 ----
/*
* Disable the timer, if it's still running
*/
! if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for process wakeup");
/*
*************** ProcLockWakeup(LockMethod lockMethodTabl
*** 1331,1451 ****
}
/*
- * CheckDeadLock
- *
- * We only get to this routine if we got SIGALRM after DeadlockTimeout
- * while waiting for a lock to be released by some other process. Look
- * to see if there's a deadlock; if not, just return and continue waiting.
- * (But signal ProcSleep to log a message, if log_lock_waits is true.)
- * If we have a real deadlock, remove ourselves from the lock's wait queue
- * and signal an error to ProcSleep.
- *
- * NB: this is run inside a signal handler, so be very wary about what is done
- * here or in called routines.
- */
- static void
- CheckDeadLock(void)
- {
- int i;
-
- /*
- * Acquire exclusive lock on the entire shared lock data structures. Must
- * grab LWLocks in partition-number order to avoid LWLock deadlock.
- *
- * Note that the deadlock check interrupt had better not be enabled
- * anywhere that this process itself holds lock partition locks, else this
- * will wait forever. Also note that LWLockAcquire creates a critical
- * section, so that this routine cannot be interrupted by cancel/die
- * interrupts.
- */
- for (i = 0; i < NUM_LOCK_PARTITIONS; i++)
- LWLockAcquire(FirstLockMgrLock + i, LW_EXCLUSIVE);
-
- /*
- * Check to see if we've been awoken by anyone in the interim.
- *
- * If we have, we can return and resume our transaction -- happy day.
- * Before we are awoken the process releasing the lock grants it to us so
- * we know that we don't have to wait anymore.
- *
- * We check by looking to see if we've been unlinked from the wait queue.
- * This is quicker than checking our semaphore's state, since no kernel
- * call is needed, and it is safe because we hold the lock partition lock.
- */
- if (MyProc->links.prev == NULL ||
- MyProc->links.next == NULL)
- goto check_done;
-
- #ifdef LOCK_DEBUG
- if (Debug_deadlocks)
- DumpAllLocks();
- #endif
-
- /* Run the deadlock check, and set deadlock_state for use by ProcSleep */
- deadlock_state = DeadLockCheck(MyProc);
-
- if (deadlock_state == DS_HARD_DEADLOCK)
- {
- /*
- * Oops. We have a deadlock.
- *
- * Get this process out of wait state. (Note: we could do this more
- * efficiently by relying on lockAwaited, but use this coding to
- * preserve the flexibility to kill some other transaction than the
- * one detecting the deadlock.)
- *
- * RemoveFromWaitQueue sets MyProc->waitStatus to STATUS_ERROR, so
- * ProcSleep will report an error after we return from the signal
- * handler.
- */
- Assert(MyProc->waitLock != NULL);
- RemoveFromWaitQueue(MyProc, LockTagHashCode(&(MyProc->waitLock->tag)));
-
- /*
- * Unlock my semaphore so that the interrupted ProcSleep() call can
- * finish.
- */
- PGSemaphoreUnlock(&MyProc->sem);
-
- /*
- * We're done here. Transaction abort caused by the error that
- * ProcSleep will raise will cause any other locks we hold to be
- * released, thus allowing other processes to wake up; we don't need
- * to do that here. NOTE: an exception is that releasing locks we
- * hold doesn't consider the possibility of waiters that were blocked
- * behind us on the lock we just failed to get, and might now be
- * wakable because we're not in front of them anymore. However,
- * RemoveFromWaitQueue took care of waking up any such processes.
- */
- }
- else if (log_lock_waits || deadlock_state == DS_BLOCKED_BY_AUTOVACUUM)
- {
- /*
- * Unlock my semaphore so that the interrupted ProcSleep() call can
- * print the log message (we daren't do it here because we are inside
- * a signal handler). It will then sleep again until someone releases
- * the lock.
- *
- * If blocked by autovacuum, this wakeup will enable ProcSleep to send
- * the canceling signal to the autovacuum worker.
- */
- PGSemaphoreUnlock(&MyProc->sem);
- }
-
- /*
- * And release locks. We do this in reverse order for two reasons: (1)
- * Anyone else who needs more than one of the locks will be trying to lock
- * them in increasing order; we don't want to release the other process
- * until it can get all the locks it needs. (2) This avoids O(N^2)
- * behavior inside LWLockRelease.
- */
- check_done:
- for (i = NUM_LOCK_PARTITIONS; --i >= 0;)
- LWLockRelease(FirstLockMgrLock + i);
- }
-
-
- /*
* ProcWaitForSignal - wait for a signal from another backend.
*
* This can share the semaphore normally used for waiting for locks,
--- 1316,1321 ----
*************** ProcSendSignal(int pid)
*** 1497,1897 ****
if (proc != NULL)
PGSemaphoreUnlock(&proc->sem);
}
-
-
- /*****************************************************************************
- * SIGALRM interrupt support
- *
- * Maybe these should be in pqsignal.c?
- *****************************************************************************/
-
- /*
- * Enable the SIGALRM interrupt to fire after the specified delay
- *
- * Delay is given in milliseconds. Caller should be sure a SIGALRM
- * signal handler is installed before this is called.
- *
- * This code properly handles nesting of deadlock timeout alarms within
- * statement timeout alarms.
- *
- * Returns TRUE if okay, FALSE on failure.
- */
- bool
- enable_sig_alarm(int delayms, bool is_statement_timeout)
- {
- TimestampTz fin_time;
- struct itimerval timeval;
-
- if (is_statement_timeout)
- {
- /*
- * Begin statement-level timeout
- *
- * Note that we compute statement_fin_time with reference to the
- * statement_timestamp, but apply the specified delay without any
- * correction; that is, we ignore whatever time has elapsed since
- * statement_timestamp was set. In the normal case only a small
- * interval will have elapsed and so this doesn't matter, but there
- * are corner cases (involving multi-statement query strings with
- * embedded COMMIT or ROLLBACK) where we might re-initialize the
- * statement timeout long after initial receipt of the message. In
- * such cases the enforcement of the statement timeout will be a bit
- * inconsistent. This annoyance is judged not worth the cost of
- * performing an additional gettimeofday() here.
- */
- Assert(!deadlock_timeout_active);
- fin_time = GetCurrentStatementStartTimestamp();
- fin_time = TimestampTzPlusMilliseconds(fin_time, delayms);
- statement_fin_time = fin_time;
- cancel_from_timeout = false;
- statement_timeout_active = true;
- }
- else if (statement_timeout_active)
- {
- /*
- * Begin deadlock timeout with statement-level timeout active
- *
- * Here, we want to interrupt at the closer of the two timeout times.
- * If fin_time >= statement_fin_time then we need not touch the
- * existing timer setting; else set up to interrupt at the deadlock
- * timeout time.
- *
- * NOTE: in this case it is possible that this routine will be
- * interrupted by the previously-set timer alarm. This is okay
- * because the signal handler will do only what it should do according
- * to the state variables. The deadlock checker may get run earlier
- * than normal, but that does no harm.
- */
- timeout_start_time = GetCurrentTimestamp();
- fin_time = TimestampTzPlusMilliseconds(timeout_start_time, delayms);
- deadlock_timeout_active = true;
- if (fin_time >= statement_fin_time)
- return true;
- }
- else
- {
- /* Begin deadlock timeout with no statement-level timeout */
- deadlock_timeout_active = true;
- /* GetCurrentTimestamp can be expensive, so only do it if we must */
- if (log_lock_waits)
- timeout_start_time = GetCurrentTimestamp();
- }
-
- /* If we reach here, okay to set the timer interrupt */
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = delayms / 1000;
- timeval.it_value.tv_usec = (delayms % 1000) * 1000;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- return true;
- }
-
- /*
- * Cancel the SIGALRM timer, either for a deadlock timeout or a statement
- * timeout. If a deadlock timeout is canceled, any active statement timeout
- * remains in force.
- *
- * Returns TRUE if okay, FALSE on failure.
- */
- bool
- disable_sig_alarm(bool is_statement_timeout)
- {
- /*
- * Always disable the interrupt if it is active; this avoids being
- * interrupted by the signal handler and thereby possibly getting
- * confused.
- *
- * We will re-enable the interrupt if necessary in CheckStatementTimeout.
- */
- if (statement_timeout_active || deadlock_timeout_active)
- {
- struct itimerval timeval;
-
- MemSet(&timeval, 0, sizeof(struct itimerval));
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- {
- statement_timeout_active = false;
- cancel_from_timeout = false;
- deadlock_timeout_active = false;
- return false;
- }
- }
-
- /* Always cancel deadlock timeout, in case this is error cleanup */
- deadlock_timeout_active = false;
-
- /* Cancel or reschedule statement timeout */
- if (is_statement_timeout)
- {
- statement_timeout_active = false;
- cancel_from_timeout = false;
- }
- else if (statement_timeout_active)
- {
- if (!CheckStatementTimeout())
- return false;
- }
- return true;
- }
-
-
- /*
- * Check for statement timeout. If the timeout time has come,
- * trigger a query-cancel interrupt; if not, reschedule the SIGALRM
- * interrupt to occur at the right time.
- *
- * Returns true if okay, false if failed to set the interrupt.
- */
- static bool
- CheckStatementTimeout(void)
- {
- TimestampTz now;
-
- if (!statement_timeout_active)
- return true; /* do nothing if not active */
-
- now = GetCurrentTimestamp();
-
- if (now >= statement_fin_time)
- {
- /* Time to die */
- statement_timeout_active = false;
- cancel_from_timeout = true;
- #ifdef HAVE_SETSID
- /* try to signal whole process group */
- kill(-MyProcPid, SIGINT);
- #endif
- kill(MyProcPid, SIGINT);
- }
- else
- {
- /* Not time yet, so (re)schedule the interrupt */
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
-
- /*
- * It's possible that the difference is less than a microsecond;
- * ensure we don't cancel, rather than set, the interrupt.
- */
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- }
-
- return true;
- }
-
-
- /*
- * Signal handler for SIGALRM for normal user backends
- *
- * Process deadlock check and/or statement timeout check, as needed.
- * To avoid various edge cases, we must be careful to do nothing
- * when there is nothing to be done. We also need to be able to
- * reschedule the timer interrupt if called before end of statement.
- */
- void
- handle_sig_alarm(SIGNAL_ARGS)
- {
- int save_errno = errno;
-
- /* SIGALRM is cause for waking anything waiting on the process latch */
- if (MyProc)
- SetLatch(&MyProc->procLatch);
-
- if (deadlock_timeout_active)
- {
- deadlock_timeout_active = false;
- CheckDeadLock();
- }
-
- if (statement_timeout_active)
- (void) CheckStatementTimeout();
-
- errno = save_errno;
- }
-
- /*
- * Signal handler for SIGALRM in Startup process
- *
- * To avoid various edge cases, we must be careful to do nothing
- * when there is nothing to be done. We also need to be able to
- * reschedule the timer interrupt if called before end of statement.
- *
- * We set either deadlock_timeout_active or statement_timeout_active
- * or both. Interrupts are enabled if standby_timeout_active.
- */
- bool
- enable_standby_sig_alarm(TimestampTz now, TimestampTz fin_time, bool deadlock_only)
- {
- TimestampTz deadlock_time = TimestampTzPlusMilliseconds(now,
- DeadlockTimeout);
-
- if (deadlock_only)
- {
- /*
- * Wake up at deadlock_time only, then wait forever
- */
- statement_fin_time = deadlock_time;
- deadlock_timeout_active = true;
- statement_timeout_active = false;
- }
- else if (fin_time > deadlock_time)
- {
- /*
- * Wake up at deadlock_time, then again at fin_time
- */
- statement_fin_time = deadlock_time;
- statement_fin_time2 = fin_time;
- deadlock_timeout_active = true;
- statement_timeout_active = true;
- }
- else
- {
- /*
- * Wake only at fin_time because its fairly soon
- */
- statement_fin_time = fin_time;
- deadlock_timeout_active = false;
- statement_timeout_active = true;
- }
-
- if (deadlock_timeout_active || statement_timeout_active)
- {
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- standby_timeout_active = true;
- }
-
- return true;
- }
-
- bool
- disable_standby_sig_alarm(void)
- {
- /*
- * Always disable the interrupt if it is active; this avoids being
- * interrupted by the signal handler and thereby possibly getting
- * confused.
- *
- * We will re-enable the interrupt if necessary in CheckStandbyTimeout.
- */
- if (standby_timeout_active)
- {
- struct itimerval timeval;
-
- MemSet(&timeval, 0, sizeof(struct itimerval));
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- {
- standby_timeout_active = false;
- return false;
- }
- }
-
- standby_timeout_active = false;
-
- return true;
- }
-
- /*
- * CheckStandbyTimeout() runs unconditionally in the Startup process
- * SIGALRM handler. Timers will only be set when InHotStandby.
- * We simply ignore any signals unless the timer has been set.
- */
- static bool
- CheckStandbyTimeout(void)
- {
- TimestampTz now;
- bool reschedule = false;
-
- standby_timeout_active = false;
-
- now = GetCurrentTimestamp();
-
- /*
- * Reschedule the timer if its not time to wake yet, or if we have both
- * timers set and the first one has just been reached.
- */
- if (now >= statement_fin_time)
- {
- if (deadlock_timeout_active)
- {
- /*
- * We're still waiting when we reach deadlock timeout, so send out
- * a request to have other backends check themselves for deadlock.
- * Then continue waiting until statement_fin_time, if that's set.
- */
- SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
- deadlock_timeout_active = false;
-
- /*
- * Begin second waiting period if required.
- */
- if (statement_timeout_active)
- {
- reschedule = true;
- statement_fin_time = statement_fin_time2;
- }
- }
- else
- {
- /*
- * We've now reached statement_fin_time, so ask all conflicts to
- * leave, so we can press ahead with applying changes in recovery.
- */
- SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
- }
- }
- else
- reschedule = true;
-
- if (reschedule)
- {
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- standby_timeout_active = true;
- }
-
- return true;
- }
-
- void
- handle_standby_sig_alarm(SIGNAL_ARGS)
- {
- int save_errno = errno;
-
- if (standby_timeout_active)
- (void) CheckStandbyTimeout();
-
- errno = save_errno;
- }
--- 1367,1369 ----
diff -dcrpN postgresql.1/src/backend/storage/lmgr/timeout.c postgresql.2/src/backend/storage/lmgr/timeout.c
*** postgresql.1/src/backend/storage/lmgr/timeout.c 1970-01-01 01:00:00.000000000 +0100
--- postgresql.2/src/backend/storage/lmgr/timeout.c 2012-04-10 08:23:29.702358244 +0200
***************
*** 0 ****
--- 1,680 ----
+ /*-------------------------------------------------------------------------
+ *
+ * timeout.c
+ * routines to manage timeout sources handled by SIGALRM
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/storage/lmgr/timeout.c
+ *
+ *-------------------------------------------------------------------------
+ */
+ /*
+ * Interface:
+ * enable_timeout(),
+ * disable_timeout(), disable_all_timeouts()
+ * get_timeout_indicator(), get_timeout_start()
+ * and
+ * handle_sig_alarm()
+ */
+ #include "postgres.h"
+
+ #include <sys/time.h>
+
+ #include "access/xact.h"
+ #include "miscadmin.h"
+ #include "storage/proc.h"
+ #include "storage/standby.h"
+ #include "storage/timeout.h"
+ #include "utils/timestamp.h"
+
+ /* GUC variables */
+ int DeadlockTimeout = 1000;
+ int StatementTimeout = 0;
+
+ /*
+ * This is used by ProcSleep() in proc.c
+ * Mark this volatile because it can be changed by the signal handler.
+ */
+ volatile DeadLockState deadlock_state = DS_NOT_YET_CHECKED;
+
+
+ /*
+ * Infrastructure for timeouts
+ */
+
+ static void InitDeadLock(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyDeadLock(bool keep_indicator);
+ static bool CheckDeadLock(void);
+
+ static void InitStatementTimeout(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyStatementTimeout(bool keep_indicator);
+ static bool CheckStatementTimeout(void);
+
+ static void InitStandbyDeadLock(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyStandbyDeadLock(bool keep_indicator);
+ static bool CheckStandbyDeadLock(void);
+
+ static void InitStandbyTimeout(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyStandbyTimeout(bool keep_indicator);
+ static bool CheckStandbyTimeout(void);
+
+ typedef void (*timeout_init)(TimestampTz, TimestampTz);
+ typedef void (*timeout_destroy)(bool);
+ typedef bool (*timeout_check)(void);
+ typedef TimestampTz (*timeout_start)(void);
+
+ typedef struct {
+ TimeoutName index;
+ bool resched_next;
+
+ /* volatile because it may be changed from the signal handler */
+ volatile bool indicator;
+
+ timeout_init timeout_init;
+ timeout_destroy timeout_destroy;
+ timeout_check timeout_check;
+ timeout_start timeout_start;
+
+ TimestampTz start_time;
+ TimestampTz fin_time;
+ } timeout_params;
+
+ /*
+ * List of possible timeout reasons in the order of enum TimeoutName.
+ * The priority of timeouts (in case two of them would trigger at the
+ * same time) is determined by this order: the earlier one in the list
+ * has higher priority.
+ */
+ static timeout_params base_timeouts[TIMEOUT_MAX] = {
+ {
+ DEADLOCK_TIMEOUT, true, false,
+ InitDeadLock, DestroyDeadLock,
+ CheckDeadLock, GetCurrentTimestamp,
+ 0
+ },
+
+ {
+ STATEMENT_TIMEOUT, false, false,
+ InitStatementTimeout, DestroyStatementTimeout,
+ CheckStatementTimeout, GetCurrentStatementStartTimestamp,
+ 0
+ },
+
+ {
+ STANDBY_DEADLOCK_TIMEOUT, true, false,
+ InitStandbyDeadLock, DestroyStandbyDeadLock,
+ CheckStandbyDeadLock, GetCurrentTimestamp,
+ 0
+ },
+
+ {
+ STANDBY_TIMEOUT, false, false,
+ InitStandbyTimeout, DestroyStandbyTimeout,
+ CheckStandbyTimeout, GetCurrentTimestamp,
+ 0
+ }
+ };
+
+ /*
+ * List of active timeouts ordered by their fin_time and priority.
+ */
+ static int n_timeouts = 0;
+ static timeout_params *timeouts[TIMEOUT_MAX];
+
+ /*****************************************************************************
+ * Internal helper functions
+ *****************************************************************************/
+
+ /*
+ * Find the index of a given timeout type in the active array
+ */
+ static int
+ find_active_timeout(TimeoutName tn)
+ {
+ int i;
+
+ for (i = 0; i < n_timeouts; i++)
+ {
+ if (timeouts[i]->index == tn)
+ return i;
+ }
+
+ return -1;
+ }
+
+ #define is_timeout_active(tn) (find_active_timeout(tn) >= 0)
+
+ /*
+ * Insert tn'th timeout into the list of timeouts at the given index
+ * if the previous timeout allows rescheduling the next one in the list.
+ */
+ static void
+ insert_timeout(TimeoutName tn, int index)
+ {
+ int i;
+
+ if (index > 0 && !timeouts[index-1]->resched_next)
+ return;
+
+ for (i = n_timeouts - 1; i >= index; i--)
+ timeouts[i+1] = timeouts[i];
+
+ timeouts[index] = &base_timeouts[tn];
+ n_timeouts++;
+ }
+
+ /*
+ * Remove the index'th element from the timeout list.
+ */
+ static void
+ remove_timeout_index(int index)
+ {
+ int i;
+
+ if (index < 0)
+ return;
+
+ for (i = index + 1; i < n_timeouts; i++)
+ timeouts[i-1] = timeouts[i];
+
+ if (n_timeouts > 0 && index < n_timeouts)
+ n_timeouts--;
+ }
+
+ /*
+ * (Re)schedule the next active timeout
+ */
+ static bool
+ schedule_timeout(TimestampTz now)
+ {
+ long secs;
+ int usecs;
+ struct itimerval timeval;
+
+ /* There is no active timeout, do nothing. */
+ if (n_timeouts == 0)
+ return true;
+
+ TimestampDifference(now, timeouts[0]->fin_time,
+ &secs, &usecs);
+
+ /*
+ * It's possible that the difference is less than a microsecond;
+ * ensure we don't cancel, rather than set, the interrupt.
+ */
+ if (secs == 0 && usecs == 0)
+ usecs = 1;
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ timeval.it_value.tv_sec = secs;
+ timeval.it_value.tv_usec = usecs;
+ if (setitimer(ITIMER_REAL, &timeval, NULL))
+ return false;
+ return true;
+ }
+
+ /*****************************************************************************
+ * Init, Destroy and Check functions for different timeouts.
+ *
+ * NB: all Check* functions are run inside a signal handler, so be very wary
+ * about what is done in them or in called routines.
+ *****************************************************************************/
+
+ /*
+ * Common Init and Destroy functions
+ */
+
+ static void
+ InitTimeout(TimeoutName tn, TimestampTz start_time, TimestampTz fin_time)
+ {
+ base_timeouts[tn].indicator = false;
+ base_timeouts[tn].start_time = start_time;
+ base_timeouts[tn].fin_time = fin_time;
+ }
+
+ static void
+ DestroyTimeout(TimeoutName tn, bool keep_indicator)
+ {
+ if (!keep_indicator)
+ base_timeouts[tn].indicator = false;
+ base_timeouts[tn].start_time = 0;
+ base_timeouts[tn].fin_time = 0;
+ }
+
+ /*
+ * Functions to manage deadlock
+ */
+
+ static void
+ InitDeadLock(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(DEADLOCK_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyDeadLock(bool keep_indicator)
+ {
+ DestroyTimeout(DEADLOCK_TIMEOUT, keep_indicator);
+ }
+
+ /*
+ * CheckDeadLock
+ *
+ * Look to see if there's a deadlock; if not, just return.
+ * (But signal ProcSleep to log a message, if log_lock_waits is true.)
+ * If we have a real deadlock, remove ourselves from the lock's wait queue
+ * and signal an error to ProcSleep.
+ */
+ static bool
+ CheckDeadLock(void)
+ {
+ TimestampTz now;
+ int i;
+
+ now = GetCurrentTimestamp();
+
+ /* If our time has not come yet, do nothing. */
+ if (now < base_timeouts[DEADLOCK_TIMEOUT].fin_time)
+ return false;
+
+ /*
+ * Acquire exclusive lock on the entire shared lock data structures. Must
+ * grab LWLocks in partition-number order to avoid LWLock deadlock.
+ *
+ * Note that the deadlock check interrupt had better not be enabled
+ * anywhere that this process itself holds lock partition locks, else this
+ * will wait forever. Also note that LWLockAcquire creates a critical
+ * section, so that this routine cannot be interrupted by cancel/die
+ * interrupts.
+ */
+ for (i = 0; i < NUM_LOCK_PARTITIONS; i++)
+ LWLockAcquire(FirstLockMgrLock + i, LW_EXCLUSIVE);
+
+ /*
+ * Check to see if we've been awoken by anyone in the interim.
+ *
+ * If we have, we can return and resume our transaction -- happy day.
+ * Before we are awoken the process releasing the lock grants it to us so
+ * we know that we don't have to wait anymore.
+ *
+ * We check by looking to see if we've been unlinked from the wait queue.
+ * This is quicker than checking our semaphore's state, since no kernel
+ * call is needed, and it is safe because we hold the lock partition lock.
+ */
+ if (MyProc->links.prev == NULL ||
+ MyProc->links.next == NULL)
+ goto check_done;
+
+ #ifdef LOCK_DEBUG
+ if (Debug_deadlocks)
+ DumpAllLocks();
+ #endif
+
+ /* Run the deadlock check, and set deadlock_state for use by ProcSleep */
+ deadlock_state = DeadLockCheck(MyProc);
+
+ if (deadlock_state == DS_HARD_DEADLOCK)
+ {
+ /*
+ * Oops. We have a deadlock.
+ *
+ * Get this process out of wait state. (Note: we could do this more
+ * efficiently by relying on lockAwaited, but use this coding to
+ * preserve the flexibility to kill some other transaction than the
+ * one detecting the deadlock.)
+ *
+ * RemoveFromWaitQueue sets MyProc->waitStatus to STATUS_ERROR, so
+ * ProcSleep will report an error after we return from the signal
+ * handler.
+ */
+ Assert(MyProc->waitLock != NULL);
+ RemoveFromWaitQueue(MyProc, LockTagHashCode(&(MyProc->waitLock->tag)));
+
+ /*
+ * Unlock my semaphore so that the interrupted ProcSleep() call can
+ * finish.
+ */
+ PGSemaphoreUnlock(&MyProc->sem);
+
+ /*
+ * We're done here. Transaction abort caused by the error that
+ * ProcSleep will raise will cause any other locks we hold to be
+ * released, thus allowing other processes to wake up; we don't need
+ * to do that here. NOTE: an exception is that releasing locks we
+ * hold doesn't consider the possibility of waiters that were blocked
+ * behind us on the lock we just failed to get, and might now be
+ * wakable because we're not in front of them anymore. However,
+ * RemoveFromWaitQueue took care of waking up any such processes.
+ */
+ }
+ else if (log_lock_waits || deadlock_state == DS_BLOCKED_BY_AUTOVACUUM)
+ {
+ /*
+ * Unlock my semaphore so that the interrupted ProcSleep() call can
+ * print the log message (we daren't do it here because we are inside
+ * a signal handler). It will then sleep again until someone releases
+ * the lock.
+ *
+ * If blocked by autovacuum, this wakeup will enable ProcSleep to send
+ * the canceling signal to the autovacuum worker.
+ */
+ PGSemaphoreUnlock(&MyProc->sem);
+ }
+
+ /*
+ * And release locks. We do this in reverse order for two reasons: (1)
+ * Anyone else who needs more than one of the locks will be trying to lock
+ * them in increasing order; we don't want to release the other process
+ * until it can get all the locks it needs. (2) This avoids O(N^2)
+ * behavior inside LWLockRelease.
+ */
+ check_done:
+ for (i = NUM_LOCK_PARTITIONS; --i >= 0;)
+ LWLockRelease(FirstLockMgrLock + i);
+
+ base_timeouts[DEADLOCK_TIMEOUT].indicator = true;
+
+ return true;
+ }
+
+ /*
+ * Functions to manage statement timeout
+ */
+
+ static void
+ InitStatementTimeout(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(STATEMENT_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyStatementTimeout(bool keep_indicator)
+ {
+ DestroyTimeout(STATEMENT_TIMEOUT, keep_indicator);
+ }
+
+ static bool
+ CheckStatementTimeout(void)
+ {
+ TimestampTz now;
+
+ now = GetCurrentTimestamp();
+
+ if (now < base_timeouts[STATEMENT_TIMEOUT].fin_time)
+ return false;
+
+ base_timeouts[STATEMENT_TIMEOUT].indicator = true;
+ #ifdef HAVE_SETSID
+ /* try to signal whole process group */
+ kill(-MyProcPid, SIGINT);
+ #endif
+ kill(MyProcPid, SIGINT);
+
+ return true;
+ }
+
+ /*
+ * Functions to manage standby deadlock functions
+ */
+
+ static void
+ InitStandbyDeadLock(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(STANDBY_DEADLOCK_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyStandbyDeadLock(bool keep_indicator)
+ {
+ DestroyTimeout(STANDBY_DEADLOCK_TIMEOUT, keep_indicator);
+ }
+
+ static bool
+ CheckStandbyDeadLock(void)
+ {
+ TimestampTz now;
+
+ now = GetCurrentTimestamp();
+
+ if (now < base_timeouts[STANDBY_DEADLOCK_TIMEOUT].fin_time)
+ return false;
+
+ base_timeouts[STANDBY_DEADLOCK_TIMEOUT].indicator = true;
+ SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
+ return true;
+ }
+
+ /*
+ * Functions to manage standby timeout
+ */
+
+ static void
+ InitStandbyTimeout(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(STANDBY_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyStandbyTimeout(bool keep_indicator)
+ {
+ DestroyTimeout(STANDBY_TIMEOUT, keep_indicator);
+ }
+
+ static bool
+ CheckStandbyTimeout(void)
+ {
+ TimestampTz now;
+
+ now = GetCurrentTimestamp();
+
+ if (now < base_timeouts[STANDBY_TIMEOUT].fin_time)
+ return false;
+
+ base_timeouts[STANDBY_TIMEOUT].indicator = true;
+ SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
+ return true;
+ }
+
+ /*****************************************************************************
+ * Public API
+ *****************************************************************************/
+
+
+ /*
+ * Enable the SIGALRM interrupt to fire after the specified delay
+ *
+ * Delay is given in milliseconds. Caller should be sure a SIGALRM
+ * signal handler is installed before this is called.
+ *
+ * This code properly handles nesting of different timeout alarms.
+ *
+ * Returns TRUE if okay, FALSE on failure to set the timer.
+ */
+ bool
+ enable_timeout(TimeoutName tn, int delayms)
+ {
+ TimestampTz start_time;
+ TimestampTz fin_time;
+ int i;
+
+ Assert(!is_timeout_active(tn));
+
+ if (delayms <= 0)
+ return true;
+
+ start_time = base_timeouts[tn].timeout_start();
+ fin_time = TimestampTzPlusMilliseconds(start_time, delayms);
+
+ /* Find out the index where to insert the new timeout. */
+ for (i = 0; i < n_timeouts; i++)
+ {
+ /*
+ * The new timeout triggers earlier than
+ * a previously added one: insert here.
+ */
+ if (fin_time < timeouts[i]->fin_time)
+ break;
+ /*
+ * The new timeout triggers at the same time
+ * as the previously added one but has greater priority.
+ */
+ if (fin_time == timeouts[i]->fin_time && tn < timeouts[i]->index)
+ break;
+ }
+
+ /*
+ * Initialize the timeout parameters
+ */
+ base_timeouts[tn].timeout_init(start_time, fin_time);
+
+ insert_timeout(tn, i);
+
+ if (i > 0)
+ return true;
+
+ /* If we reach here, okay to set the timer interrupt */
+ if (!schedule_timeout(start_time))
+ return false;
+ return true;
+ }
+
+ /*
+ * Cancel the SIGALRM timer for the specific timeout.
+ * If a timeout is canceled, any other active timeout remains in force.
+ *
+ * Returns TRUE if okay, FALSE on failure to set the timer for
+ * the next timeout source.
+ */
+ bool
+ disable_timeout(TimeoutName tn, bool keep_indicator)
+ {
+ int i;
+
+ /*
+ * Always disable the interrupt if it is active; this avoids being
+ * interrupted by the signal handler and thereby possibly getting
+ * confused.
+ *
+ * We will re-enable the interrupt if necessary in ->check_timeout().
+ */
+ if (n_timeouts > 0)
+ {
+ struct itimerval timeval;
+
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ if (setitimer(ITIMER_REAL, &timeval, NULL))
+ {
+ n_timeouts = 0;
+ for (i = 0; i < TIMEOUT_MAX; i++)
+ base_timeouts[i].timeout_destroy(false);
+
+ return false;
+ }
+ }
+
+ /* Find the timeout and remove from the list. */
+ i = find_active_timeout(tn);
+ remove_timeout_index(i);
+
+ /* Do cleanup. */
+ base_timeouts[tn].timeout_destroy(keep_indicator);
+
+ /*
+ * If the first timeout was removed from the list and there is
+ * at least one active left, reschedule it.
+ */
+ if (i == 0 && n_timeouts > 0)
+ if (!schedule_timeout(GetCurrentTimestamp()))
+ return false;
+
+ return true;
+ }
+
+ /*
+ * Disable SIGALRM and remove all timeouts from the list and
+ * reset the timeout indicators.
+ */
+ bool
+ disable_all_timeouts(bool keep_indicators)
+ {
+ struct itimerval timeval;
+ int i;
+ bool ret;
+
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ ret = (setitimer(ITIMER_REAL, &timeval, NULL) == 0);
+
+ n_timeouts = 0;
+ for (i = 0; i < TIMEOUT_MAX; i++)
+ base_timeouts[i].timeout_destroy(keep_indicators);
+
+ return ret;
+ }
+
+ /*
+ * Return the timeout indicator
+ */
+ bool
+ get_timeout_indicator(TimeoutName tn)
+ {
+ return base_timeouts[tn].indicator;
+ }
+
+ /*
+ * Return the start of the timer for this timeout
+ */
+ TimestampTz
+ get_timeout_start(TimeoutName tn)
+ {
+ return base_timeouts[tn].start_time;
+ }
+
+ /*
+ * Signal handler for SIGALRM
+ *
+ * Process the check for the currently active timeout source and
+ * reschedule the next as needed. To avoid various edge cases,
+ * we must be careful to do nothing when there is nothing to be done.
+ */
+ void
+ handle_sig_alarm(SIGNAL_ARGS)
+ {
+ int save_errno = errno;
+
+ /*
+ * SIGALRM is cause for waking anything waiting on the process latch.
+ * Recovery (the startup process) doesn't have MyProc set so
+ * it can also use this signal handler.
+ */
+ if (MyProc)
+ SetLatch(&MyProc->procLatch);
+
+ if (n_timeouts > 0)
+ {
+ bool ret;
+ bool reschedule = true;
+
+ ret = timeouts[0]->timeout_check();
+
+ /* Shift timeouts if the timeout was triggered */
+ if (ret)
+ {
+ reschedule = timeouts[0]->resched_next;
+ /*
+ * Short circuit disable_timeout(..., true) for the
+ * timeout source that just triggered.
+ */
+ remove_timeout_index(0);
+ }
+
+ if (reschedule)
+ schedule_timeout(GetCurrentTimestamp());
+ else
+ disable_all_timeouts(true);
+ }
+
+ errno = save_errno;
+ }
diff -dcrpN postgresql.1/src/backend/tcop/postgres.c postgresql.2/src/backend/tcop/postgres.c
*** postgresql.1/src/backend/tcop/postgres.c 2012-04-10 08:13:00.087488597 +0200
--- postgresql.2/src/backend/tcop/postgres.c 2012-04-10 08:23:29.710358580 +0200
***************
*** 64,69 ****
--- 64,70 ----
#include "storage/proc.h"
#include "storage/procsignal.h"
#include "storage/sinval.h"
+ #include "storage/timeout.h"
#include "tcop/fastpath.h"
#include "tcop/pquery.h"
#include "tcop/tcopprot.h"
*************** start_xact_command(void)
*** 2370,2378 ****
/* Set statement timeout running, if any */
/* NB: this mustn't be enabled until we are within an xact */
if (StatementTimeout > 0)
! enable_sig_alarm(StatementTimeout, true);
else
! cancel_from_timeout = false;
xact_started = true;
}
--- 2371,2379 ----
/* Set statement timeout running, if any */
/* NB: this mustn't be enabled until we are within an xact */
if (StatementTimeout > 0)
! enable_timeout(STATEMENT_TIMEOUT, StatementTimeout);
else
! disable_timeout(STATEMENT_TIMEOUT, false);
xact_started = true;
}
*************** finish_xact_command(void)
*** 2384,2390 ****
if (xact_started)
{
/* Cancel any active statement timeout before committing */
! disable_sig_alarm(true);
/* Now commit the command */
ereport(DEBUG3,
--- 2385,2391 ----
if (xact_started)
{
/* Cancel any active statement timeout before committing */
! disable_all_timeouts(false);
/* Now commit the command */
ereport(DEBUG3,
*************** ProcessInterrupts(void)
*** 2865,2871 ****
(errcode(ERRCODE_QUERY_CANCELED),
errmsg("canceling authentication due to timeout")));
}
! if (cancel_from_timeout)
{
ImmediateInterruptOK = false; /* not idle anymore */
DisableNotifyInterrupt();
--- 2866,2872 ----
(errcode(ERRCODE_QUERY_CANCELED),
errmsg("canceling authentication due to timeout")));
}
! if (get_timeout_indicator(STATEMENT_TIMEOUT))
{
ImmediateInterruptOK = false; /* not idle anymore */
DisableNotifyInterrupt();
*************** PostgresMain(int argc, char *argv[], con
*** 3770,3779 ****
/*
* Forget any pending QueryCancel request, since we're returning to
! * the idle loop anyway, and cancel the statement timer if running.
*/
QueryCancelPending = false;
! disable_sig_alarm(true);
QueryCancelPending = false; /* again in case timeout occurred */
/*
--- 3771,3780 ----
/*
* Forget any pending QueryCancel request, since we're returning to
! * the idle loop anyway, and cancel the timer if running.
*/
QueryCancelPending = false;
! disable_all_timeouts(false);
QueryCancelPending = false; /* again in case timeout occurred */
/*
diff -dcrpN 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-04-09 07:18:15.429829115 +0200
--- postgresql.2/src/backend/utils/init/postinit.c 2012-04-10 08:23:29.712358664 +0200
***************
*** 41,51 ****
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lmgr.h"
- #include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "storage/smgr.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
#include "utils/fmgroids.h"
--- 41,51 ----
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lmgr.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "storage/smgr.h"
+ #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
#include "utils/fmgroids.h"
*************** PerformAuthentication(Port *port)
*** 204,210 ****
* during authentication. Since we're inside a transaction and might do
* database access, we have to use the statement_timeout infrastructure.
*/
! if (!enable_sig_alarm(AuthenticationTimeout * 1000, true))
elog(FATAL, "could not set timer for authorization timeout");
/*
--- 204,210 ----
* during authentication. Since we're inside a transaction and might do
* database access, we have to use the statement_timeout infrastructure.
*/
! if (!enable_timeout(STATEMENT_TIMEOUT, AuthenticationTimeout * 1000))
elog(FATAL, "could not set timer for authorization timeout");
/*
*************** PerformAuthentication(Port *port)
*** 215,221 ****
/*
* Done with authentication. Disable the timeout, and log if needed.
*/
! if (!disable_sig_alarm(true))
elog(FATAL, "could not disable timer for authorization timeout");
if (Log_connections)
--- 215,221 ----
/*
* Done with authentication. Disable the timeout, and log if needed.
*/
! if (!disable_timeout(STATEMENT_TIMEOUT, false))
elog(FATAL, "could not disable timer for authorization timeout");
if (Log_connections)
diff -dcrpN 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-04-09 07:18:15.569834855 +0200
--- postgresql.2/src/backend/utils/misc/guc.c 2012-04-10 08:23:29.733359542 +0200
***************
*** 63,68 ****
--- 63,69 ----
#include "storage/standby.h"
#include "storage/fd.h"
#include "storage/predicate.h"
+ #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "tsearch/ts_cache.h"
#include "utils/builtins.h"
diff -dcrpN postgresql.1/src/include/storage/proc.h postgresql.2/src/include/storage/proc.h
*** postgresql.1/src/include/storage/proc.h 2012-04-10 08:13:00.090488723 +0200
--- postgresql.2/src/include/storage/proc.h 2012-04-10 08:23:29.738359749 +0200
*************** extern PGPROC *PreparedXactProcs;
*** 215,227 ****
/* configurable options */
- extern int DeadlockTimeout;
- extern int StatementTimeout;
extern bool log_lock_waits;
- extern volatile bool cancel_from_timeout;
-
-
/*
* Function Prototypes
*/
--- 215,222 ----
*************** extern void LockErrorCleanup(void);
*** 249,261 ****
extern void ProcWaitForSignal(void);
extern void ProcSendSignal(int pid);
- extern bool enable_sig_alarm(int delayms, bool is_statement_timeout);
- extern bool disable_sig_alarm(bool is_statement_timeout);
- extern void handle_sig_alarm(SIGNAL_ARGS);
-
- extern bool enable_standby_sig_alarm(TimestampTz now,
- TimestampTz fin_time, bool deadlock_only);
- extern bool disable_standby_sig_alarm(void);
- extern void handle_standby_sig_alarm(SIGNAL_ARGS);
-
#endif /* PROC_H */
--- 244,247 ----
diff -dcrpN postgresql.1/src/include/storage/timeout.h postgresql.2/src/include/storage/timeout.h
*** postgresql.1/src/include/storage/timeout.h 1970-01-01 01:00:00.000000000 +0100
--- postgresql.2/src/include/storage/timeout.h 2012-04-10 08:23:29.740359833 +0200
***************
*** 0 ****
--- 1,38 ----
+ /*-------------------------------------------------------------------------
+ *
+ * timeout.h
+ * SIGALRM timeout API
+ *
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/storage/timeout.h
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef _TIMEOUT_H_
+ #define _TIMEOUT_H_
+
+ #include "datatype/timestamp.h"
+
+ /* configurable options */
+ extern int DeadlockTimeout;
+ extern int StatementTimeout;
+
+ typedef enum TimeoutName {
+ DEADLOCK_TIMEOUT,
+ STATEMENT_TIMEOUT,
+ STANDBY_DEADLOCK_TIMEOUT,
+ STANDBY_TIMEOUT,
+ TIMEOUT_MAX
+ } TimeoutName;
+
+ extern bool enable_timeout(TimeoutName tn, int delayms);
+ extern bool disable_timeout(TimeoutName tn, bool keep_indicator);
+ extern bool disable_all_timeouts(bool keep_indicators);
+ extern bool get_timeout_indicator(TimeoutName tn);
+ extern TimestampTz get_timeout_start(TimeoutName tn);
+ extern void handle_sig_alarm(SIGNAL_ARGS);
+
+ #endif /* _TIMEOUT_H_ */
2-lock_timeout-v2.patchtext/x-patch; name=2-lock_timeout-v2.patchDownload
diff -dcrpN postgresql.2/doc/src/sgml/config.sgml postgresql.3/doc/src/sgml/config.sgml
*** postgresql.2/doc/src/sgml/config.sgml 2012-04-10 08:11:19.393283266 +0200
--- postgresql.3/doc/src/sgml/config.sgml 2012-04-10 08:27:30.364381807 +0200
*************** COPY postgres_log FROM '/full/path/to/lo
*** 4948,4954 ****
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.
</para>
<para>
--- 4948,4957 ----
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. 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>
*************** COPY postgres_log FROM '/full/path/to/lo
*** 4959,4964 ****
--- 4962,4995 ----
</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 has to wait more
+ than the specified number of milliseconds, starting from the time the
+ command arrives at the server from the client. If the statement involves
+ more than one such lock, the timeout applies to every one of them.
+ 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 to acquire.
+ 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 off the limitation.
+ </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 -dcrpN postgresql.2/doc/src/sgml/ref/lock.sgml postgresql.3/doc/src/sgml/ref/lock.sgml
*** postgresql.2/doc/src/sgml/ref/lock.sgml 2012-04-09 07:18:15.046813412 +0200
--- postgresql.3/doc/src/sgml/ref/lock.sgml 2012-04-10 08:27:30.375382269 +0200
*************** LOCK [ TABLE ] [ ONLY ] <replaceable cla
*** 39,46 ****
<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.)
</para>
--- 39,49 ----
<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. 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 -dcrpN postgresql.2/doc/src/sgml/ref/select.sgml postgresql.3/doc/src/sgml/ref/select.sgml
*** postgresql.2/doc/src/sgml/ref/select.sgml 2012-04-09 07:18:15.057813863 +0200
--- postgresql.3/doc/src/sgml/ref/select.sgml 2012-04-10 08:27:30.380382479 +0200
*************** FOR SHARE [ OF <replaceable class="param
*** 1199,1204 ****
--- 1199,1211 ----
</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 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 -dcrpN postgresql.2/src/backend/port/posix_sema.c postgresql.3/src/backend/port/posix_sema.c
*** postgresql.2/src/backend/port/posix_sema.c 2012-04-09 07:18:15.328824974 +0200
--- postgresql.3/src/backend/port/posix_sema.c 2012-04-10 08:27:30.401383353 +0200
***************
*** 24,29 ****
--- 24,30 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/timeout.h"
#ifdef USE_NAMED_POSIX_SEMAPHORES
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 313,315 ****
--- 314,341 ----
return true;
}
+
+ /*
+ * PGSemaphoreTimedLock
+ *
+ * Lock a semaphore (decrement count), blocking if count would be < 0
+ * Return if lock_timeout expired
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ int errStatus;
+
+ do
+ {
+ ImmediateInterruptOK = interruptOK;
+ CHECK_FOR_INTERRUPTS();
+ errStatus = sem_wait(PG_SEM_REF(sema));
+ ImmediateInterruptOK = false;
+ } while (errStatus < 0 && errno == EINTR && !get_timeout_indicator(LOCK_TIMEOUT));
+
+ if (get_timeout_indicator(LOCK_TIMEOUT))
+ return;
+ if (errStatus < 0)
+ elog(FATAL, "sem_wait failed: %m");
+ }
diff -dcrpN postgresql.2/src/backend/port/sysv_sema.c postgresql.3/src/backend/port/sysv_sema.c
*** postgresql.2/src/backend/port/sysv_sema.c 2012-04-09 07:18:15.328824974 +0200
--- postgresql.3/src/backend/port/sysv_sema.c 2012-04-10 08:27:30.402383395 +0200
***************
*** 30,35 ****
--- 30,36 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/timeout.h"
#ifndef HAVE_UNION_SEMUN
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 495,497 ****
--- 496,528 ----
return true;
}
+
+ /*
+ * PGSemaphoreTimedLock
+ *
+ * Lock a semaphore (decrement count), blocking if count would be < 0
+ * Return if lock_timeout expired
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ int errStatus;
+ 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;
+ } while (errStatus < 0 && errno == EINTR && !get_timeout_indicator(LOCK_TIMEOUT));
+
+ if (get_timeout_indicator(LOCK_TIMEOUT))
+ return;
+ if (errStatus < 0)
+ elog(FATAL, "semop(id=%d) failed: %m", sema->semId);
+ }
diff -dcrpN postgresql.2/src/backend/port/win32_sema.c postgresql.3/src/backend/port/win32_sema.c
*** postgresql.2/src/backend/port/win32_sema.c 2012-04-09 07:18:15.331825097 +0200
--- postgresql.3/src/backend/port/win32_sema.c 2012-04-10 08:27:30.404383479 +0200
***************
*** 16,21 ****
--- 16,22 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/timeout.h"
static HANDLE *mySemSet; /* IDs of sema sets acquired so far */
static int numSems; /* number of sema sets acquired so far */
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 205,207 ****
--- 206,263 ----
/* 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.
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ DWORD ret;
+ HANDLE wh[2];
+
+ wh[0] = *sema;
+ wh[1] = pgwin32_signal_event;
+
+ /*
+ * 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();
+
+ errno = 0;
+ ret = WaitForMultipleObjectsEx(2, wh, FALSE, INFINITE, TRUE);
+
+ if (ret == WAIT_OBJECT_0)
+ {
+ /* We got it! */
+ return;
+ }
+ else if (ret == WAIT_OBJECT_0 + 1)
+ {
+ /* Signal event is set - we have a signal to deliver */
+ pgwin32_dispatch_queued_signals();
+ errno = EINTR;
+ }
+ else
+ /* Otherwise we are in trouble */
+ errno = EIDRM;
+
+ ImmediateInterruptOK = false;
+ } while (errno == EINTR && !get_timeout_indicator(LOCK_TIMEOUT));
+
+ if (get_timeout_indicator(LOCK_TIMEOUT))
+ return;
+ if (errno != 0)
+ ereport(FATAL,
+ (errmsg("could not lock semaphore: error code %d", (int) GetLastError())));
+ }
diff -dcrpN postgresql.2/src/backend/storage/lmgr/lmgr.c postgresql.3/src/backend/storage/lmgr/lmgr.c
*** postgresql.2/src/backend/storage/lmgr/lmgr.c 2012-04-09 07:18:15.368826614 +0200
--- postgresql.3/src/backend/storage/lmgr/lmgr.c 2012-04-10 08:27:30.412383815 +0200
***************
*** 19,26 ****
--- 19,29 ----
#include "access/transam.h"
#include "access/xact.h"
#include "catalog/catalog.h"
+ #include "catalog/pg_database.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
+ #include "utils/lsyscache.h"
+ #include "storage/proc.h"
#include "storage/procarray.h"
#include "utils/inval.h"
*************** LockRelationOid(Oid relid, LOCKMODE lock
*** 78,83 ****
--- 81,101 ----
res = LockAcquire(&tag, lockmode, false, false);
+ if (res == LOCKACQUIRE_NOT_AVAIL)
+ {
+ char *relname = get_rel_name(relid);
+ if (relname)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ relname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation with OID %u",
+ relid)));
+ }
+
/*
* Now that we have the lock, check for invalidation messages, so that we
* will update or flush any stale relcache entry before we try to use it.
*************** LockRelation(Relation relation, LOCKMODE
*** 174,179 ****
--- 192,203 ----
res = LockAcquire(&tag, lockmode, false, false);
+ if (res == LOCKACQUIRE_NOT_AVAIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ RelationGetRelationName(relation))));
+
/*
* Now that we have the lock, check for invalidation messages; see notes
* in LockRelationOid.
*************** LockRelationIdForSession(LockRelId *reli
*** 251,257 ****
SET_LOCKTAG_RELATION(tag, relid->dbId, relid->relId);
! (void) LockAcquire(&tag, lockmode, true, false);
}
/*
--- 275,294 ----
SET_LOCKTAG_RELATION(tag, relid->dbId, relid->relId);
! if (LockAcquire(&tag, lockmode, true, false) == LOCKACQUIRE_NOT_AVAIL)
! {
! char *relname = get_rel_name(relid->relId);
! if (relname)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on relation \"%s\"",
! relname)));
! else
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on relation with OID %u",
! relid->relId)));
! }
}
/*
*************** LockRelationForExtension(Relation relati
*** 286,292 ****
relation->rd_lockInfo.lockRelId.dbId,
relation->rd_lockInfo.lockRelId.relId);
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 323,333 ----
relation->rd_lockInfo.lockRelId.dbId,
relation->rd_lockInfo.lockRelId.relId);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on index \"%s\"",
! RelationGetRelationName(relation))));
}
/*
*************** LockPage(Relation relation, BlockNumber
*** 320,326 ****
relation->rd_lockInfo.lockRelId.relId,
blkno);
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 361,371 ----
relation->rd_lockInfo.lockRelId.relId,
blkno);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on page %u of relation \"%s\"",
! blkno, RelationGetRelationName(relation))));
}
/*
*************** LockTuple(Relation relation, ItemPointer
*** 376,382 ****
ItemPointerGetBlockNumber(tid),
ItemPointerGetOffsetNumber(tid));
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 421,431 ----
ItemPointerGetBlockNumber(tid),
ItemPointerGetOffsetNumber(tid));
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on row in relation \"%s\"",
! RelationGetRelationName(relation))));
}
/*
*************** XactLockTableInsert(TransactionId xid)
*** 430,436 ****
SET_LOCKTAG_TRANSACTION(tag, xid);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
}
/*
--- 479,488 ----
SET_LOCKTAG_TRANSACTION(tag, xid);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on transaction with ID %u", xid)));
}
/*
*************** XactLockTableWait(TransactionId xid)
*** 474,480 ****
SET_LOCKTAG_TRANSACTION(tag, xid);
! (void) LockAcquire(&tag, ShareLock, false, false);
LockRelease(&tag, ShareLock, false);
--- 526,535 ----
SET_LOCKTAG_TRANSACTION(tag, xid);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on transaction with ID %u", xid)));
LockRelease(&tag, ShareLock, false);
*************** LockDatabaseObject(Oid classid, Oid obji
*** 535,541 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, false, false);
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
--- 590,600 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
*************** LockSharedObject(Oid classid, Oid objid,
*** 576,582 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, false, false);
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
--- 635,645 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
*************** LockSharedObjectForSession(Oid classid,
*** 618,624 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, true, false);
}
/*
--- 681,702 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, true, false) == LOCKACQUIRE_NOT_AVAIL)
! switch(classid)
! {
! case DatabaseRelationId:
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on database with ID %u",
! objid)));
! break;
! default:
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
! break;
! }
}
/*
diff -dcrpN postgresql.2/src/backend/storage/lmgr/lock.c postgresql.3/src/backend/storage/lmgr/lock.c
*** postgresql.2/src/backend/storage/lmgr/lock.c 2012-04-10 08:13:00.066487715 +0200
--- postgresql.3/src/backend/storage/lmgr/lock.c 2012-04-10 08:38:22.484909020 +0200
*************** static PROCLOCK *SetupLockInTable(LockMe
*** 341,347 ****
static void GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner);
static void BeginStrongLockAcquire(LOCALLOCK *locallock, uint32 fasthashcode);
static void FinishStrongLockAcquire(void);
! static void WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner);
static void ReleaseLockForOwner(LOCALLOCK *locallock, ResourceOwner owner);
static bool UnGrantLock(LOCK *lock, LOCKMODE lockmode,
PROCLOCK *proclock, LockMethod lockMethodTable);
--- 341,347 ----
static void GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner);
static void BeginStrongLockAcquire(LOCALLOCK *locallock, uint32 fasthashcode);
static void FinishStrongLockAcquire(void);
! static int WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner);
static void ReleaseLockForOwner(LOCALLOCK *locallock, ResourceOwner owner);
static bool UnGrantLock(LOCK *lock, LOCKMODE lockmode,
PROCLOCK *proclock, LockMethod lockMethodTable);
*************** ProcLockHashCode(const PROCLOCKTAG *proc
*** 547,553 ****
* dontWait: if true, don't wait to acquire lock
*
* Returns one of:
! * LOCKACQUIRE_NOT_AVAIL lock not available, and dontWait=true
* LOCKACQUIRE_OK lock successfully acquired
* LOCKACQUIRE_ALREADY_HELD incremented count for lock already held
*
--- 547,553 ----
* dontWait: if true, don't wait to acquire lock
*
* Returns one of:
! * LOCKACQUIRE_NOT_AVAIL lock not available, either dontWait=true or timeout
* LOCKACQUIRE_OK lock successfully acquired
* LOCKACQUIRE_ALREADY_HELD incremented count for lock already held
*
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 857,863 ****
locktag->locktag_type,
lockmode);
! WaitOnLock(locallock, owner);
TRACE_POSTGRESQL_LOCK_WAIT_DONE(locktag->locktag_field1,
locktag->locktag_field2,
--- 857,863 ----
locktag->locktag_type,
lockmode);
! status = WaitOnLock(locallock, owner);
TRACE_POSTGRESQL_LOCK_WAIT_DONE(locktag->locktag_field1,
locktag->locktag_field2,
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 872,898 ****
* done when the lock was granted to us --- see notes in WaitOnLock.
*/
! /*
! * Check the proclock entry status, in case something in the ipc
! * communication doesn't work correctly.
! */
! if (!(proclock->holdMask & LOCKBIT_ON(lockmode)))
{
! PROCLOCK_PRINT("LockAcquire: INCONSISTENT", proclock);
! LOCK_PRINT("LockAcquire: INCONSISTENT", lock, lockmode);
! /* Should we retry ? */
! LWLockRelease(partitionLock);
! elog(ERROR, "LockAcquire failed");
}
- PROCLOCK_PRINT("LockAcquire: granted", proclock);
- LOCK_PRINT("LockAcquire: granted", lock, lockmode);
}
! /*
! * Lock state is fully up-to-date now; if we error out after this, no
! * special error cleanup is required.
! */
! FinishStrongLockAcquire();
LWLockRelease(partitionLock);
--- 872,921 ----
* done when the lock was granted to us --- see notes in WaitOnLock.
*/
! switch (status)
{
! case STATUS_OK:
! /*
! * Check the proclock entry status, in case something in the ipc
! * communication doesn't work correctly.
! */
! if (!(proclock->holdMask & LOCKBIT_ON(lockmode)))
! {
! PROCLOCK_PRINT("LockAcquire: INCONSISTENT", proclock);
! LOCK_PRINT("LockAcquire: INCONSISTENT", lock, lockmode);
! /* Should we retry ? */
! LWLockRelease(partitionLock);
! elog(ERROR, "LockAcquire failed");
! }
! PROCLOCK_PRINT("LockAcquire: granted", proclock);
! LOCK_PRINT("LockAcquire: granted", lock, lockmode);
! break;
! case STATUS_WAITING:
! PROCLOCK_PRINT("LockAcquire: timed out", proclock);
! LOCK_PRINT("LockAcquire: timed out", lock, lockmode);
! break;
! default:
! elog(ERROR, "LockAcquire invalid status");
! break;
}
}
! if (status == STATUS_WAITING)
! {
! /*
! * lock_timeout was set and WaitOnLock() indicated
! * we timed out. Clean up manually.
! */
! AbortStrongLockAcquire();
! }
! else
! {
! /*
! * Lock state is fully up-to-date now; if we error out after this, no
! * special error cleanup is required.
! */
! FinishStrongLockAcquire();
! }
LWLockRelease(partitionLock);
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 911,917 ****
locktag->locktag_field2);
}
! return LOCKACQUIRE_OK;
}
/*
--- 934,940 ----
locktag->locktag_field2);
}
! return (status == STATUS_OK ? LOCKACQUIRE_OK : LOCKACQUIRE_NOT_AVAIL);
}
/*
*************** GrantAwaitedLock(void)
*** 1429,1442 ****
* Caller must have set MyProc->heldLocks to reflect locks already held
* on the lockable object by this process.
*
* The appropriate partition lock must be held at entry.
*/
! static void
WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner)
{
LOCKMETHODID lockmethodid = LOCALLOCK_LOCKMETHOD(*locallock);
LockMethod lockMethodTable = LockMethods[lockmethodid];
char *volatile new_status = NULL;
LOCK_PRINT("WaitOnLock: sleeping on lock",
locallock->lock, locallock->tag.mode);
--- 1452,1471 ----
* Caller must have set MyProc->heldLocks to reflect locks already held
* on the lockable object by this process.
*
+ * Result: returns value of ProcSleep()
+ * STATUS_OK if we acquired the lock
+ * STATUS_ERROR if not (deadlock)
+ * STATUS_WAITING if not (timeout)
+ *
* The appropriate partition lock must be held at entry.
*/
! static int
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);
*************** WaitOnLock(LOCALLOCK *locallock, Resourc
*** 1478,1485 ****
*/
PG_TRY();
{
! if (ProcSleep(locallock, lockMethodTable) != STATUS_OK)
{
/*
* We failed as a result of a deadlock, see CheckDeadLock(). Quit
* now.
--- 1507,1519 ----
*/
PG_TRY();
{
! wait_status = ProcSleep(locallock, lockMethodTable);
! switch (wait_status)
{
+ case STATUS_OK:
+ case STATUS_WAITING:
+ break;
+ default:
/*
* We failed as a result of a deadlock, see CheckDeadLock(). Quit
* now.
*************** WaitOnLock(LOCALLOCK *locallock, Resourc
*** 1524,1531 ****
pfree(new_status);
}
! LOCK_PRINT("WaitOnLock: wakeup on lock",
locallock->lock, locallock->tag.mode);
}
/*
--- 1558,1571 ----
pfree(new_status);
}
! 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);
+
+ return wait_status;
}
/*
*************** VirtualXactLock(VirtualTransactionId vxi
*** 3797,3803 ****
LWLockRelease(proc->backendLock);
/* Time to wait. */
! (void) LockAcquire(&tag, ShareLock, false, false);
LockRelease(&tag, ShareLock, false);
return true;
--- 3837,3847 ----
LWLockRelease(proc->backendLock);
/* Time to wait. */
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on virtual transaction with ID %u",
! vxid.localTransactionId)));
LockRelease(&tag, ShareLock, false);
return true;
diff -dcrpN postgresql.2/src/backend/storage/lmgr/proc.c postgresql.3/src/backend/storage/lmgr/proc.c
*** postgresql.2/src/backend/storage/lmgr/proc.c 2012-04-10 08:23:29.697358034 +0200
--- postgresql.3/src/backend/storage/lmgr/proc.c 2012-04-10 08:29:57.447507809 +0200
*************** LockErrorCleanup(void)
*** 637,644 ****
if (lockAwaited == NULL)
return;
! /* Turn off the deadlock timer, if it's still running (see ProcSleep) */
disable_timeout(DEADLOCK_TIMEOUT, false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
--- 637,648 ----
if (lockAwaited == NULL)
return;
! /*
! * Turn off the deadlock and lock timeout timers,
! * if they are still running (see ProcSleep)
! */
disable_timeout(DEADLOCK_TIMEOUT, false);
+ disable_timeout(LOCK_TIMEOUT, false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
*************** ProcQueueInit(PROC_QUEUE *queue)
*** 872,878 ****
* 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).
*
* ASSUME: that no one will fiddle with the queue until after
* we release the partition lock.
--- 876,885 ----
* 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)
! * STATUS_WAITING if not (timeout)
*
* ASSUME: that no one will fiddle with the queue until after
* we release the partition lock.
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 894,899 ****
--- 901,907 ----
LOCKMASK myHeldLocks = MyProc->heldLocks;
bool early_deadlock = false;
bool allow_autovacuum_cancel = true;
+ bool timeout_detected = false;
int myWaitStatus;
PGPROC *proc;
int i;
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1035,1042 ****
elog(FATAL, "could not set timer for process wakeup");
/*
! * 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
* saved wakeup might be leftover from a previous operation (for example,
* we aborted ProcWaitForSignal just before someone did ProcSendSignal).
--- 1043,1056 ----
elog(FATAL, "could not set timer for process wakeup");
/*
! * Queue the timer for lock timeout, too.
! */
! if (!enable_timeout(LOCK_TIMEOUT, LockTimeout))
! elog(FATAL, "could not set timer for process wakeup");
!
! /*
! * 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).
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1053,1059 ****
*/
do
{
! PGSemaphoreLock(&MyProc->sem, true);
/*
* waitStatus could change from STATUS_WAITING to something else
--- 1067,1078 ----
*/
do
{
! PGSemaphoreTimedLock(&MyProc->sem, true);
!
! /* Check and keep the lock timeout indicator for later checks */
! timeout_detected = get_timeout_indicator(LOCK_TIMEOUT);
! if (timeout_detected)
! break;
/*
* waitStatus could change from STATUS_WAITING to something else
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1182,1193 ****
} while (myWaitStatus == STATUS_WAITING);
/*
! * Disable the timer, if it's still running
*/
if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for process wakeup");
/*
* Re-acquire the lock table's partition lock. We have to do this to hold
* off cancel/die interrupts before we can mess with lockAwaited (else we
* might have a missed or duplicated locallock update).
--- 1201,1218 ----
} while (myWaitStatus == STATUS_WAITING);
/*
! * Disable the deadlock timer, if it's still running
*/
if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for process wakeup");
/*
+ * Disable the lock timeout timer, if it's still running
+ */
+ if (!disable_timeout(LOCK_TIMEOUT, false))
+ elog(FATAL, "could not disable timer for process wakeup");
+
+ /*
* Re-acquire the lock table's partition lock. We have to do this to hold
* off cancel/die interrupts before we can mess with lockAwaited (else we
* might have a missed or duplicated locallock update).
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1195,1200 ****
--- 1220,1234 ----
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;
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1208,1215 ****
/*
* We don't have to do anything else, because the awaker did all the
* necessary update of the lock table and MyProc.
*/
! return MyProc->waitStatus;
}
--- 1242,1251 ----
/*
* 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 (timeout_detected ? STATUS_WAITING : MyProc->waitStatus);
}
diff -dcrpN postgresql.2/src/backend/storage/lmgr/timeout.c postgresql.3/src/backend/storage/lmgr/timeout.c
*** postgresql.2/src/backend/storage/lmgr/timeout.c 2012-04-10 08:23:29.702358244 +0200
--- postgresql.3/src/backend/storage/lmgr/timeout.c 2012-04-10 08:27:30.462385906 +0200
***************
*** 34,39 ****
--- 34,40 ----
/* GUC variables */
int DeadlockTimeout = 1000;
int StatementTimeout = 0;
+ int LockTimeout = 0;
/*
* This is used by ProcSleep() in proc.c
*************** static void InitDeadLock(TimestampTz sta
*** 50,55 ****
--- 51,60 ----
static void DestroyDeadLock(bool keep_indicator);
static bool CheckDeadLock(void);
+ static void InitLockTimeout(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyLockTimeout(bool keep_indicator);
+ static bool CheckLockTimeout(void);
+
static void InitStatementTimeout(TimestampTz start_time, TimestampTz fin_time);
static void DestroyStatementTimeout(bool keep_indicator);
static bool CheckStatementTimeout(void);
*************** static timeout_params base_timeouts[TIME
*** 98,103 ****
--- 103,115 ----
},
{
+ LOCK_TIMEOUT, false, false,
+ InitLockTimeout, DestroyLockTimeout,
+ CheckLockTimeout, GetCurrentTimestamp,
+ 0
+ },
+
+ {
STATEMENT_TIMEOUT, false, false,
InitStatementTimeout, DestroyStatementTimeout,
CheckStatementTimeout, GetCurrentStatementStartTimestamp,
*************** check_done:
*** 380,385 ****
--- 392,427 ----
return true;
}
+ /*
+ * Functions to manage lock timeout
+ */
+
+ static void
+ InitLockTimeout(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(LOCK_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyLockTimeout(bool keep_indicator)
+ {
+ DestroyTimeout(LOCK_TIMEOUT, keep_indicator);
+ }
+
+ static bool
+ CheckLockTimeout(void)
+ {
+ TimestampTz now;
+
+ now = GetCurrentTimestamp();
+
+ if (now < base_timeouts[LOCK_TIMEOUT].fin_time)
+ return false;
+
+ base_timeouts[LOCK_TIMEOUT].indicator = true;
+ return true;
+ }
+
/*
* Functions to manage statement timeout
*/
diff -dcrpN postgresql.2/src/backend/utils/adt/lockfuncs.c postgresql.3/src/backend/utils/adt/lockfuncs.c
*** postgresql.2/src/backend/utils/adt/lockfuncs.c 2012-04-09 07:18:15.396827762 +0200
--- postgresql.3/src/backend/utils/adt/lockfuncs.c 2012-04-10 08:27:30.472386322 +0200
*************** pg_advisory_lock_int8(PG_FUNCTION_ARGS)
*** 421,427 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ExclusiveLock, true, false);
PG_RETURN_VOID();
}
--- 421,431 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ExclusiveLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_int8(PG_FUNCTION_A
*** 438,444 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
PG_RETURN_VOID();
}
--- 442,452 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_shared_int8(PG_FUNCTION
*** 454,460 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ShareLock, true, false);
PG_RETURN_VOID();
}
--- 462,472 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ShareLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_shared_int8(PG_FUN
*** 471,477 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ShareLock, false, false);
PG_RETURN_VOID();
}
--- 483,493 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_int4(PG_FUNCTION_ARGS)
*** 604,610 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ExclusiveLock, true, false);
PG_RETURN_VOID();
}
--- 620,630 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ExclusiveLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_int4(PG_FUNCTION_A
*** 622,628 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
PG_RETURN_VOID();
}
--- 642,652 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_shared_int4(PG_FUNCTION
*** 639,645 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ShareLock, true, false);
PG_RETURN_VOID();
}
--- 663,673 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ShareLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_shared_int4(PG_FUN
*** 657,663 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ShareLock, false, false);
PG_RETURN_VOID();
}
--- 685,695 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
diff -dcrpN postgresql.2/src/backend/utils/misc/guc.c postgresql.3/src/backend/utils/misc/guc.c
*** postgresql.2/src/backend/utils/misc/guc.c 2012-04-10 08:23:29.733359542 +0200
--- postgresql.3/src/backend/utils/misc/guc.c 2012-04-10 08:27:30.504387656 +0200
*************** static struct config_int ConfigureNamesI
*** 1861,1866 ****
--- 1861,1877 ----
},
{
+ {"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 -dcrpN postgresql.2/src/backend/utils/misc/postgresql.conf.sample postgresql.3/src/backend/utils/misc/postgresql.conf.sample
*** postgresql.2/src/backend/utils/misc/postgresql.conf.sample 2012-04-09 07:18:15.570834896 +0200
--- postgresql.3/src/backend/utils/misc/postgresql.conf.sample 2012-04-10 08:27:30.506387739 +0200
***************
*** 527,532 ****
--- 527,535 ----
#------------------------------------------------------------------------------
#deadlock_timeout = 1s
+ #lock_timeout = 0 # timeout value for heavy-weight locks
+ # taken by statements. 0 disables timeout
+ # unit in milliseconds, default is 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 -dcrpN postgresql.2/src/include/storage/pg_sema.h postgresql.3/src/include/storage/pg_sema.h
*** postgresql.2/src/include/storage/pg_sema.h 2012-04-09 07:18:15.798844244 +0200
--- postgresql.3/src/include/storage/pg_sema.h 2012-04-10 08:27:30.535388947 +0200
*************** extern void PGSemaphoreUnlock(PGSemaphor
*** 80,83 ****
--- 80,89 ----
/* 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 void PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK);
+
#endif /* PG_SEMA_H */
diff -dcrpN postgresql.2/src/include/storage/timeout.h postgresql.3/src/include/storage/timeout.h
*** postgresql.2/src/include/storage/timeout.h 2012-04-10 08:23:29.740359833 +0200
--- postgresql.3/src/include/storage/timeout.h 2012-04-10 08:27:30.550389571 +0200
***************
*** 19,27 ****
--- 19,29 ----
/* configurable options */
extern int DeadlockTimeout;
extern int StatementTimeout;
+ extern int LockTimeout;
typedef enum TimeoutName {
DEADLOCK_TIMEOUT,
+ LOCK_TIMEOUT,
STATEMENT_TIMEOUT,
STANDBY_DEADLOCK_TIMEOUT,
STANDBY_TIMEOUT,
diff -dcrpN postgresql.2/src/test/regress/expected/prepared_xacts.out postgresql.3/src/test/regress/expected/prepared_xacts.out
*** postgresql.2/src/test/regress/expected/prepared_xacts.out 2012-04-09 07:18:16.076855642 +0200
--- postgresql.3/src/test/regress/expected/prepared_xacts.out 2012-04-10 08:27:30.558389902 +0200
*************** set statement_timeout to 2000;
*** 198,203 ****
--- 198,210 ----
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: could not obtain lock on relation "pxtest3"
+ LINE 1: SELECT * FROM pxtest3;
+ ^
+ reset lock_timeout;
-- Disconnect, we will continue testing in a different backend
\c -
-- There should still be two prepared transactions
*************** set statement_timeout to 2000;
*** 213,218 ****
--- 220,232 ----
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: could not obtain lock on relation "pxtest3"
+ LINE 1: SELECT * FROM pxtest3;
+ ^
+ reset lock_timeout;
-- Commit table creation
COMMIT PREPARED 'regress-one';
\d pxtest2
diff -dcrpN postgresql.2/src/test/regress/sql/prepared_xacts.sql postgresql.3/src/test/regress/sql/prepared_xacts.sql
*** postgresql.2/src/test/regress/sql/prepared_xacts.sql 2012-04-09 07:18:16.123857569 +0200
--- postgresql.3/src/test/regress/sql/prepared_xacts.sql 2012-04-10 08:27:30.570390404 +0200
*************** set statement_timeout to 2000;
*** 126,131 ****
--- 126,136 ----
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 -
*************** set statement_timeout to 2000;
*** 137,142 ****
--- 142,152 ----
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
2012-04-10 09:02 keltezéssel, Boszormenyi Zoltan írta:
2012-04-06 14:47 keltezéssel, Cousin Marc írta:
On 05/04/12 08:02, Boszormenyi Zoltan wrote:
2012-04-04 21:30 keltezéssel, Alvaro Herrera írta:
I think this patch is doing two things: first touching infrastructure
stuff and then adding lock_timeout on top of that. Would it work to
split the patch in two pieces?Sure. Attached is the split version.
Best regards,
Zoltán BöszörményiHi,
I've started looking at and testing both patches.
Technically speaking, I think the source looks much better than the
first version of lock timeout, and may help adding other timeouts in the
future. I haven't tested it in depth though, because I encountered the
following problem:While testing the patch, I found a way to crash PG. But what's weird is
that it crashes also with an unpatched git version.Here is the way to reproduce it (I have done it with a pgbench schema):
- Set a small statement_timeout (just to save time during the tests)
Session1:
=#BEGIN;
=#lock TABLE pgbench_accounts ;Session 2:
=#BEGIN;
=#lock TABLE pgbench_accounts ;
ERROR: canceling statement due to statement timeout
=# lock TABLE pgbench_accounts ;I'm using \set ON_ERROR_ROLLBACK INTERACTIVE by the way. It can also be
done with a rollback to savepoint of course.Session 2 crashes with this : TRAP : FailedAssertion(«
!(locallock->holdsStrongLockCount == 0) », fichier : « lock.c », ligne :
749).It can also be done without a statement_timeout, and a control-C on the
second lock table.I didn't touch anything but this. It occurs everytime, when asserts are
activated.I tried it on 9.1.3, and I couldn't make it crash with the same sequence
of events. So maybe it's something introduced since ? Or is the assert
still valid ?Cheers
Attached are the new patches. I rebased them to current GIT and
they are expected to be applied after Robert Haas' patch in the
"bug in fast-path locking" thread.Now it survives the above scenario.
Best regards,
Zoltán Böszörményi
New patch attached, rebased to today's GIT.
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/
Attachments:
1-timeout-framework-v3.patchtext/x-patch; name=1-timeout-framework-v3.patchDownload
diff -dcrpN postgresql.orig/src/backend/postmaster/autovacuum.c postgresql.1/src/backend/postmaster/autovacuum.c
*** postgresql.orig/src/backend/postmaster/autovacuum.c 2012-04-16 19:57:22.440915512 +0200
--- postgresql.1/src/backend/postmaster/autovacuum.c 2012-04-23 10:30:38.289731986 +0200
***************
*** 86,94 ****
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
- #include "storage/proc.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "tcop/tcopprot.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
--- 86,94 ----
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
+ #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
*************** AutoVacLauncherMain(int argc, char *argv
*** 484,490 ****
/* Forget any pending QueryCancel request */
QueryCancelPending = false;
! disable_sig_alarm(true);
QueryCancelPending = false; /* again in case timeout occurred */
/* Report the error to the server log */
--- 484,490 ----
/* Forget any pending QueryCancel request */
QueryCancelPending = false;
! disable_all_timeouts(false);
QueryCancelPending = false; /* again in case timeout occurred */
/* Report the error to the server log */
diff -dcrpN postgresql.orig/src/backend/postmaster/postmaster.c postgresql.1/src/backend/postmaster/postmaster.c
*** postgresql.orig/src/backend/postmaster/postmaster.c 2012-04-23 08:28:35.641057679 +0200
--- postgresql.1/src/backend/postmaster/postmaster.c 2012-04-23 10:30:38.292732003 +0200
***************
*** 112,118 ****
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
! #include "storage/proc.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/datetime.h"
--- 112,118 ----
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
! #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/datetime.h"
*************** BackendInitialize(Port *port)
*** 3471,3479 ****
* Ready to begin client interaction. We will give up and exit(1) after a
* time delay, so that a broken client can't hog a connection
* indefinitely. PreAuthDelay and any DNS interactions above don't count
! * against the time limit.
*/
! if (!enable_sig_alarm(AuthenticationTimeout * 1000, false))
elog(FATAL, "could not set timer for startup packet timeout");
/*
--- 3471,3479 ----
* Ready to begin client interaction. We will give up and exit(1) after a
* time delay, so that a broken client can't hog a connection
* indefinitely. PreAuthDelay and any DNS interactions above don't count
! * against the time limit. Use the deadlock timeout interface.
*/
! if (!enable_timeout(DEADLOCK_TIMEOUT, AuthenticationTimeout * 1000))
elog(FATAL, "could not set timer for startup packet timeout");
/*
*************** BackendInitialize(Port *port)
*** 3511,3517 ****
/*
* Disable the timeout, and prevent SIGTERM/SIGQUIT again.
*/
! if (!disable_sig_alarm(false))
elog(FATAL, "could not disable timer for startup packet timeout");
PG_SETMASK(&BlockSig);
}
--- 3511,3517 ----
/*
* Disable the timeout, and prevent SIGTERM/SIGQUIT again.
*/
! if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for startup packet timeout");
PG_SETMASK(&BlockSig);
}
diff -dcrpN postgresql.orig/src/backend/postmaster/startup.c postgresql.1/src/backend/postmaster/startup.c
*** postgresql.orig/src/backend/postmaster/startup.c 2012-04-16 19:57:22.442915536 +0200
--- postgresql.1/src/backend/postmaster/startup.c 2012-04-23 10:30:38.293732008 +0200
***************
*** 27,33 ****
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
! #include "storage/proc.h"
#include "utils/guc.h"
--- 27,33 ----
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
! #include "storage/timeout.h"
#include "utils/guc.h"
*************** StartupProcessMain(void)
*** 195,202 ****
pqsignal(SIGTERM, StartupProcShutdownHandler); /* request shutdown */
pqsignal(SIGQUIT, startupproc_quickdie); /* hard crash time */
if (EnableHotStandby)
! pqsignal(SIGALRM, handle_standby_sig_alarm); /* ignored unless
! * InHotStandby */
else
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
--- 195,202 ----
pqsignal(SIGTERM, StartupProcShutdownHandler); /* request shutdown */
pqsignal(SIGQUIT, startupproc_quickdie); /* hard crash time */
if (EnableHotStandby)
! pqsignal(SIGALRM, handle_sig_alarm); /* ignored unless
! * InHotStandby */
else
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
diff -dcrpN postgresql.orig/src/backend/storage/ipc/standby.c postgresql.1/src/backend/storage/ipc/standby.c
*** postgresql.orig/src/backend/storage/ipc/standby.c 2012-04-16 19:57:22.458915722 +0200
--- postgresql.1/src/backend/storage/ipc/standby.c 2012-04-23 10:30:38.294732014 +0200
***************
*** 23,32 ****
#include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
- #include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/sinvaladt.h"
#include "storage/standby.h"
#include "utils/ps_status.h"
#include "utils/timestamp.h"
--- 23,32 ----
#include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
#include "storage/procarray.h"
#include "storage/sinvaladt.h"
#include "storage/standby.h"
+ #include "storage/timeout.h"
#include "utils/ps_status.h"
#include "utils/timestamp.h"
*************** ResolveRecoveryConflictWithLock(Oid dbOi
*** 394,400 ****
void
ResolveRecoveryConflictWithBufferPin(void)
{
- bool sig_alarm_enabled = false;
TimestampTz ltime;
TimestampTz now;
--- 394,399 ----
*************** ResolveRecoveryConflictWithBufferPin(voi
*** 409,417 ****
* We're willing to wait forever for conflicts, so set timeout for
* deadlock check (only)
*/
! if (enable_standby_sig_alarm(now, now, true))
! sig_alarm_enabled = true;
! else
elog(FATAL, "could not set timer for process wakeup");
}
else if (now >= ltime)
--- 408,414 ----
* We're willing to wait forever for conflicts, so set timeout for
* deadlock check (only)
*/
! if (!enable_timeout(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout))
elog(FATAL, "could not set timer for process wakeup");
}
else if (now >= ltime)
*************** ResolveRecoveryConflictWithBufferPin(voi
*** 423,446 ****
}
else
{
/*
* Wake up at ltime, and check for deadlocks as well if we will be
* waiting longer than deadlock_timeout
*/
! if (enable_standby_sig_alarm(now, ltime, false))
! sig_alarm_enabled = true;
! else
elog(FATAL, "could not set timer for process wakeup");
}
/* Wait to be signaled by UnpinBuffer() */
ProcWaitForSignal();
! if (sig_alarm_enabled)
! {
! if (!disable_standby_sig_alarm())
! elog(FATAL, "could not disable timer for process wakeup");
! }
}
void
--- 420,447 ----
}
else
{
+ long secs, msecs;
+ int usecs;
+
/*
* Wake up at ltime, and check for deadlocks as well if we will be
* waiting longer than deadlock_timeout
*/
! if (!enable_timeout(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout))
! elog(FATAL, "could not set timer for process wakeup");
!
! TimestampDifference(now, ltime, &secs, &usecs);
! msecs = secs * 1000 + usecs / 1000;
!
! if (!enable_timeout(STANDBY_TIMEOUT, msecs))
elog(FATAL, "could not set timer for process wakeup");
}
/* Wait to be signaled by UnpinBuffer() */
ProcWaitForSignal();
! if (!disable_all_timeouts(false))
! elog(FATAL, "could not disable timer for process wakeup");
}
void
diff -dcrpN postgresql.orig/src/backend/storage/lmgr/Makefile postgresql.1/src/backend/storage/lmgr/Makefile
*** postgresql.orig/src/backend/storage/lmgr/Makefile 2012-04-16 19:57:22.458915722 +0200
--- postgresql.1/src/backend/storage/lmgr/Makefile 2012-04-23 10:30:38.295732021 +0200
*************** subdir = src/backend/storage/lmgr
*** 12,18 ****
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
! OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o predicate.o
include $(top_srcdir)/src/backend/common.mk
--- 12,18 ----
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
! OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o predicate.o timeout.o
include $(top_srcdir)/src/backend/common.mk
diff -dcrpN postgresql.orig/src/backend/storage/lmgr/proc.c postgresql.1/src/backend/storage/lmgr/proc.c
*** postgresql.orig/src/backend/storage/lmgr/proc.c 2012-04-19 11:48:04.716978188 +0200
--- postgresql.1/src/backend/storage/lmgr/proc.c 2012-04-23 10:30:38.297732034 +0200
***************
*** 48,59 ****
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/spin.h"
#include "utils/timestamp.h"
/* GUC variables */
- int DeadlockTimeout = 1000;
- int StatementTimeout = 0;
bool log_lock_waits = false;
/* Pointer to this process's PGPROC struct, if any */
--- 48,58 ----
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/spin.h"
+ #include "storage/timeout.h"
#include "utils/timestamp.h"
/* GUC variables */
bool log_lock_waits = false;
/* Pointer to this process's PGPROC struct, if any */
*************** PGPROC *PreparedXactProcs = NULL;
*** 77,103 ****
/* If we are waiting for a lock, this points to the associated LOCALLOCK */
static LOCALLOCK *lockAwaited = NULL;
! /* Mark these volatile because they can be changed by signal handler */
! static volatile bool standby_timeout_active = false;
! static volatile bool statement_timeout_active = false;
! static volatile bool deadlock_timeout_active = false;
! static volatile DeadLockState deadlock_state = DS_NOT_YET_CHECKED;
! volatile bool cancel_from_timeout = false;
!
! /* timeout_start_time is set when log_lock_waits is true */
! static TimestampTz timeout_start_time;
!
! /* statement_fin_time is valid only if statement_timeout_active is true */
! static TimestampTz statement_fin_time;
! static TimestampTz statement_fin_time2; /* valid only in recovery */
!
static void RemoveProcFromArray(int code, Datum arg);
static void ProcKill(int code, Datum arg);
static void AuxiliaryProcKill(int code, Datum arg);
- static bool CheckStatementTimeout(void);
- static bool CheckStandbyTimeout(void);
-
/*
* Report shared-memory space needed by InitProcGlobal.
--- 76,87 ----
/* If we are waiting for a lock, this points to the associated LOCALLOCK */
static LOCALLOCK *lockAwaited = NULL;
! /* Declared in timeout.c */
! extern volatile DeadLockState deadlock_state;
static void RemoveProcFromArray(int code, Datum arg);
static void ProcKill(int code, Datum arg);
static void AuxiliaryProcKill(int code, Datum arg);
/*
* Report shared-memory space needed by InitProcGlobal.
*************** LockErrorCleanup(void)
*** 654,660 ****
return;
/* Turn off the deadlock timer, if it's still running (see ProcSleep) */
! disable_sig_alarm(false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
--- 638,644 ----
return;
/* Turn off the deadlock timer, if it's still running (see ProcSleep) */
! disable_timeout(DEADLOCK_TIMEOUT, false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1047,1053 ****
* 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 (!enable_sig_alarm(DeadlockTimeout, false))
elog(FATAL, "could not set timer for process wakeup");
/*
--- 1031,1037 ----
* 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 (!enable_timeout(DEADLOCK_TIMEOUT, DeadlockTimeout))
elog(FATAL, "could not set timer for process wakeup");
/*
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1137,1143 ****
DescribeLockTag(&buf, &locallock->tag.lock);
modename = GetLockmodeName(locallock->tag.lock.locktag_lockmethodid,
lockmode);
! TimestampDifference(timeout_start_time, GetCurrentTimestamp(),
&secs, &usecs);
msecs = secs * 1000 + usecs / 1000;
usecs = usecs % 1000;
--- 1121,1128 ----
DescribeLockTag(&buf, &locallock->tag.lock);
modename = GetLockmodeName(locallock->tag.lock.locktag_lockmethodid,
lockmode);
! TimestampDifference(get_timeout_start(DEADLOCK_TIMEOUT),
! GetCurrentTimestamp(),
&secs, &usecs);
msecs = secs * 1000 + usecs / 1000;
usecs = usecs % 1000;
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1199,1205 ****
/*
* Disable the timer, if it's still running
*/
! if (!disable_sig_alarm(false))
elog(FATAL, "could not disable timer for process wakeup");
/*
--- 1184,1190 ----
/*
* Disable the timer, if it's still running
*/
! if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for process wakeup");
/*
*************** ProcLockWakeup(LockMethod lockMethodTabl
*** 1331,1451 ****
}
/*
- * CheckDeadLock
- *
- * We only get to this routine if we got SIGALRM after DeadlockTimeout
- * while waiting for a lock to be released by some other process. Look
- * to see if there's a deadlock; if not, just return and continue waiting.
- * (But signal ProcSleep to log a message, if log_lock_waits is true.)
- * If we have a real deadlock, remove ourselves from the lock's wait queue
- * and signal an error to ProcSleep.
- *
- * NB: this is run inside a signal handler, so be very wary about what is done
- * here or in called routines.
- */
- static void
- CheckDeadLock(void)
- {
- int i;
-
- /*
- * Acquire exclusive lock on the entire shared lock data structures. Must
- * grab LWLocks in partition-number order to avoid LWLock deadlock.
- *
- * Note that the deadlock check interrupt had better not be enabled
- * anywhere that this process itself holds lock partition locks, else this
- * will wait forever. Also note that LWLockAcquire creates a critical
- * section, so that this routine cannot be interrupted by cancel/die
- * interrupts.
- */
- for (i = 0; i < NUM_LOCK_PARTITIONS; i++)
- LWLockAcquire(FirstLockMgrLock + i, LW_EXCLUSIVE);
-
- /*
- * Check to see if we've been awoken by anyone in the interim.
- *
- * If we have, we can return and resume our transaction -- happy day.
- * Before we are awoken the process releasing the lock grants it to us so
- * we know that we don't have to wait anymore.
- *
- * We check by looking to see if we've been unlinked from the wait queue.
- * This is quicker than checking our semaphore's state, since no kernel
- * call is needed, and it is safe because we hold the lock partition lock.
- */
- if (MyProc->links.prev == NULL ||
- MyProc->links.next == NULL)
- goto check_done;
-
- #ifdef LOCK_DEBUG
- if (Debug_deadlocks)
- DumpAllLocks();
- #endif
-
- /* Run the deadlock check, and set deadlock_state for use by ProcSleep */
- deadlock_state = DeadLockCheck(MyProc);
-
- if (deadlock_state == DS_HARD_DEADLOCK)
- {
- /*
- * Oops. We have a deadlock.
- *
- * Get this process out of wait state. (Note: we could do this more
- * efficiently by relying on lockAwaited, but use this coding to
- * preserve the flexibility to kill some other transaction than the
- * one detecting the deadlock.)
- *
- * RemoveFromWaitQueue sets MyProc->waitStatus to STATUS_ERROR, so
- * ProcSleep will report an error after we return from the signal
- * handler.
- */
- Assert(MyProc->waitLock != NULL);
- RemoveFromWaitQueue(MyProc, LockTagHashCode(&(MyProc->waitLock->tag)));
-
- /*
- * Unlock my semaphore so that the interrupted ProcSleep() call can
- * finish.
- */
- PGSemaphoreUnlock(&MyProc->sem);
-
- /*
- * We're done here. Transaction abort caused by the error that
- * ProcSleep will raise will cause any other locks we hold to be
- * released, thus allowing other processes to wake up; we don't need
- * to do that here. NOTE: an exception is that releasing locks we
- * hold doesn't consider the possibility of waiters that were blocked
- * behind us on the lock we just failed to get, and might now be
- * wakable because we're not in front of them anymore. However,
- * RemoveFromWaitQueue took care of waking up any such processes.
- */
- }
- else if (log_lock_waits || deadlock_state == DS_BLOCKED_BY_AUTOVACUUM)
- {
- /*
- * Unlock my semaphore so that the interrupted ProcSleep() call can
- * print the log message (we daren't do it here because we are inside
- * a signal handler). It will then sleep again until someone releases
- * the lock.
- *
- * If blocked by autovacuum, this wakeup will enable ProcSleep to send
- * the canceling signal to the autovacuum worker.
- */
- PGSemaphoreUnlock(&MyProc->sem);
- }
-
- /*
- * And release locks. We do this in reverse order for two reasons: (1)
- * Anyone else who needs more than one of the locks will be trying to lock
- * them in increasing order; we don't want to release the other process
- * until it can get all the locks it needs. (2) This avoids O(N^2)
- * behavior inside LWLockRelease.
- */
- check_done:
- for (i = NUM_LOCK_PARTITIONS; --i >= 0;)
- LWLockRelease(FirstLockMgrLock + i);
- }
-
-
- /*
* ProcWaitForSignal - wait for a signal from another backend.
*
* This can share the semaphore normally used for waiting for locks,
--- 1316,1321 ----
*************** ProcSendSignal(int pid)
*** 1497,1897 ****
if (proc != NULL)
PGSemaphoreUnlock(&proc->sem);
}
-
-
- /*****************************************************************************
- * SIGALRM interrupt support
- *
- * Maybe these should be in pqsignal.c?
- *****************************************************************************/
-
- /*
- * Enable the SIGALRM interrupt to fire after the specified delay
- *
- * Delay is given in milliseconds. Caller should be sure a SIGALRM
- * signal handler is installed before this is called.
- *
- * This code properly handles nesting of deadlock timeout alarms within
- * statement timeout alarms.
- *
- * Returns TRUE if okay, FALSE on failure.
- */
- bool
- enable_sig_alarm(int delayms, bool is_statement_timeout)
- {
- TimestampTz fin_time;
- struct itimerval timeval;
-
- if (is_statement_timeout)
- {
- /*
- * Begin statement-level timeout
- *
- * Note that we compute statement_fin_time with reference to the
- * statement_timestamp, but apply the specified delay without any
- * correction; that is, we ignore whatever time has elapsed since
- * statement_timestamp was set. In the normal case only a small
- * interval will have elapsed and so this doesn't matter, but there
- * are corner cases (involving multi-statement query strings with
- * embedded COMMIT or ROLLBACK) where we might re-initialize the
- * statement timeout long after initial receipt of the message. In
- * such cases the enforcement of the statement timeout will be a bit
- * inconsistent. This annoyance is judged not worth the cost of
- * performing an additional gettimeofday() here.
- */
- Assert(!deadlock_timeout_active);
- fin_time = GetCurrentStatementStartTimestamp();
- fin_time = TimestampTzPlusMilliseconds(fin_time, delayms);
- statement_fin_time = fin_time;
- cancel_from_timeout = false;
- statement_timeout_active = true;
- }
- else if (statement_timeout_active)
- {
- /*
- * Begin deadlock timeout with statement-level timeout active
- *
- * Here, we want to interrupt at the closer of the two timeout times.
- * If fin_time >= statement_fin_time then we need not touch the
- * existing timer setting; else set up to interrupt at the deadlock
- * timeout time.
- *
- * NOTE: in this case it is possible that this routine will be
- * interrupted by the previously-set timer alarm. This is okay
- * because the signal handler will do only what it should do according
- * to the state variables. The deadlock checker may get run earlier
- * than normal, but that does no harm.
- */
- timeout_start_time = GetCurrentTimestamp();
- fin_time = TimestampTzPlusMilliseconds(timeout_start_time, delayms);
- deadlock_timeout_active = true;
- if (fin_time >= statement_fin_time)
- return true;
- }
- else
- {
- /* Begin deadlock timeout with no statement-level timeout */
- deadlock_timeout_active = true;
- /* GetCurrentTimestamp can be expensive, so only do it if we must */
- if (log_lock_waits)
- timeout_start_time = GetCurrentTimestamp();
- }
-
- /* If we reach here, okay to set the timer interrupt */
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = delayms / 1000;
- timeval.it_value.tv_usec = (delayms % 1000) * 1000;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- return true;
- }
-
- /*
- * Cancel the SIGALRM timer, either for a deadlock timeout or a statement
- * timeout. If a deadlock timeout is canceled, any active statement timeout
- * remains in force.
- *
- * Returns TRUE if okay, FALSE on failure.
- */
- bool
- disable_sig_alarm(bool is_statement_timeout)
- {
- /*
- * Always disable the interrupt if it is active; this avoids being
- * interrupted by the signal handler and thereby possibly getting
- * confused.
- *
- * We will re-enable the interrupt if necessary in CheckStatementTimeout.
- */
- if (statement_timeout_active || deadlock_timeout_active)
- {
- struct itimerval timeval;
-
- MemSet(&timeval, 0, sizeof(struct itimerval));
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- {
- statement_timeout_active = false;
- cancel_from_timeout = false;
- deadlock_timeout_active = false;
- return false;
- }
- }
-
- /* Always cancel deadlock timeout, in case this is error cleanup */
- deadlock_timeout_active = false;
-
- /* Cancel or reschedule statement timeout */
- if (is_statement_timeout)
- {
- statement_timeout_active = false;
- cancel_from_timeout = false;
- }
- else if (statement_timeout_active)
- {
- if (!CheckStatementTimeout())
- return false;
- }
- return true;
- }
-
-
- /*
- * Check for statement timeout. If the timeout time has come,
- * trigger a query-cancel interrupt; if not, reschedule the SIGALRM
- * interrupt to occur at the right time.
- *
- * Returns true if okay, false if failed to set the interrupt.
- */
- static bool
- CheckStatementTimeout(void)
- {
- TimestampTz now;
-
- if (!statement_timeout_active)
- return true; /* do nothing if not active */
-
- now = GetCurrentTimestamp();
-
- if (now >= statement_fin_time)
- {
- /* Time to die */
- statement_timeout_active = false;
- cancel_from_timeout = true;
- #ifdef HAVE_SETSID
- /* try to signal whole process group */
- kill(-MyProcPid, SIGINT);
- #endif
- kill(MyProcPid, SIGINT);
- }
- else
- {
- /* Not time yet, so (re)schedule the interrupt */
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
-
- /*
- * It's possible that the difference is less than a microsecond;
- * ensure we don't cancel, rather than set, the interrupt.
- */
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- }
-
- return true;
- }
-
-
- /*
- * Signal handler for SIGALRM for normal user backends
- *
- * Process deadlock check and/or statement timeout check, as needed.
- * To avoid various edge cases, we must be careful to do nothing
- * when there is nothing to be done. We also need to be able to
- * reschedule the timer interrupt if called before end of statement.
- */
- void
- handle_sig_alarm(SIGNAL_ARGS)
- {
- int save_errno = errno;
-
- /* SIGALRM is cause for waking anything waiting on the process latch */
- if (MyProc)
- SetLatch(&MyProc->procLatch);
-
- if (deadlock_timeout_active)
- {
- deadlock_timeout_active = false;
- CheckDeadLock();
- }
-
- if (statement_timeout_active)
- (void) CheckStatementTimeout();
-
- errno = save_errno;
- }
-
- /*
- * Signal handler for SIGALRM in Startup process
- *
- * To avoid various edge cases, we must be careful to do nothing
- * when there is nothing to be done. We also need to be able to
- * reschedule the timer interrupt if called before end of statement.
- *
- * We set either deadlock_timeout_active or statement_timeout_active
- * or both. Interrupts are enabled if standby_timeout_active.
- */
- bool
- enable_standby_sig_alarm(TimestampTz now, TimestampTz fin_time, bool deadlock_only)
- {
- TimestampTz deadlock_time = TimestampTzPlusMilliseconds(now,
- DeadlockTimeout);
-
- if (deadlock_only)
- {
- /*
- * Wake up at deadlock_time only, then wait forever
- */
- statement_fin_time = deadlock_time;
- deadlock_timeout_active = true;
- statement_timeout_active = false;
- }
- else if (fin_time > deadlock_time)
- {
- /*
- * Wake up at deadlock_time, then again at fin_time
- */
- statement_fin_time = deadlock_time;
- statement_fin_time2 = fin_time;
- deadlock_timeout_active = true;
- statement_timeout_active = true;
- }
- else
- {
- /*
- * Wake only at fin_time because its fairly soon
- */
- statement_fin_time = fin_time;
- deadlock_timeout_active = false;
- statement_timeout_active = true;
- }
-
- if (deadlock_timeout_active || statement_timeout_active)
- {
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- standby_timeout_active = true;
- }
-
- return true;
- }
-
- bool
- disable_standby_sig_alarm(void)
- {
- /*
- * Always disable the interrupt if it is active; this avoids being
- * interrupted by the signal handler and thereby possibly getting
- * confused.
- *
- * We will re-enable the interrupt if necessary in CheckStandbyTimeout.
- */
- if (standby_timeout_active)
- {
- struct itimerval timeval;
-
- MemSet(&timeval, 0, sizeof(struct itimerval));
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- {
- standby_timeout_active = false;
- return false;
- }
- }
-
- standby_timeout_active = false;
-
- return true;
- }
-
- /*
- * CheckStandbyTimeout() runs unconditionally in the Startup process
- * SIGALRM handler. Timers will only be set when InHotStandby.
- * We simply ignore any signals unless the timer has been set.
- */
- static bool
- CheckStandbyTimeout(void)
- {
- TimestampTz now;
- bool reschedule = false;
-
- standby_timeout_active = false;
-
- now = GetCurrentTimestamp();
-
- /*
- * Reschedule the timer if its not time to wake yet, or if we have both
- * timers set and the first one has just been reached.
- */
- if (now >= statement_fin_time)
- {
- if (deadlock_timeout_active)
- {
- /*
- * We're still waiting when we reach deadlock timeout, so send out
- * a request to have other backends check themselves for deadlock.
- * Then continue waiting until statement_fin_time, if that's set.
- */
- SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
- deadlock_timeout_active = false;
-
- /*
- * Begin second waiting period if required.
- */
- if (statement_timeout_active)
- {
- reschedule = true;
- statement_fin_time = statement_fin_time2;
- }
- }
- else
- {
- /*
- * We've now reached statement_fin_time, so ask all conflicts to
- * leave, so we can press ahead with applying changes in recovery.
- */
- SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
- }
- }
- else
- reschedule = true;
-
- if (reschedule)
- {
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- standby_timeout_active = true;
- }
-
- return true;
- }
-
- void
- handle_standby_sig_alarm(SIGNAL_ARGS)
- {
- int save_errno = errno;
-
- if (standby_timeout_active)
- (void) CheckStandbyTimeout();
-
- errno = save_errno;
- }
--- 1367,1369 ----
diff -dcrpN postgresql.orig/src/backend/storage/lmgr/timeout.c postgresql.1/src/backend/storage/lmgr/timeout.c
*** postgresql.orig/src/backend/storage/lmgr/timeout.c 1970-01-01 01:00:00.000000000 +0100
--- postgresql.1/src/backend/storage/lmgr/timeout.c 2012-04-23 10:30:38.299732046 +0200
***************
*** 0 ****
--- 1,680 ----
+ /*-------------------------------------------------------------------------
+ *
+ * timeout.c
+ * routines to manage timeout sources handled by SIGALRM
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/storage/lmgr/timeout.c
+ *
+ *-------------------------------------------------------------------------
+ */
+ /*
+ * Interface:
+ * enable_timeout(),
+ * disable_timeout(), disable_all_timeouts()
+ * get_timeout_indicator(), get_timeout_start()
+ * and
+ * handle_sig_alarm()
+ */
+ #include "postgres.h"
+
+ #include <sys/time.h>
+
+ #include "access/xact.h"
+ #include "miscadmin.h"
+ #include "storage/proc.h"
+ #include "storage/standby.h"
+ #include "storage/timeout.h"
+ #include "utils/timestamp.h"
+
+ /* GUC variables */
+ int DeadlockTimeout = 1000;
+ int StatementTimeout = 0;
+
+ /*
+ * This is used by ProcSleep() in proc.c
+ * Mark this volatile because it can be changed by the signal handler.
+ */
+ volatile DeadLockState deadlock_state = DS_NOT_YET_CHECKED;
+
+
+ /*
+ * Infrastructure for timeouts
+ */
+
+ static void InitDeadLock(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyDeadLock(bool keep_indicator);
+ static bool CheckDeadLock(void);
+
+ static void InitStatementTimeout(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyStatementTimeout(bool keep_indicator);
+ static bool CheckStatementTimeout(void);
+
+ static void InitStandbyDeadLock(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyStandbyDeadLock(bool keep_indicator);
+ static bool CheckStandbyDeadLock(void);
+
+ static void InitStandbyTimeout(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyStandbyTimeout(bool keep_indicator);
+ static bool CheckStandbyTimeout(void);
+
+ typedef void (*timeout_init)(TimestampTz, TimestampTz);
+ typedef void (*timeout_destroy)(bool);
+ typedef bool (*timeout_check)(void);
+ typedef TimestampTz (*timeout_start)(void);
+
+ typedef struct {
+ TimeoutName index;
+ bool resched_next;
+
+ /* volatile because it may be changed from the signal handler */
+ volatile bool indicator;
+
+ timeout_init timeout_init;
+ timeout_destroy timeout_destroy;
+ timeout_check timeout_check;
+ timeout_start timeout_start;
+
+ TimestampTz start_time;
+ TimestampTz fin_time;
+ } timeout_params;
+
+ /*
+ * List of possible timeout reasons in the order of enum TimeoutName.
+ * The priority of timeouts (in case two of them would trigger at the
+ * same time) is determined by this order: the earlier one in the list
+ * has higher priority.
+ */
+ static timeout_params base_timeouts[TIMEOUT_MAX] = {
+ {
+ DEADLOCK_TIMEOUT, true, false,
+ InitDeadLock, DestroyDeadLock,
+ CheckDeadLock, GetCurrentTimestamp,
+ 0
+ },
+
+ {
+ STATEMENT_TIMEOUT, false, false,
+ InitStatementTimeout, DestroyStatementTimeout,
+ CheckStatementTimeout, GetCurrentStatementStartTimestamp,
+ 0
+ },
+
+ {
+ STANDBY_DEADLOCK_TIMEOUT, true, false,
+ InitStandbyDeadLock, DestroyStandbyDeadLock,
+ CheckStandbyDeadLock, GetCurrentTimestamp,
+ 0
+ },
+
+ {
+ STANDBY_TIMEOUT, false, false,
+ InitStandbyTimeout, DestroyStandbyTimeout,
+ CheckStandbyTimeout, GetCurrentTimestamp,
+ 0
+ }
+ };
+
+ /*
+ * List of active timeouts ordered by their fin_time and priority.
+ */
+ static int n_timeouts = 0;
+ static timeout_params *timeouts[TIMEOUT_MAX];
+
+ /*****************************************************************************
+ * Internal helper functions
+ *****************************************************************************/
+
+ /*
+ * Find the index of a given timeout type in the active array
+ */
+ static int
+ find_active_timeout(TimeoutName tn)
+ {
+ int i;
+
+ for (i = 0; i < n_timeouts; i++)
+ {
+ if (timeouts[i]->index == tn)
+ return i;
+ }
+
+ return -1;
+ }
+
+ #define is_timeout_active(tn) (find_active_timeout(tn) >= 0)
+
+ /*
+ * Insert tn'th timeout into the list of timeouts at the given index
+ * if the previous timeout allows rescheduling the next one in the list.
+ */
+ static void
+ insert_timeout(TimeoutName tn, int index)
+ {
+ int i;
+
+ if (index > 0 && !timeouts[index-1]->resched_next)
+ return;
+
+ for (i = n_timeouts - 1; i >= index; i--)
+ timeouts[i+1] = timeouts[i];
+
+ timeouts[index] = &base_timeouts[tn];
+ n_timeouts++;
+ }
+
+ /*
+ * Remove the index'th element from the timeout list.
+ */
+ static void
+ remove_timeout_index(int index)
+ {
+ int i;
+
+ if (index < 0)
+ return;
+
+ for (i = index + 1; i < n_timeouts; i++)
+ timeouts[i-1] = timeouts[i];
+
+ if (n_timeouts > 0 && index < n_timeouts)
+ n_timeouts--;
+ }
+
+ /*
+ * (Re)schedule the next active timeout
+ */
+ static bool
+ schedule_timeout(TimestampTz now)
+ {
+ long secs;
+ int usecs;
+ struct itimerval timeval;
+
+ /* There is no active timeout, do nothing. */
+ if (n_timeouts == 0)
+ return true;
+
+ TimestampDifference(now, timeouts[0]->fin_time,
+ &secs, &usecs);
+
+ /*
+ * It's possible that the difference is less than a microsecond;
+ * ensure we don't cancel, rather than set, the interrupt.
+ */
+ if (secs == 0 && usecs == 0)
+ usecs = 1;
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ timeval.it_value.tv_sec = secs;
+ timeval.it_value.tv_usec = usecs;
+ if (setitimer(ITIMER_REAL, &timeval, NULL))
+ return false;
+ return true;
+ }
+
+ /*****************************************************************************
+ * Init, Destroy and Check functions for different timeouts.
+ *
+ * NB: all Check* functions are run inside a signal handler, so be very wary
+ * about what is done in them or in called routines.
+ *****************************************************************************/
+
+ /*
+ * Common Init and Destroy functions
+ */
+
+ static void
+ InitTimeout(TimeoutName tn, TimestampTz start_time, TimestampTz fin_time)
+ {
+ base_timeouts[tn].indicator = false;
+ base_timeouts[tn].start_time = start_time;
+ base_timeouts[tn].fin_time = fin_time;
+ }
+
+ static void
+ DestroyTimeout(TimeoutName tn, bool keep_indicator)
+ {
+ if (!keep_indicator)
+ base_timeouts[tn].indicator = false;
+ base_timeouts[tn].start_time = 0;
+ base_timeouts[tn].fin_time = 0;
+ }
+
+ /*
+ * Functions to manage deadlock
+ */
+
+ static void
+ InitDeadLock(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(DEADLOCK_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyDeadLock(bool keep_indicator)
+ {
+ DestroyTimeout(DEADLOCK_TIMEOUT, keep_indicator);
+ }
+
+ /*
+ * CheckDeadLock
+ *
+ * Look to see if there's a deadlock; if not, just return.
+ * (But signal ProcSleep to log a message, if log_lock_waits is true.)
+ * If we have a real deadlock, remove ourselves from the lock's wait queue
+ * and signal an error to ProcSleep.
+ */
+ static bool
+ CheckDeadLock(void)
+ {
+ TimestampTz now;
+ int i;
+
+ now = GetCurrentTimestamp();
+
+ /* If our time has not come yet, do nothing. */
+ if (now < base_timeouts[DEADLOCK_TIMEOUT].fin_time)
+ return false;
+
+ /*
+ * Acquire exclusive lock on the entire shared lock data structures. Must
+ * grab LWLocks in partition-number order to avoid LWLock deadlock.
+ *
+ * Note that the deadlock check interrupt had better not be enabled
+ * anywhere that this process itself holds lock partition locks, else this
+ * will wait forever. Also note that LWLockAcquire creates a critical
+ * section, so that this routine cannot be interrupted by cancel/die
+ * interrupts.
+ */
+ for (i = 0; i < NUM_LOCK_PARTITIONS; i++)
+ LWLockAcquire(FirstLockMgrLock + i, LW_EXCLUSIVE);
+
+ /*
+ * Check to see if we've been awoken by anyone in the interim.
+ *
+ * If we have, we can return and resume our transaction -- happy day.
+ * Before we are awoken the process releasing the lock grants it to us so
+ * we know that we don't have to wait anymore.
+ *
+ * We check by looking to see if we've been unlinked from the wait queue.
+ * This is quicker than checking our semaphore's state, since no kernel
+ * call is needed, and it is safe because we hold the lock partition lock.
+ */
+ if (MyProc->links.prev == NULL ||
+ MyProc->links.next == NULL)
+ goto check_done;
+
+ #ifdef LOCK_DEBUG
+ if (Debug_deadlocks)
+ DumpAllLocks();
+ #endif
+
+ /* Run the deadlock check, and set deadlock_state for use by ProcSleep */
+ deadlock_state = DeadLockCheck(MyProc);
+
+ if (deadlock_state == DS_HARD_DEADLOCK)
+ {
+ /*
+ * Oops. We have a deadlock.
+ *
+ * Get this process out of wait state. (Note: we could do this more
+ * efficiently by relying on lockAwaited, but use this coding to
+ * preserve the flexibility to kill some other transaction than the
+ * one detecting the deadlock.)
+ *
+ * RemoveFromWaitQueue sets MyProc->waitStatus to STATUS_ERROR, so
+ * ProcSleep will report an error after we return from the signal
+ * handler.
+ */
+ Assert(MyProc->waitLock != NULL);
+ RemoveFromWaitQueue(MyProc, LockTagHashCode(&(MyProc->waitLock->tag)));
+
+ /*
+ * Unlock my semaphore so that the interrupted ProcSleep() call can
+ * finish.
+ */
+ PGSemaphoreUnlock(&MyProc->sem);
+
+ /*
+ * We're done here. Transaction abort caused by the error that
+ * ProcSleep will raise will cause any other locks we hold to be
+ * released, thus allowing other processes to wake up; we don't need
+ * to do that here. NOTE: an exception is that releasing locks we
+ * hold doesn't consider the possibility of waiters that were blocked
+ * behind us on the lock we just failed to get, and might now be
+ * wakable because we're not in front of them anymore. However,
+ * RemoveFromWaitQueue took care of waking up any such processes.
+ */
+ }
+ else if (log_lock_waits || deadlock_state == DS_BLOCKED_BY_AUTOVACUUM)
+ {
+ /*
+ * Unlock my semaphore so that the interrupted ProcSleep() call can
+ * print the log message (we daren't do it here because we are inside
+ * a signal handler). It will then sleep again until someone releases
+ * the lock.
+ *
+ * If blocked by autovacuum, this wakeup will enable ProcSleep to send
+ * the canceling signal to the autovacuum worker.
+ */
+ PGSemaphoreUnlock(&MyProc->sem);
+ }
+
+ /*
+ * And release locks. We do this in reverse order for two reasons: (1)
+ * Anyone else who needs more than one of the locks will be trying to lock
+ * them in increasing order; we don't want to release the other process
+ * until it can get all the locks it needs. (2) This avoids O(N^2)
+ * behavior inside LWLockRelease.
+ */
+ check_done:
+ for (i = NUM_LOCK_PARTITIONS; --i >= 0;)
+ LWLockRelease(FirstLockMgrLock + i);
+
+ base_timeouts[DEADLOCK_TIMEOUT].indicator = true;
+
+ return true;
+ }
+
+ /*
+ * Functions to manage statement timeout
+ */
+
+ static void
+ InitStatementTimeout(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(STATEMENT_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyStatementTimeout(bool keep_indicator)
+ {
+ DestroyTimeout(STATEMENT_TIMEOUT, keep_indicator);
+ }
+
+ static bool
+ CheckStatementTimeout(void)
+ {
+ TimestampTz now;
+
+ now = GetCurrentTimestamp();
+
+ if (now < base_timeouts[STATEMENT_TIMEOUT].fin_time)
+ return false;
+
+ base_timeouts[STATEMENT_TIMEOUT].indicator = true;
+ #ifdef HAVE_SETSID
+ /* try to signal whole process group */
+ kill(-MyProcPid, SIGINT);
+ #endif
+ kill(MyProcPid, SIGINT);
+
+ return true;
+ }
+
+ /*
+ * Functions to manage standby deadlock functions
+ */
+
+ static void
+ InitStandbyDeadLock(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(STANDBY_DEADLOCK_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyStandbyDeadLock(bool keep_indicator)
+ {
+ DestroyTimeout(STANDBY_DEADLOCK_TIMEOUT, keep_indicator);
+ }
+
+ static bool
+ CheckStandbyDeadLock(void)
+ {
+ TimestampTz now;
+
+ now = GetCurrentTimestamp();
+
+ if (now < base_timeouts[STANDBY_DEADLOCK_TIMEOUT].fin_time)
+ return false;
+
+ base_timeouts[STANDBY_DEADLOCK_TIMEOUT].indicator = true;
+ SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
+ return true;
+ }
+
+ /*
+ * Functions to manage standby timeout
+ */
+
+ static void
+ InitStandbyTimeout(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(STANDBY_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyStandbyTimeout(bool keep_indicator)
+ {
+ DestroyTimeout(STANDBY_TIMEOUT, keep_indicator);
+ }
+
+ static bool
+ CheckStandbyTimeout(void)
+ {
+ TimestampTz now;
+
+ now = GetCurrentTimestamp();
+
+ if (now < base_timeouts[STANDBY_TIMEOUT].fin_time)
+ return false;
+
+ base_timeouts[STANDBY_TIMEOUT].indicator = true;
+ SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
+ return true;
+ }
+
+ /*****************************************************************************
+ * Public API
+ *****************************************************************************/
+
+
+ /*
+ * Enable the SIGALRM interrupt to fire after the specified delay
+ *
+ * Delay is given in milliseconds. Caller should be sure a SIGALRM
+ * signal handler is installed before this is called.
+ *
+ * This code properly handles nesting of different timeout alarms.
+ *
+ * Returns TRUE if okay, FALSE on failure to set the timer.
+ */
+ bool
+ enable_timeout(TimeoutName tn, int delayms)
+ {
+ TimestampTz start_time;
+ TimestampTz fin_time;
+ int i;
+
+ Assert(!is_timeout_active(tn));
+
+ if (delayms <= 0)
+ return true;
+
+ start_time = base_timeouts[tn].timeout_start();
+ fin_time = TimestampTzPlusMilliseconds(start_time, delayms);
+
+ /* Find out the index where to insert the new timeout. */
+ for (i = 0; i < n_timeouts; i++)
+ {
+ /*
+ * The new timeout triggers earlier than
+ * a previously added one: insert here.
+ */
+ if (fin_time < timeouts[i]->fin_time)
+ break;
+ /*
+ * The new timeout triggers at the same time
+ * as the previously added one but has greater priority.
+ */
+ if (fin_time == timeouts[i]->fin_time && tn < timeouts[i]->index)
+ break;
+ }
+
+ /*
+ * Initialize the timeout parameters
+ */
+ base_timeouts[tn].timeout_init(start_time, fin_time);
+
+ insert_timeout(tn, i);
+
+ if (i > 0)
+ return true;
+
+ /* If we reach here, okay to set the timer interrupt */
+ if (!schedule_timeout(start_time))
+ return false;
+ return true;
+ }
+
+ /*
+ * Cancel the SIGALRM timer for the specific timeout.
+ * If a timeout is canceled, any other active timeout remains in force.
+ *
+ * Returns TRUE if okay, FALSE on failure to set the timer for
+ * the next timeout source.
+ */
+ bool
+ disable_timeout(TimeoutName tn, bool keep_indicator)
+ {
+ int i;
+
+ /*
+ * Always disable the interrupt if it is active; this avoids being
+ * interrupted by the signal handler and thereby possibly getting
+ * confused.
+ *
+ * We will re-enable the interrupt if necessary in ->check_timeout().
+ */
+ if (n_timeouts > 0)
+ {
+ struct itimerval timeval;
+
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ if (setitimer(ITIMER_REAL, &timeval, NULL))
+ {
+ n_timeouts = 0;
+ for (i = 0; i < TIMEOUT_MAX; i++)
+ base_timeouts[i].timeout_destroy(false);
+
+ return false;
+ }
+ }
+
+ /* Find the timeout and remove from the list. */
+ i = find_active_timeout(tn);
+ remove_timeout_index(i);
+
+ /* Do cleanup. */
+ base_timeouts[tn].timeout_destroy(keep_indicator);
+
+ /*
+ * If the first timeout was removed from the list and there is
+ * at least one active left, reschedule it.
+ */
+ if (i == 0 && n_timeouts > 0)
+ if (!schedule_timeout(GetCurrentTimestamp()))
+ return false;
+
+ return true;
+ }
+
+ /*
+ * Disable SIGALRM and remove all timeouts from the list and
+ * reset the timeout indicators.
+ */
+ bool
+ disable_all_timeouts(bool keep_indicators)
+ {
+ struct itimerval timeval;
+ int i;
+ bool ret;
+
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ ret = (setitimer(ITIMER_REAL, &timeval, NULL) == 0);
+
+ n_timeouts = 0;
+ for (i = 0; i < TIMEOUT_MAX; i++)
+ base_timeouts[i].timeout_destroy(keep_indicators);
+
+ return ret;
+ }
+
+ /*
+ * Return the timeout indicator
+ */
+ bool
+ get_timeout_indicator(TimeoutName tn)
+ {
+ return base_timeouts[tn].indicator;
+ }
+
+ /*
+ * Return the start of the timer for this timeout
+ */
+ TimestampTz
+ get_timeout_start(TimeoutName tn)
+ {
+ return base_timeouts[tn].start_time;
+ }
+
+ /*
+ * Signal handler for SIGALRM
+ *
+ * Process the check for the currently active timeout source and
+ * reschedule the next as needed. To avoid various edge cases,
+ * we must be careful to do nothing when there is nothing to be done.
+ */
+ void
+ handle_sig_alarm(SIGNAL_ARGS)
+ {
+ int save_errno = errno;
+
+ /*
+ * SIGALRM is cause for waking anything waiting on the process latch.
+ * Recovery (the startup process) doesn't have MyProc set so
+ * it can also use this signal handler.
+ */
+ if (MyProc)
+ SetLatch(&MyProc->procLatch);
+
+ if (n_timeouts > 0)
+ {
+ bool ret;
+ bool reschedule = true;
+
+ ret = timeouts[0]->timeout_check();
+
+ /* Shift timeouts if the timeout was triggered */
+ if (ret)
+ {
+ reschedule = timeouts[0]->resched_next;
+ /*
+ * Short circuit disable_timeout(..., true) for the
+ * timeout source that just triggered.
+ */
+ remove_timeout_index(0);
+ }
+
+ if (reschedule)
+ schedule_timeout(GetCurrentTimestamp());
+ else
+ disable_all_timeouts(true);
+ }
+
+ errno = save_errno;
+ }
diff -dcrpN postgresql.orig/src/backend/tcop/postgres.c postgresql.1/src/backend/tcop/postgres.c
*** postgresql.orig/src/backend/tcop/postgres.c 2012-04-19 11:48:04.717978193 +0200
--- postgresql.1/src/backend/tcop/postgres.c 2012-04-23 10:30:38.301732058 +0200
***************
*** 64,69 ****
--- 64,70 ----
#include "storage/proc.h"
#include "storage/procsignal.h"
#include "storage/sinval.h"
+ #include "storage/timeout.h"
#include "tcop/fastpath.h"
#include "tcop/pquery.h"
#include "tcop/tcopprot.h"
*************** start_xact_command(void)
*** 2370,2378 ****
/* Set statement timeout running, if any */
/* NB: this mustn't be enabled until we are within an xact */
if (StatementTimeout > 0)
! enable_sig_alarm(StatementTimeout, true);
else
! cancel_from_timeout = false;
xact_started = true;
}
--- 2371,2379 ----
/* Set statement timeout running, if any */
/* NB: this mustn't be enabled until we are within an xact */
if (StatementTimeout > 0)
! enable_timeout(STATEMENT_TIMEOUT, StatementTimeout);
else
! disable_timeout(STATEMENT_TIMEOUT, false);
xact_started = true;
}
*************** finish_xact_command(void)
*** 2384,2390 ****
if (xact_started)
{
/* Cancel any active statement timeout before committing */
! disable_sig_alarm(true);
/* Now commit the command */
ereport(DEBUG3,
--- 2385,2391 ----
if (xact_started)
{
/* Cancel any active statement timeout before committing */
! disable_all_timeouts(false);
/* Now commit the command */
ereport(DEBUG3,
*************** ProcessInterrupts(void)
*** 2865,2871 ****
(errcode(ERRCODE_QUERY_CANCELED),
errmsg("canceling authentication due to timeout")));
}
! if (cancel_from_timeout)
{
ImmediateInterruptOK = false; /* not idle anymore */
DisableNotifyInterrupt();
--- 2866,2872 ----
(errcode(ERRCODE_QUERY_CANCELED),
errmsg("canceling authentication due to timeout")));
}
! if (get_timeout_indicator(STATEMENT_TIMEOUT))
{
ImmediateInterruptOK = false; /* not idle anymore */
DisableNotifyInterrupt();
*************** PostgresMain(int argc, char *argv[], con
*** 3770,3779 ****
/*
* Forget any pending QueryCancel request, since we're returning to
! * the idle loop anyway, and cancel the statement timer if running.
*/
QueryCancelPending = false;
! disable_sig_alarm(true);
QueryCancelPending = false; /* again in case timeout occurred */
/*
--- 3771,3780 ----
/*
* Forget any pending QueryCancel request, since we're returning to
! * the idle loop anyway, and cancel the timer if running.
*/
QueryCancelPending = false;
! disable_all_timeouts(false);
QueryCancelPending = false; /* again in case timeout occurred */
/*
diff -dcrpN postgresql.orig/src/backend/utils/init/postinit.c postgresql.1/src/backend/utils/init/postinit.c
*** postgresql.orig/src/backend/utils/init/postinit.c 2012-04-16 19:57:22.490916093 +0200
--- postgresql.1/src/backend/utils/init/postinit.c 2012-04-23 10:30:38.302732064 +0200
***************
*** 41,51 ****
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lmgr.h"
- #include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "storage/smgr.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
#include "utils/fmgroids.h"
--- 41,51 ----
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lmgr.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "storage/smgr.h"
+ #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
#include "utils/fmgroids.h"
*************** PerformAuthentication(Port *port)
*** 204,210 ****
* during authentication. Since we're inside a transaction and might do
* database access, we have to use the statement_timeout infrastructure.
*/
! if (!enable_sig_alarm(AuthenticationTimeout * 1000, true))
elog(FATAL, "could not set timer for authorization timeout");
/*
--- 204,210 ----
* during authentication. Since we're inside a transaction and might do
* database access, we have to use the statement_timeout infrastructure.
*/
! if (!enable_timeout(STATEMENT_TIMEOUT, AuthenticationTimeout * 1000))
elog(FATAL, "could not set timer for authorization timeout");
/*
*************** PerformAuthentication(Port *port)
*** 215,221 ****
/*
* Done with authentication. Disable the timeout, and log if needed.
*/
! if (!disable_sig_alarm(true))
elog(FATAL, "could not disable timer for authorization timeout");
if (Log_connections)
--- 215,221 ----
/*
* Done with authentication. Disable the timeout, and log if needed.
*/
! if (!disable_timeout(STATEMENT_TIMEOUT, false))
elog(FATAL, "could not disable timer for authorization timeout");
if (Log_connections)
diff -dcrpN postgresql.orig/src/backend/utils/misc/guc.c postgresql.1/src/backend/utils/misc/guc.c
*** postgresql.orig/src/backend/utils/misc/guc.c 2012-04-16 19:57:22.580917138 +0200
--- postgresql.1/src/backend/utils/misc/guc.c 2012-04-23 10:30:38.305732082 +0200
***************
*** 63,68 ****
--- 63,69 ----
#include "storage/standby.h"
#include "storage/fd.h"
#include "storage/predicate.h"
+ #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "tsearch/ts_cache.h"
#include "utils/builtins.h"
diff -dcrpN postgresql.orig/src/include/storage/proc.h postgresql.1/src/include/storage/proc.h
*** postgresql.orig/src/include/storage/proc.h 2012-04-19 11:48:04.735978291 +0200
--- postgresql.1/src/include/storage/proc.h 2012-04-23 10:30:38.306732088 +0200
*************** extern PGPROC *PreparedXactProcs;
*** 215,227 ****
/* configurable options */
- extern int DeadlockTimeout;
- extern int StatementTimeout;
extern bool log_lock_waits;
- extern volatile bool cancel_from_timeout;
-
-
/*
* Function Prototypes
*/
--- 215,222 ----
*************** extern void LockErrorCleanup(void);
*** 249,261 ****
extern void ProcWaitForSignal(void);
extern void ProcSendSignal(int pid);
- extern bool enable_sig_alarm(int delayms, bool is_statement_timeout);
- extern bool disable_sig_alarm(bool is_statement_timeout);
- extern void handle_sig_alarm(SIGNAL_ARGS);
-
- extern bool enable_standby_sig_alarm(TimestampTz now,
- TimestampTz fin_time, bool deadlock_only);
- extern bool disable_standby_sig_alarm(void);
- extern void handle_standby_sig_alarm(SIGNAL_ARGS);
-
#endif /* PROC_H */
--- 244,247 ----
diff -dcrpN postgresql.orig/src/include/storage/timeout.h postgresql.1/src/include/storage/timeout.h
*** postgresql.orig/src/include/storage/timeout.h 1970-01-01 01:00:00.000000000 +0100
--- postgresql.1/src/include/storage/timeout.h 2012-04-23 10:30:38.306732088 +0200
***************
*** 0 ****
--- 1,38 ----
+ /*-------------------------------------------------------------------------
+ *
+ * timeout.h
+ * SIGALRM timeout API
+ *
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/storage/timeout.h
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef _TIMEOUT_H_
+ #define _TIMEOUT_H_
+
+ #include "datatype/timestamp.h"
+
+ /* configurable options */
+ extern int DeadlockTimeout;
+ extern int StatementTimeout;
+
+ typedef enum TimeoutName {
+ DEADLOCK_TIMEOUT,
+ STATEMENT_TIMEOUT,
+ STANDBY_DEADLOCK_TIMEOUT,
+ STANDBY_TIMEOUT,
+ TIMEOUT_MAX
+ } TimeoutName;
+
+ extern bool enable_timeout(TimeoutName tn, int delayms);
+ extern bool disable_timeout(TimeoutName tn, bool keep_indicator);
+ extern bool disable_all_timeouts(bool keep_indicators);
+ extern bool get_timeout_indicator(TimeoutName tn);
+ extern TimestampTz get_timeout_start(TimeoutName tn);
+ extern void handle_sig_alarm(SIGNAL_ARGS);
+
+ #endif /* _TIMEOUT_H_ */
2-lock_timeout-v3.patchtext/x-patch; name=2-lock_timeout-v3.patchDownload
diff -dcrpN postgresql.1/doc/src/sgml/config.sgml postgresql.2/doc/src/sgml/config.sgml
*** postgresql.1/doc/src/sgml/config.sgml 2012-04-19 11:48:04.698978089 +0200
--- postgresql.2/doc/src/sgml/config.sgml 2012-04-23 10:30:55.563836885 +0200
*************** COPY postgres_log FROM '/full/path/to/lo
*** 4957,4963 ****
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.
</para>
<para>
--- 4957,4966 ----
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. 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>
*************** COPY postgres_log FROM '/full/path/to/lo
*** 4968,4973 ****
--- 4971,5004 ----
</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 has to wait more
+ than the specified number of milliseconds, starting from the time the
+ command arrives at the server from the client. If the statement involves
+ more than one such lock, the timeout applies to every one of them.
+ 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 to acquire.
+ 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 off the limitation.
+ </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 -dcrpN 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-04-23 10:30:55.564836891 +0200
*************** LOCK [ TABLE ] [ ONLY ] <replaceable cla
*** 39,46 ****
<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.)
</para>
--- 39,49 ----
<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. 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 -dcrpN 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-04-23 10:30:55.565836897 +0200
*************** FOR SHARE [ OF <replaceable class="param
*** 1199,1204 ****
--- 1199,1211 ----
</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 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 -dcrpN postgresql.1/src/backend/port/posix_sema.c postgresql.2/src/backend/port/posix_sema.c
*** postgresql.1/src/backend/port/posix_sema.c 2012-04-16 19:57:22.438915489 +0200
--- postgresql.2/src/backend/port/posix_sema.c 2012-04-23 10:30:55.566836903 +0200
***************
*** 24,29 ****
--- 24,30 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/timeout.h"
#ifdef USE_NAMED_POSIX_SEMAPHORES
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 313,315 ****
--- 314,341 ----
return true;
}
+
+ /*
+ * PGSemaphoreTimedLock
+ *
+ * Lock a semaphore (decrement count), blocking if count would be < 0
+ * Return if lock_timeout expired
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ int errStatus;
+
+ do
+ {
+ ImmediateInterruptOK = interruptOK;
+ CHECK_FOR_INTERRUPTS();
+ errStatus = sem_wait(PG_SEM_REF(sema));
+ ImmediateInterruptOK = false;
+ } while (errStatus < 0 && errno == EINTR && !get_timeout_indicator(LOCK_TIMEOUT));
+
+ if (get_timeout_indicator(LOCK_TIMEOUT))
+ return;
+ if (errStatus < 0)
+ elog(FATAL, "sem_wait failed: %m");
+ }
diff -dcrpN postgresql.1/src/backend/port/sysv_sema.c postgresql.2/src/backend/port/sysv_sema.c
*** postgresql.1/src/backend/port/sysv_sema.c 2012-04-16 19:57:22.438915489 +0200
--- postgresql.2/src/backend/port/sysv_sema.c 2012-04-23 10:30:55.566836903 +0200
***************
*** 30,35 ****
--- 30,36 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/timeout.h"
#ifndef HAVE_UNION_SEMUN
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 495,497 ****
--- 496,528 ----
return true;
}
+
+ /*
+ * PGSemaphoreTimedLock
+ *
+ * Lock a semaphore (decrement count), blocking if count would be < 0
+ * Return if lock_timeout expired
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ int errStatus;
+ 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;
+ } while (errStatus < 0 && errno == EINTR && !get_timeout_indicator(LOCK_TIMEOUT));
+
+ if (get_timeout_indicator(LOCK_TIMEOUT))
+ return;
+ if (errStatus < 0)
+ elog(FATAL, "semop(id=%d) failed: %m", sema->semId);
+ }
diff -dcrpN postgresql.1/src/backend/port/win32_sema.c postgresql.2/src/backend/port/win32_sema.c
*** postgresql.1/src/backend/port/win32_sema.c 2012-04-16 19:57:22.439915501 +0200
--- postgresql.2/src/backend/port/win32_sema.c 2012-04-23 10:30:55.566836903 +0200
***************
*** 16,21 ****
--- 16,22 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/timeout.h"
static HANDLE *mySemSet; /* IDs of sema sets acquired so far */
static int numSems; /* number of sema sets acquired so far */
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 205,207 ****
--- 206,263 ----
/* 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.
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ DWORD ret;
+ HANDLE wh[2];
+
+ wh[0] = *sema;
+ wh[1] = pgwin32_signal_event;
+
+ /*
+ * 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();
+
+ errno = 0;
+ ret = WaitForMultipleObjectsEx(2, wh, FALSE, INFINITE, TRUE);
+
+ if (ret == WAIT_OBJECT_0)
+ {
+ /* We got it! */
+ return;
+ }
+ else if (ret == WAIT_OBJECT_0 + 1)
+ {
+ /* Signal event is set - we have a signal to deliver */
+ pgwin32_dispatch_queued_signals();
+ errno = EINTR;
+ }
+ else
+ /* Otherwise we are in trouble */
+ errno = EIDRM;
+
+ ImmediateInterruptOK = false;
+ } while (errno == EINTR && !get_timeout_indicator(LOCK_TIMEOUT));
+
+ if (get_timeout_indicator(LOCK_TIMEOUT))
+ return;
+ if (errno != 0)
+ ereport(FATAL,
+ (errmsg("could not lock semaphore: error code %d", (int) GetLastError())));
+ }
diff -dcrpN postgresql.1/src/backend/storage/lmgr/lmgr.c postgresql.2/src/backend/storage/lmgr/lmgr.c
*** postgresql.1/src/backend/storage/lmgr/lmgr.c 2012-04-16 19:57:22.459915733 +0200
--- postgresql.2/src/backend/storage/lmgr/lmgr.c 2012-04-23 10:30:55.568836915 +0200
***************
*** 19,26 ****
--- 19,29 ----
#include "access/transam.h"
#include "access/xact.h"
#include "catalog/catalog.h"
+ #include "catalog/pg_database.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
+ #include "utils/lsyscache.h"
+ #include "storage/proc.h"
#include "storage/procarray.h"
#include "utils/inval.h"
*************** LockRelationOid(Oid relid, LOCKMODE lock
*** 78,83 ****
--- 81,101 ----
res = LockAcquire(&tag, lockmode, false, false);
+ if (res == LOCKACQUIRE_NOT_AVAIL)
+ {
+ char *relname = get_rel_name(relid);
+ if (relname)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ relname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation with OID %u",
+ relid)));
+ }
+
/*
* Now that we have the lock, check for invalidation messages, so that we
* will update or flush any stale relcache entry before we try to use it.
*************** LockRelation(Relation relation, LOCKMODE
*** 174,179 ****
--- 192,203 ----
res = LockAcquire(&tag, lockmode, false, false);
+ if (res == LOCKACQUIRE_NOT_AVAIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ RelationGetRelationName(relation))));
+
/*
* Now that we have the lock, check for invalidation messages; see notes
* in LockRelationOid.
*************** LockRelationIdForSession(LockRelId *reli
*** 251,257 ****
SET_LOCKTAG_RELATION(tag, relid->dbId, relid->relId);
! (void) LockAcquire(&tag, lockmode, true, false);
}
/*
--- 275,294 ----
SET_LOCKTAG_RELATION(tag, relid->dbId, relid->relId);
! if (LockAcquire(&tag, lockmode, true, false) == LOCKACQUIRE_NOT_AVAIL)
! {
! char *relname = get_rel_name(relid->relId);
! if (relname)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on relation \"%s\"",
! relname)));
! else
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on relation with OID %u",
! relid->relId)));
! }
}
/*
*************** LockRelationForExtension(Relation relati
*** 286,292 ****
relation->rd_lockInfo.lockRelId.dbId,
relation->rd_lockInfo.lockRelId.relId);
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 323,333 ----
relation->rd_lockInfo.lockRelId.dbId,
relation->rd_lockInfo.lockRelId.relId);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on index \"%s\"",
! RelationGetRelationName(relation))));
}
/*
*************** LockPage(Relation relation, BlockNumber
*** 320,326 ****
relation->rd_lockInfo.lockRelId.relId,
blkno);
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 361,371 ----
relation->rd_lockInfo.lockRelId.relId,
blkno);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on page %u of relation \"%s\"",
! blkno, RelationGetRelationName(relation))));
}
/*
*************** LockTuple(Relation relation, ItemPointer
*** 376,382 ****
ItemPointerGetBlockNumber(tid),
ItemPointerGetOffsetNumber(tid));
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 421,431 ----
ItemPointerGetBlockNumber(tid),
ItemPointerGetOffsetNumber(tid));
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on row in relation \"%s\"",
! RelationGetRelationName(relation))));
}
/*
*************** XactLockTableInsert(TransactionId xid)
*** 430,436 ****
SET_LOCKTAG_TRANSACTION(tag, xid);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
}
/*
--- 479,488 ----
SET_LOCKTAG_TRANSACTION(tag, xid);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on transaction with ID %u", xid)));
}
/*
*************** XactLockTableWait(TransactionId xid)
*** 474,480 ****
SET_LOCKTAG_TRANSACTION(tag, xid);
! (void) LockAcquire(&tag, ShareLock, false, false);
LockRelease(&tag, ShareLock, false);
--- 526,535 ----
SET_LOCKTAG_TRANSACTION(tag, xid);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on transaction with ID %u", xid)));
LockRelease(&tag, ShareLock, false);
*************** LockDatabaseObject(Oid classid, Oid obji
*** 535,541 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, false, false);
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
--- 590,600 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
*************** LockSharedObject(Oid classid, Oid objid,
*** 576,582 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, false, false);
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
--- 635,645 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
*************** LockSharedObjectForSession(Oid classid,
*** 618,624 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, true, false);
}
/*
--- 681,702 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, true, false) == LOCKACQUIRE_NOT_AVAIL)
! switch(classid)
! {
! case DatabaseRelationId:
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on database with ID %u",
! objid)));
! break;
! default:
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
! break;
! }
}
/*
diff -dcrpN postgresql.1/src/backend/storage/lmgr/lock.c postgresql.2/src/backend/storage/lmgr/lock.c
*** postgresql.1/src/backend/storage/lmgr/lock.c 2012-04-19 11:48:04.716978188 +0200
--- postgresql.2/src/backend/storage/lmgr/lock.c 2012-04-23 10:40:29.622300613 +0200
*************** static PROCLOCK *SetupLockInTable(LockMe
*** 341,347 ****
static void GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner);
static void BeginStrongLockAcquire(LOCALLOCK *locallock, uint32 fasthashcode);
static void FinishStrongLockAcquire(void);
! static void WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner);
static void ReleaseLockForOwner(LOCALLOCK *locallock, ResourceOwner owner);
static bool UnGrantLock(LOCK *lock, LOCKMODE lockmode,
PROCLOCK *proclock, LockMethod lockMethodTable);
--- 341,347 ----
static void GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner);
static void BeginStrongLockAcquire(LOCALLOCK *locallock, uint32 fasthashcode);
static void FinishStrongLockAcquire(void);
! static int WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner);
static void ReleaseLockForOwner(LOCALLOCK *locallock, ResourceOwner owner);
static bool UnGrantLock(LOCK *lock, LOCKMODE lockmode,
PROCLOCK *proclock, LockMethod lockMethodTable);
*************** ProcLockHashCode(const PROCLOCKTAG *proc
*** 547,553 ****
* dontWait: if true, don't wait to acquire lock
*
* Returns one of:
! * LOCKACQUIRE_NOT_AVAIL lock not available, and dontWait=true
* LOCKACQUIRE_OK lock successfully acquired
* LOCKACQUIRE_ALREADY_HELD incremented count for lock already held
*
--- 547,553 ----
* dontWait: if true, don't wait to acquire lock
*
* Returns one of:
! * LOCKACQUIRE_NOT_AVAIL lock not available, either dontWait=true or timeout
* LOCKACQUIRE_OK lock successfully acquired
* LOCKACQUIRE_ALREADY_HELD incremented count for lock already held
*
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 857,863 ****
locktag->locktag_type,
lockmode);
! WaitOnLock(locallock, owner);
TRACE_POSTGRESQL_LOCK_WAIT_DONE(locktag->locktag_field1,
locktag->locktag_field2,
--- 857,863 ----
locktag->locktag_type,
lockmode);
! status = WaitOnLock(locallock, owner);
TRACE_POSTGRESQL_LOCK_WAIT_DONE(locktag->locktag_field1,
locktag->locktag_field2,
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 872,899 ****
* done when the lock was granted to us --- see notes in WaitOnLock.
*/
! /*
! * Check the proclock entry status, in case something in the ipc
! * communication doesn't work correctly.
! */
! if (!(proclock->holdMask & LOCKBIT_ON(lockmode)))
{
! AbortStrongLockAcquire();
! PROCLOCK_PRINT("LockAcquire: INCONSISTENT", proclock);
! LOCK_PRINT("LockAcquire: INCONSISTENT", lock, lockmode);
! /* Should we retry ? */
! LWLockRelease(partitionLock);
! elog(ERROR, "LockAcquire failed");
}
- PROCLOCK_PRINT("LockAcquire: granted", proclock);
- LOCK_PRINT("LockAcquire: granted", lock, lockmode);
}
! /*
! * Lock state is fully up-to-date now; if we error out after this, no
! * special error cleanup is required.
! */
! FinishStrongLockAcquire();
LWLockRelease(partitionLock);
--- 872,922 ----
* done when the lock was granted to us --- see notes in WaitOnLock.
*/
! switch (status)
{
! case STATUS_OK:
! /*
! * Check the proclock entry status, in case something in the ipc
! * communication doesn't work correctly.
! */
! if (!(proclock->holdMask & LOCKBIT_ON(lockmode)))
! {
! AbortStrongLockAcquire();
! PROCLOCK_PRINT("LockAcquire: INCONSISTENT", proclock);
! LOCK_PRINT("LockAcquire: INCONSISTENT", lock, lockmode);
! /* Should we retry ? */
! LWLockRelease(partitionLock);
! elog(ERROR, "LockAcquire failed");
! }
! PROCLOCK_PRINT("LockAcquire: granted", proclock);
! LOCK_PRINT("LockAcquire: granted", lock, lockmode);
! break;
! case STATUS_WAITING:
! PROCLOCK_PRINT("LockAcquire: timed out", proclock);
! LOCK_PRINT("LockAcquire: timed out", lock, lockmode);
! break;
! default:
! elog(ERROR, "LockAcquire invalid status");
! break;
}
}
! if (status == STATUS_WAITING)
! {
! /*
! * lock_timeout was set and WaitOnLock() indicated
! * we timed out. Clean up manually.
! */
! AbortStrongLockAcquire();
! }
! else
! {
! /*
! * Lock state is fully up-to-date now; if we error out after this, no
! * special error cleanup is required.
! */
! FinishStrongLockAcquire();
! }
LWLockRelease(partitionLock);
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 912,918 ****
locktag->locktag_field2);
}
! return LOCKACQUIRE_OK;
}
/*
--- 935,941 ----
locktag->locktag_field2);
}
! return (status == STATUS_OK ? LOCKACQUIRE_OK : LOCKACQUIRE_NOT_AVAIL);
}
/*
*************** GrantAwaitedLock(void)
*** 1430,1443 ****
* Caller must have set MyProc->heldLocks to reflect locks already held
* on the lockable object by this process.
*
* The appropriate partition lock must be held at entry.
*/
! static void
WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner)
{
LOCKMETHODID lockmethodid = LOCALLOCK_LOCKMETHOD(*locallock);
LockMethod lockMethodTable = LockMethods[lockmethodid];
char *volatile new_status = NULL;
LOCK_PRINT("WaitOnLock: sleeping on lock",
locallock->lock, locallock->tag.mode);
--- 1453,1472 ----
* Caller must have set MyProc->heldLocks to reflect locks already held
* on the lockable object by this process.
*
+ * Result: returns value of ProcSleep()
+ * STATUS_OK if we acquired the lock
+ * STATUS_ERROR if not (deadlock)
+ * STATUS_WAITING if not (timeout)
+ *
* The appropriate partition lock must be held at entry.
*/
! static int
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);
*************** WaitOnLock(LOCALLOCK *locallock, Resourc
*** 1479,1486 ****
*/
PG_TRY();
{
! if (ProcSleep(locallock, lockMethodTable) != STATUS_OK)
{
/*
* We failed as a result of a deadlock, see CheckDeadLock(). Quit
* now.
--- 1508,1520 ----
*/
PG_TRY();
{
! wait_status = ProcSleep(locallock, lockMethodTable);
! switch (wait_status)
{
+ case STATUS_OK:
+ case STATUS_WAITING:
+ break;
+ default:
/*
* We failed as a result of a deadlock, see CheckDeadLock(). Quit
* now.
*************** WaitOnLock(LOCALLOCK *locallock, Resourc
*** 1525,1532 ****
pfree(new_status);
}
! LOCK_PRINT("WaitOnLock: wakeup on lock",
locallock->lock, locallock->tag.mode);
}
/*
--- 1559,1572 ----
pfree(new_status);
}
! 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);
+
+ return wait_status;
}
/*
*************** VirtualXactLock(VirtualTransactionId vxi
*** 3798,3804 ****
LWLockRelease(proc->backendLock);
/* Time to wait. */
! (void) LockAcquire(&tag, ShareLock, false, false);
LockRelease(&tag, ShareLock, false);
return true;
--- 3838,3848 ----
LWLockRelease(proc->backendLock);
/* Time to wait. */
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on virtual transaction with ID %u",
! vxid.localTransactionId)));
LockRelease(&tag, ShareLock, false);
return true;
diff -dcrpN 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-04-23 10:30:38.297732034 +0200
--- postgresql.2/src/backend/storage/lmgr/proc.c 2012-04-23 10:30:55.572836939 +0200
*************** LockErrorCleanup(void)
*** 637,644 ****
if (lockAwaited == NULL)
return;
! /* Turn off the deadlock timer, if it's still running (see ProcSleep) */
disable_timeout(DEADLOCK_TIMEOUT, false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
--- 637,648 ----
if (lockAwaited == NULL)
return;
! /*
! * Turn off the deadlock and lock timeout timers,
! * if they are still running (see ProcSleep)
! */
disable_timeout(DEADLOCK_TIMEOUT, false);
+ disable_timeout(LOCK_TIMEOUT, false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
*************** ProcQueueInit(PROC_QUEUE *queue)
*** 872,878 ****
* 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).
*
* ASSUME: that no one will fiddle with the queue until after
* we release the partition lock.
--- 876,885 ----
* 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)
! * STATUS_WAITING if not (timeout)
*
* ASSUME: that no one will fiddle with the queue until after
* we release the partition lock.
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 894,899 ****
--- 901,907 ----
LOCKMASK myHeldLocks = MyProc->heldLocks;
bool early_deadlock = false;
bool allow_autovacuum_cancel = true;
+ bool timeout_detected = false;
int myWaitStatus;
PGPROC *proc;
int i;
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1035,1042 ****
elog(FATAL, "could not set timer for process wakeup");
/*
! * 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
* saved wakeup might be leftover from a previous operation (for example,
* we aborted ProcWaitForSignal just before someone did ProcSendSignal).
--- 1043,1056 ----
elog(FATAL, "could not set timer for process wakeup");
/*
! * Queue the timer for lock timeout, too.
! */
! if (!enable_timeout(LOCK_TIMEOUT, LockTimeout))
! elog(FATAL, "could not set timer for process wakeup");
!
! /*
! * 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).
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1053,1059 ****
*/
do
{
! PGSemaphoreLock(&MyProc->sem, true);
/*
* waitStatus could change from STATUS_WAITING to something else
--- 1067,1078 ----
*/
do
{
! PGSemaphoreTimedLock(&MyProc->sem, true);
!
! /* Check and keep the lock timeout indicator for later checks */
! timeout_detected = get_timeout_indicator(LOCK_TIMEOUT);
! if (timeout_detected)
! break;
/*
* waitStatus could change from STATUS_WAITING to something else
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1182,1193 ****
} while (myWaitStatus == STATUS_WAITING);
/*
! * Disable the timer, if it's still running
*/
if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for process wakeup");
/*
* Re-acquire the lock table's partition lock. We have to do this to hold
* off cancel/die interrupts before we can mess with lockAwaited (else we
* might have a missed or duplicated locallock update).
--- 1201,1218 ----
} while (myWaitStatus == STATUS_WAITING);
/*
! * Disable the deadlock timer, if it's still running
*/
if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for process wakeup");
/*
+ * Disable the lock timeout timer, if it's still running
+ */
+ if (!disable_timeout(LOCK_TIMEOUT, false))
+ elog(FATAL, "could not disable timer for process wakeup");
+
+ /*
* Re-acquire the lock table's partition lock. We have to do this to hold
* off cancel/die interrupts before we can mess with lockAwaited (else we
* might have a missed or duplicated locallock update).
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1195,1200 ****
--- 1220,1234 ----
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;
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1208,1215 ****
/*
* We don't have to do anything else, because the awaker did all the
* necessary update of the lock table and MyProc.
*/
! return MyProc->waitStatus;
}
--- 1242,1251 ----
/*
* 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 (timeout_detected ? STATUS_WAITING : MyProc->waitStatus);
}
diff -dcrpN postgresql.1/src/backend/storage/lmgr/timeout.c postgresql.2/src/backend/storage/lmgr/timeout.c
*** postgresql.1/src/backend/storage/lmgr/timeout.c 2012-04-23 10:30:38.299732046 +0200
--- postgresql.2/src/backend/storage/lmgr/timeout.c 2012-04-23 10:30:55.573836945 +0200
***************
*** 34,39 ****
--- 34,40 ----
/* GUC variables */
int DeadlockTimeout = 1000;
int StatementTimeout = 0;
+ int LockTimeout = 0;
/*
* This is used by ProcSleep() in proc.c
*************** static void InitDeadLock(TimestampTz sta
*** 50,55 ****
--- 51,60 ----
static void DestroyDeadLock(bool keep_indicator);
static bool CheckDeadLock(void);
+ static void InitLockTimeout(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyLockTimeout(bool keep_indicator);
+ static bool CheckLockTimeout(void);
+
static void InitStatementTimeout(TimestampTz start_time, TimestampTz fin_time);
static void DestroyStatementTimeout(bool keep_indicator);
static bool CheckStatementTimeout(void);
*************** static timeout_params base_timeouts[TIME
*** 98,103 ****
--- 103,115 ----
},
{
+ LOCK_TIMEOUT, false, false,
+ InitLockTimeout, DestroyLockTimeout,
+ CheckLockTimeout, GetCurrentTimestamp,
+ 0
+ },
+
+ {
STATEMENT_TIMEOUT, false, false,
InitStatementTimeout, DestroyStatementTimeout,
CheckStatementTimeout, GetCurrentStatementStartTimestamp,
*************** check_done:
*** 380,385 ****
--- 392,427 ----
return true;
}
+ /*
+ * Functions to manage lock timeout
+ */
+
+ static void
+ InitLockTimeout(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(LOCK_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyLockTimeout(bool keep_indicator)
+ {
+ DestroyTimeout(LOCK_TIMEOUT, keep_indicator);
+ }
+
+ static bool
+ CheckLockTimeout(void)
+ {
+ TimestampTz now;
+
+ now = GetCurrentTimestamp();
+
+ if (now < base_timeouts[LOCK_TIMEOUT].fin_time)
+ return false;
+
+ base_timeouts[LOCK_TIMEOUT].indicator = true;
+ return true;
+ }
+
/*
* Functions to manage statement timeout
*/
diff -dcrpN postgresql.1/src/backend/utils/adt/lockfuncs.c postgresql.2/src/backend/utils/adt/lockfuncs.c
*** postgresql.1/src/backend/utils/adt/lockfuncs.c 2012-04-16 19:57:22.473915895 +0200
--- postgresql.2/src/backend/utils/adt/lockfuncs.c 2012-04-23 10:30:55.574836952 +0200
*************** pg_advisory_lock_int8(PG_FUNCTION_ARGS)
*** 421,427 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ExclusiveLock, true, false);
PG_RETURN_VOID();
}
--- 421,431 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ExclusiveLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_int8(PG_FUNCTION_A
*** 438,444 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
PG_RETURN_VOID();
}
--- 442,452 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_shared_int8(PG_FUNCTION
*** 454,460 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ShareLock, true, false);
PG_RETURN_VOID();
}
--- 462,472 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ShareLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_shared_int8(PG_FUN
*** 471,477 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ShareLock, false, false);
PG_RETURN_VOID();
}
--- 483,493 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_int4(PG_FUNCTION_ARGS)
*** 604,610 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ExclusiveLock, true, false);
PG_RETURN_VOID();
}
--- 620,630 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ExclusiveLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_int4(PG_FUNCTION_A
*** 622,628 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
PG_RETURN_VOID();
}
--- 642,652 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_shared_int4(PG_FUNCTION
*** 639,645 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ShareLock, true, false);
PG_RETURN_VOID();
}
--- 663,673 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ShareLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_shared_int4(PG_FUN
*** 657,663 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ShareLock, false, false);
PG_RETURN_VOID();
}
--- 685,695 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
diff -dcrpN 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-04-23 10:30:38.305732082 +0200
--- postgresql.2/src/backend/utils/misc/guc.c 2012-04-23 10:30:55.576836964 +0200
*************** static struct config_int ConfigureNamesI
*** 1861,1866 ****
--- 1861,1877 ----
},
{
+ {"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 -dcrpN 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-04-16 19:57:22.580917138 +0200
--- postgresql.2/src/backend/utils/misc/postgresql.conf.sample 2012-04-23 10:30:55.577836970 +0200
***************
*** 528,533 ****
--- 528,536 ----
#------------------------------------------------------------------------------
#deadlock_timeout = 1s
+ #lock_timeout = 0 # timeout value for heavy-weight locks
+ # taken by statements. 0 disables timeout
+ # unit in milliseconds, default is 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 -dcrpN postgresql.1/src/include/storage/pg_sema.h postgresql.2/src/include/storage/pg_sema.h
*** postgresql.1/src/include/storage/pg_sema.h 2012-04-16 19:57:22.672918205 +0200
--- postgresql.2/src/include/storage/pg_sema.h 2012-04-23 10:30:55.577836970 +0200
*************** extern void PGSemaphoreUnlock(PGSemaphor
*** 80,83 ****
--- 80,89 ----
/* 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 void PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK);
+
#endif /* PG_SEMA_H */
diff -dcrpN postgresql.1/src/include/storage/timeout.h postgresql.2/src/include/storage/timeout.h
*** postgresql.1/src/include/storage/timeout.h 2012-04-23 10:30:38.306732088 +0200
--- postgresql.2/src/include/storage/timeout.h 2012-04-23 10:30:55.577836970 +0200
***************
*** 19,27 ****
--- 19,29 ----
/* configurable options */
extern int DeadlockTimeout;
extern int StatementTimeout;
+ extern int LockTimeout;
typedef enum TimeoutName {
DEADLOCK_TIMEOUT,
+ LOCK_TIMEOUT,
STATEMENT_TIMEOUT,
STANDBY_DEADLOCK_TIMEOUT,
STANDBY_TIMEOUT,
diff -dcrpN 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-04-23 10:30:55.577836970 +0200
*************** set statement_timeout to 2000;
*** 198,203 ****
--- 198,210 ----
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: could not obtain lock on relation "pxtest3"
+ LINE 1: SELECT * FROM pxtest3;
+ ^
+ reset lock_timeout;
-- Disconnect, we will continue testing in a different backend
\c -
-- There should still be two prepared transactions
*************** set statement_timeout to 2000;
*** 213,218 ****
--- 220,232 ----
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: could not obtain lock on relation "pxtest3"
+ LINE 1: SELECT * FROM pxtest3;
+ ^
+ reset lock_timeout;
-- Commit table creation
COMMIT PREPARED 'regress-one';
\d pxtest2
diff -dcrpN 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-04-23 10:30:55.578836976 +0200
*************** set statement_timeout to 2000;
*** 126,131 ****
--- 126,136 ----
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 -
*************** set statement_timeout to 2000;
*** 137,142 ****
--- 142,152 ----
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
On Mon, 2012-04-23 at 10:53 +0200, Boszormenyi Zoltan wrote:
2012-04-10 09:02 keltezéssel, Boszormenyi Zoltan írta:
2012-04-06 14:47 keltezéssel, Cousin Marc írta:
On 05/04/12 08:02, Boszormenyi Zoltan wrote:
2012-04-04 21:30 keltezéssel, Alvaro Herrera írta:
I think this patch is doing two things: first touching infrastructure
stuff and then adding lock_timeout on top of that. Would it work to
split the patch in two pieces?Sure. Attached is the split version.
Best regards,
Zoltán BöszörményiHi,
I've started looking at and testing both patches.
Technically speaking, I think the source looks much better than the
first version of lock timeout, and may help adding other timeouts in the
future. I haven't tested it in depth though, because I encountered the
following problem:While testing the patch, I found a way to crash PG. But what's weird is
that it crashes also with an unpatched git version.Here is the way to reproduce it (I have done it with a pgbench schema):
- Set a small statement_timeout (just to save time during the tests)
Session1:
=#BEGIN;
=#lock TABLE pgbench_accounts ;Session 2:
=#BEGIN;
=#lock TABLE pgbench_accounts ;
ERROR: canceling statement due to statement timeout
=# lock TABLE pgbench_accounts ;I'm using \set ON_ERROR_ROLLBACK INTERACTIVE by the way. It can also be
done with a rollback to savepoint of course.Session 2 crashes with this : TRAP : FailedAssertion(«
!(locallock->holdsStrongLockCount == 0) », fichier : « lock.c », ligne :
749).It can also be done without a statement_timeout, and a control-C on the
second lock table.I didn't touch anything but this. It occurs everytime, when asserts are
activated.I tried it on 9.1.3, and I couldn't make it crash with the same sequence
of events. So maybe it's something introduced since ? Or is the assert
still valid ?Cheers
Attached are the new patches. I rebased them to current GIT and
they are expected to be applied after Robert Haas' patch in the
"bug in fast-path locking" thread.Now it survives the above scenario.
Best regards,
Zoltán BöszörményiNew patch attached, rebased to today's GIT.
Best regards,
Zoltán Böszörményi
Ok, I've done what was missing from the review (from when I had a bug in
locking the other day), so here is the full review. By the way, this
patch doesn't belong to current commitfest, but to the next one.
Is the patch in context diff format?
Yes
Does it apply cleanly to the current git master?
Yes
Does it include reasonable tests, necessary doc patches, etc?
The new lock_timeout GUC is documented. There are regression tests.
Read what the patch is supposed to do, and consider:
Does the patch actually implement that?
Yes
Do we want that?
I do. Mostly for administrative jobs which could lock the whole
application. It would be much easier to run reindexes, vacuum full, etc…
without worrying about bringing application down because of lock
contention.
Do we already have it?
No.
Does it follow SQL spec, or the community-agreed behavior?
I don't know if there is a consensus on this new GUC. statement_timeout
is obviously not in the SQL spec.
Does it include pg_dump support (if applicable)?
Not applicable
Are there dangers?
Yes, as it rewrites all the timeout code. I feel it is much cleaner this
way, as there is a generic set of function managing all sigalarm code,
but it heavily touches this part.
Have all the bases been covered?
I tried all sql statements I could think of (select, insert, update,
delete, truncate, drop, create index, adding constraint, lock.
I tried having statement_timeout, lock_timeout and deadlock_timeout at
very short and close or equal values. It worked too.
Rollback to savepoint while holding locks dont crash PostgreSQL anymore.
Other timeouts such as archive_timeout and checkpoint_timeout still
work.
Does the feature work as advertised?
Yes
Are there corner cases the author has failed to consider?
I didn't find any.
Are there any assertion failures or crashes?
No.
Does the patch slow down simple tests?
No
If it claims to improve performance, does it?
Not applicable
Does it slow down other things?
No
Does it follow the project coding guidelines?
I think so
Are there portability issues?
No, all the portable code (acquiring locks and manipulating sigalarm) is
the same as before.
Will it work on Windows/BSD etc?
It should. I couldn't test it though.
Are the comments sufficient and accurate?
Yes
Does it do what it says, correctly?
Yes
Does it produce compiler warnings?
No
Can you make it crash?
Not anymore
Is everything done in a way that fits together coherently with other
features/modules?
Yes, I think so. The new way of handling sigalarm seems more robust to
me.
Are there interdependencies that can cause problems?
I don't see any.
Regards,
Marc
2012-04-23 15:08 keltezéssel, Marc Cousin írta:
On Mon, 2012-04-23 at 10:53 +0200, Boszormenyi Zoltan wrote:
2012-04-10 09:02 keltezéssel, Boszormenyi Zoltan írta:
2012-04-06 14:47 keltezéssel, Cousin Marc írta:
On 05/04/12 08:02, Boszormenyi Zoltan wrote:
2012-04-04 21:30 keltezéssel, Alvaro Herrera írta:
I think this patch is doing two things: first touching infrastructure
stuff and then adding lock_timeout on top of that. Would it work to
split the patch in two pieces?Sure. Attached is the split version.
Best regards,
Zoltán BöszörményiHi,
I've started looking at and testing both patches.
Technically speaking, I think the source looks much better than the
first version of lock timeout, and may help adding other timeouts in the
future. I haven't tested it in depth though, because I encountered the
following problem:While testing the patch, I found a way to crash PG. But what's weird is
that it crashes also with an unpatched git version.Here is the way to reproduce it (I have done it with a pgbench schema):
- Set a small statement_timeout (just to save time during the tests)
Session1:
=#BEGIN;
=#lock TABLE pgbench_accounts ;Session 2:
=#BEGIN;
=#lock TABLE pgbench_accounts ;
ERROR: canceling statement due to statement timeout
=# lock TABLE pgbench_accounts ;I'm using \set ON_ERROR_ROLLBACK INTERACTIVE by the way. It can also be
done with a rollback to savepoint of course.Session 2 crashes with this : TRAP : FailedAssertion(«
!(locallock->holdsStrongLockCount == 0) », fichier : « lock.c », ligne :
749).It can also be done without a statement_timeout, and a control-C on the
second lock table.I didn't touch anything but this. It occurs everytime, when asserts are
activated.I tried it on 9.1.3, and I couldn't make it crash with the same sequence
of events. So maybe it's something introduced since ? Or is the assert
still valid ?Cheers
Attached are the new patches. I rebased them to current GIT and
they are expected to be applied after Robert Haas' patch in the
"bug in fast-path locking" thread.Now it survives the above scenario.
Best regards,
Zoltán BöszörményiNew patch attached, rebased to today's GIT.
Best regards,
Zoltán BöszörményiOk, I've done what was missing from the review (from when I had a bug in
locking the other day), so here is the full review. By the way, this
patch doesn't belong to current commitfest, but to the next one.
It was added to 2012-Next when I posted it, 2012-01 was already
closed for new additions.
Is the patch in context diff format?
YesDoes it apply cleanly to the current git master?
YesDoes it include reasonable tests, necessary doc patches, etc?
The new lock_timeout GUC is documented. There are regression tests.Read what the patch is supposed to do, and consider:
Does the patch actually implement that?
YesDo we want that?
I do. Mostly for administrative jobs which could lock the whole
application. It would be much easier to run reindexes, vacuum full, etc…
without worrying about bringing application down because of lock
contention.Do we already have it?
No.Does it follow SQL spec, or the community-agreed behavior?
I don't know if there is a consensus on this new GUC. statement_timeout
is obviously not in the SQL spec.Does it include pg_dump support (if applicable)?
Not applicableAre there dangers?
Yes, as it rewrites all the timeout code. I feel it is much cleaner this
way, as there is a generic set of function managing all sigalarm code,
but it heavily touches this part.Have all the bases been covered?
I tried all sql statements I could think of (select, insert, update,
delete, truncate, drop, create index, adding constraint, lock.I tried having statement_timeout, lock_timeout and deadlock_timeout at
very short and close or equal values. It worked too.Rollback to savepoint while holding locks dont crash PostgreSQL anymore.
Other timeouts such as archive_timeout and checkpoint_timeout still
work.Does the feature work as advertised?
YesAre there corner cases the author has failed to consider?
I didn't find any.Are there any assertion failures or crashes?
No.Does the patch slow down simple tests?
NoIf it claims to improve performance, does it?
Not applicableDoes it slow down other things?
NoDoes it follow the project coding guidelines?
I think soAre there portability issues?
No, all the portable code (acquiring locks and manipulating sigalarm) is
the same as before.Will it work on Windows/BSD etc?
It should. I couldn't test it though.Are the comments sufficient and accurate?
YesDoes it do what it says, correctly?
YesDoes it produce compiler warnings?
NoCan you make it crash?
Not anymoreIs everything done in a way that fits together coherently with other
features/modules?
Yes, I think so. The new way of handling sigalarm seems more robust to
me.Are there interdependencies that can cause problems?
I don't see any.Regards,
Marc
Thanks for the review.
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/
Hi,
2012-04-24 09:00 keltezéssel, Boszormenyi Zoltan írta:
2012-04-23 15:08 keltezéssel, Marc Cousin írta:
On Mon, 2012-04-23 at 10:53 +0200, Boszormenyi Zoltan wrote:
2012-04-10 09:02 keltezéssel, Boszormenyi Zoltan írta:
2012-04-06 14:47 keltezéssel, Cousin Marc írta:
On 05/04/12 08:02, Boszormenyi Zoltan wrote:
2012-04-04 21:30 keltezéssel, Alvaro Herrera írta:
I think this patch is doing two things: first touching infrastructure
stuff and then adding lock_timeout on top of that. Would it work to
split the patch in two pieces?Sure. Attached is the split version.
Best regards,
Zoltán BöszörményiHi,
I've started looking at and testing both patches.
Technically speaking, I think the source looks much better than the
first version of lock timeout, and may help adding other timeouts in the
future. I haven't tested it in depth though, because I encountered the
following problem:While testing the patch, I found a way to crash PG. But what's weird is
that it crashes also with an unpatched git version.Here is the way to reproduce it (I have done it with a pgbench schema):
- Set a small statement_timeout (just to save time during the tests)
Session1:
=#BEGIN;
=#lock TABLE pgbench_accounts ;Session 2:
=#BEGIN;
=#lock TABLE pgbench_accounts ;
ERROR: canceling statement due to statement timeout
=# lock TABLE pgbench_accounts ;I'm using \set ON_ERROR_ROLLBACK INTERACTIVE by the way. It can also be
done with a rollback to savepoint of course.Session 2 crashes with this : TRAP : FailedAssertion(«
!(locallock->holdsStrongLockCount == 0) », fichier : « lock.c », ligne :
749).It can also be done without a statement_timeout, and a control-C on the
second lock table.I didn't touch anything but this. It occurs everytime, when asserts are
activated.I tried it on 9.1.3, and I couldn't make it crash with the same sequence
of events. So maybe it's something introduced since ? Or is the assert
still valid ?Cheers
Attached are the new patches. I rebased them to current GIT and
they are expected to be applied after Robert Haas' patch in the
"bug in fast-path locking" thread.Now it survives the above scenario.
Best regards,
Zoltán BöszörményiNew patch attached, rebased to today's GIT.
Best regards,
Zoltán BöszörményiOk, I've done what was missing from the review (from when I had a bug in
locking the other day), so here is the full review. By the way, this
patch doesn't belong to current commitfest, but to the next one.It was added to 2012-Next when I posted it, 2012-01 was already
closed for new additions.Is the patch in context diff format?
YesDoes it apply cleanly to the current git master?
YesDoes it include reasonable tests, necessary doc patches, etc?
The new lock_timeout GUC is documented. There are regression tests.Read what the patch is supposed to do, and consider:
Does the patch actually implement that?
YesDo we want that?
I do. Mostly for administrative jobs which could lock the whole
application. It would be much easier to run reindexes, vacuum full, etc…
without worrying about bringing application down because of lock
contention.Do we already have it?
No.Does it follow SQL spec, or the community-agreed behavior?
I don't know if there is a consensus on this new GUC. statement_timeout
is obviously not in the SQL spec.Does it include pg_dump support (if applicable)?
Not applicableAre there dangers?
Yes, as it rewrites all the timeout code. I feel it is much cleaner this
way, as there is a generic set of function managing all sigalarm code,
but it heavily touches this part.Have all the bases been covered?
I tried all sql statements I could think of (select, insert, update,
delete, truncate, drop, create index, adding constraint, lock.I tried having statement_timeout, lock_timeout and deadlock_timeout at
very short and close or equal values. It worked too.Rollback to savepoint while holding locks dont crash PostgreSQL anymore.
Other timeouts such as archive_timeout and checkpoint_timeout still
work.Does the feature work as advertised?
YesAre there corner cases the author has failed to consider?
I didn't find any.Are there any assertion failures or crashes?
No.Does the patch slow down simple tests?
NoIf it claims to improve performance, does it?
Not applicableDoes it slow down other things?
NoDoes it follow the project coding guidelines?
I think soAre there portability issues?
No, all the portable code (acquiring locks and manipulating sigalarm) is
the same as before.Will it work on Windows/BSD etc?
It should. I couldn't test it though.Are the comments sufficient and accurate?
YesDoes it do what it says, correctly?
YesDoes it produce compiler warnings?
NoCan you make it crash?
Not anymoreIs everything done in a way that fits together coherently with other
features/modules?
Yes, I think so. The new way of handling sigalarm seems more robust to
me.Are there interdependencies that can cause problems?
I don't see any.Regards,
Marc
Thanks for the review.
Best regards,
Zoltán Böszörményi
New patches are attached, rebased to current GIT to avoid
a (trivial) reject. No other changes.
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/
Attachments:
1-timeout-framework-v4.patchtext/x-patch; name=1-timeout-framework-v4.patchDownload
diff -dcrpN postgresql/src/backend/postmaster/autovacuum.c postgresql.1/src/backend/postmaster/autovacuum.c
*** postgresql/src/backend/postmaster/autovacuum.c 2012-04-16 19:57:22.440915512 +0200
--- postgresql.1/src/backend/postmaster/autovacuum.c 2012-05-07 19:39:07.961976453 +0200
***************
*** 86,94 ****
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
- #include "storage/proc.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "tcop/tcopprot.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
--- 86,94 ----
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
+ #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
*************** AutoVacLauncherMain(int argc, char *argv
*** 484,490 ****
/* Forget any pending QueryCancel request */
QueryCancelPending = false;
! disable_sig_alarm(true);
QueryCancelPending = false; /* again in case timeout occurred */
/* Report the error to the server log */
--- 484,490 ----
/* Forget any pending QueryCancel request */
QueryCancelPending = false;
! disable_all_timeouts(false);
QueryCancelPending = false; /* again in case timeout occurred */
/* Report the error to the server log */
diff -dcrpN postgresql/src/backend/postmaster/postmaster.c postgresql.1/src/backend/postmaster/postmaster.c
*** postgresql/src/backend/postmaster/postmaster.c 2012-04-23 08:28:35.641057679 +0200
--- postgresql.1/src/backend/postmaster/postmaster.c 2012-05-07 19:39:07.962976459 +0200
***************
*** 112,118 ****
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
! #include "storage/proc.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/datetime.h"
--- 112,118 ----
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
! #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/datetime.h"
*************** BackendInitialize(Port *port)
*** 3471,3479 ****
* Ready to begin client interaction. We will give up and exit(1) after a
* time delay, so that a broken client can't hog a connection
* indefinitely. PreAuthDelay and any DNS interactions above don't count
! * against the time limit.
*/
! if (!enable_sig_alarm(AuthenticationTimeout * 1000, false))
elog(FATAL, "could not set timer for startup packet timeout");
/*
--- 3471,3479 ----
* Ready to begin client interaction. We will give up and exit(1) after a
* time delay, so that a broken client can't hog a connection
* indefinitely. PreAuthDelay and any DNS interactions above don't count
! * against the time limit. Use the deadlock timeout interface.
*/
! if (!enable_timeout(DEADLOCK_TIMEOUT, AuthenticationTimeout * 1000))
elog(FATAL, "could not set timer for startup packet timeout");
/*
*************** BackendInitialize(Port *port)
*** 3511,3517 ****
/*
* Disable the timeout, and prevent SIGTERM/SIGQUIT again.
*/
! if (!disable_sig_alarm(false))
elog(FATAL, "could not disable timer for startup packet timeout");
PG_SETMASK(&BlockSig);
}
--- 3511,3517 ----
/*
* Disable the timeout, and prevent SIGTERM/SIGQUIT again.
*/
! if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for startup packet timeout");
PG_SETMASK(&BlockSig);
}
diff -dcrpN postgresql/src/backend/postmaster/startup.c postgresql.1/src/backend/postmaster/startup.c
*** postgresql/src/backend/postmaster/startup.c 2012-04-16 19:57:22.442915536 +0200
--- postgresql.1/src/backend/postmaster/startup.c 2012-05-07 19:39:07.964976468 +0200
***************
*** 27,33 ****
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
! #include "storage/proc.h"
#include "utils/guc.h"
--- 27,33 ----
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
! #include "storage/timeout.h"
#include "utils/guc.h"
*************** StartupProcessMain(void)
*** 195,202 ****
pqsignal(SIGTERM, StartupProcShutdownHandler); /* request shutdown */
pqsignal(SIGQUIT, startupproc_quickdie); /* hard crash time */
if (EnableHotStandby)
! pqsignal(SIGALRM, handle_standby_sig_alarm); /* ignored unless
! * InHotStandby */
else
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
--- 195,202 ----
pqsignal(SIGTERM, StartupProcShutdownHandler); /* request shutdown */
pqsignal(SIGQUIT, startupproc_quickdie); /* hard crash time */
if (EnableHotStandby)
! pqsignal(SIGALRM, handle_sig_alarm); /* ignored unless
! * InHotStandby */
else
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
diff -dcrpN postgresql/src/backend/storage/ipc/standby.c postgresql.1/src/backend/storage/ipc/standby.c
*** postgresql/src/backend/storage/ipc/standby.c 2012-05-02 17:36:48.984503204 +0200
--- postgresql.1/src/backend/storage/ipc/standby.c 2012-05-07 19:39:07.965976473 +0200
***************
*** 23,32 ****
#include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
- #include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/sinvaladt.h"
#include "storage/standby.h"
#include "utils/ps_status.h"
#include "utils/timestamp.h"
--- 23,32 ----
#include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
#include "storage/procarray.h"
#include "storage/sinvaladt.h"
#include "storage/standby.h"
+ #include "storage/timeout.h"
#include "utils/ps_status.h"
#include "utils/timestamp.h"
*************** ResolveRecoveryConflictWithLock(Oid dbOi
*** 394,400 ****
void
ResolveRecoveryConflictWithBufferPin(void)
{
- bool sig_alarm_enabled = false;
TimestampTz ltime;
TimestampTz now;
--- 394,399 ----
*************** ResolveRecoveryConflictWithBufferPin(voi
*** 409,417 ****
* We're willing to wait forever for conflicts, so set timeout for
* deadlock check (only)
*/
! if (enable_standby_sig_alarm(now, now, true))
! sig_alarm_enabled = true;
! else
elog(FATAL, "could not set timer for process wakeup");
}
else if (now >= ltime)
--- 408,414 ----
* We're willing to wait forever for conflicts, so set timeout for
* deadlock check (only)
*/
! if (!enable_timeout(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout))
elog(FATAL, "could not set timer for process wakeup");
}
else if (now >= ltime)
*************** ResolveRecoveryConflictWithBufferPin(voi
*** 423,446 ****
}
else
{
/*
* Wake up at ltime, and check for deadlocks as well if we will be
* waiting longer than deadlock_timeout
*/
! if (enable_standby_sig_alarm(now, ltime, false))
! sig_alarm_enabled = true;
! else
elog(FATAL, "could not set timer for process wakeup");
}
/* Wait to be signaled by UnpinBuffer() */
ProcWaitForSignal();
! if (sig_alarm_enabled)
! {
! if (!disable_standby_sig_alarm())
! elog(FATAL, "could not disable timer for process wakeup");
! }
}
void
--- 420,447 ----
}
else
{
+ long secs, msecs;
+ int usecs;
+
/*
* Wake up at ltime, and check for deadlocks as well if we will be
* waiting longer than deadlock_timeout
*/
! if (!enable_timeout(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout))
! elog(FATAL, "could not set timer for process wakeup");
!
! TimestampDifference(now, ltime, &secs, &usecs);
! msecs = secs * 1000 + usecs / 1000;
!
! if (!enable_timeout(STANDBY_TIMEOUT, msecs))
elog(FATAL, "could not set timer for process wakeup");
}
/* Wait to be signaled by UnpinBuffer() */
ProcWaitForSignal();
! if (!disable_all_timeouts(false))
! elog(FATAL, "could not disable timer for process wakeup");
}
void
diff -dcrpN postgresql/src/backend/storage/lmgr/Makefile postgresql.1/src/backend/storage/lmgr/Makefile
*** postgresql/src/backend/storage/lmgr/Makefile 2012-04-16 19:57:22.458915722 +0200
--- postgresql.1/src/backend/storage/lmgr/Makefile 2012-05-07 19:39:07.965976473 +0200
*************** subdir = src/backend/storage/lmgr
*** 12,18 ****
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
! OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o predicate.o
include $(top_srcdir)/src/backend/common.mk
--- 12,18 ----
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
! OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o predicate.o timeout.o
include $(top_srcdir)/src/backend/common.mk
diff -dcrpN postgresql/src/backend/storage/lmgr/proc.c postgresql.1/src/backend/storage/lmgr/proc.c
*** postgresql/src/backend/storage/lmgr/proc.c 2012-05-05 13:22:40.777667148 +0200
--- postgresql.1/src/backend/storage/lmgr/proc.c 2012-05-07 19:39:07.966976479 +0200
***************
*** 48,59 ****
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/spin.h"
#include "utils/timestamp.h"
/* GUC variables */
- int DeadlockTimeout = 1000;
- int StatementTimeout = 0;
bool log_lock_waits = false;
/* Pointer to this process's PGPROC struct, if any */
--- 48,58 ----
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/spin.h"
+ #include "storage/timeout.h"
#include "utils/timestamp.h"
/* GUC variables */
bool log_lock_waits = false;
/* Pointer to this process's PGPROC struct, if any */
*************** PGPROC *PreparedXactProcs = NULL;
*** 77,103 ****
/* If we are waiting for a lock, this points to the associated LOCALLOCK */
static LOCALLOCK *lockAwaited = NULL;
! /* Mark these volatile because they can be changed by signal handler */
! static volatile bool standby_timeout_active = false;
! static volatile bool statement_timeout_active = false;
! static volatile bool deadlock_timeout_active = false;
! static volatile DeadLockState deadlock_state = DS_NOT_YET_CHECKED;
! volatile bool cancel_from_timeout = false;
!
! /* timeout_start_time is set when log_lock_waits is true */
! static TimestampTz timeout_start_time;
!
! /* statement_fin_time is valid only if statement_timeout_active is true */
! static TimestampTz statement_fin_time;
! static TimestampTz statement_fin_time2; /* valid only in recovery */
!
static void RemoveProcFromArray(int code, Datum arg);
static void ProcKill(int code, Datum arg);
static void AuxiliaryProcKill(int code, Datum arg);
- static bool CheckStatementTimeout(void);
- static bool CheckStandbyTimeout(void);
-
/*
* Report shared-memory space needed by InitProcGlobal.
--- 76,87 ----
/* If we are waiting for a lock, this points to the associated LOCALLOCK */
static LOCALLOCK *lockAwaited = NULL;
! /* Declared in timeout.c */
! extern volatile DeadLockState deadlock_state;
static void RemoveProcFromArray(int code, Datum arg);
static void ProcKill(int code, Datum arg);
static void AuxiliaryProcKill(int code, Datum arg);
/*
* Report shared-memory space needed by InitProcGlobal.
*************** LockErrorCleanup(void)
*** 654,660 ****
return;
/* Turn off the deadlock timer, if it's still running (see ProcSleep) */
! disable_sig_alarm(false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
--- 638,644 ----
return;
/* Turn off the deadlock timer, if it's still running (see ProcSleep) */
! disable_timeout(DEADLOCK_TIMEOUT, false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1049,1055 ****
* 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 (!enable_sig_alarm(DeadlockTimeout, false))
elog(FATAL, "could not set timer for process wakeup");
/*
--- 1033,1039 ----
* 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 (!enable_timeout(DEADLOCK_TIMEOUT, DeadlockTimeout))
elog(FATAL, "could not set timer for process wakeup");
/*
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1139,1145 ****
DescribeLockTag(&buf, &locallock->tag.lock);
modename = GetLockmodeName(locallock->tag.lock.locktag_lockmethodid,
lockmode);
! TimestampDifference(timeout_start_time, GetCurrentTimestamp(),
&secs, &usecs);
msecs = secs * 1000 + usecs / 1000;
usecs = usecs % 1000;
--- 1123,1130 ----
DescribeLockTag(&buf, &locallock->tag.lock);
modename = GetLockmodeName(locallock->tag.lock.locktag_lockmethodid,
lockmode);
! TimestampDifference(get_timeout_start(DEADLOCK_TIMEOUT),
! GetCurrentTimestamp(),
&secs, &usecs);
msecs = secs * 1000 + usecs / 1000;
usecs = usecs % 1000;
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1201,1207 ****
/*
* Disable the timer, if it's still running
*/
! if (!disable_sig_alarm(false))
elog(FATAL, "could not disable timer for process wakeup");
/*
--- 1186,1192 ----
/*
* Disable the timer, if it's still running
*/
! if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for process wakeup");
/*
*************** ProcLockWakeup(LockMethod lockMethodTabl
*** 1333,1453 ****
}
/*
- * CheckDeadLock
- *
- * We only get to this routine if we got SIGALRM after DeadlockTimeout
- * while waiting for a lock to be released by some other process. Look
- * to see if there's a deadlock; if not, just return and continue waiting.
- * (But signal ProcSleep to log a message, if log_lock_waits is true.)
- * If we have a real deadlock, remove ourselves from the lock's wait queue
- * and signal an error to ProcSleep.
- *
- * NB: this is run inside a signal handler, so be very wary about what is done
- * here or in called routines.
- */
- static void
- CheckDeadLock(void)
- {
- int i;
-
- /*
- * Acquire exclusive lock on the entire shared lock data structures. Must
- * grab LWLocks in partition-number order to avoid LWLock deadlock.
- *
- * Note that the deadlock check interrupt had better not be enabled
- * anywhere that this process itself holds lock partition locks, else this
- * will wait forever. Also note that LWLockAcquire creates a critical
- * section, so that this routine cannot be interrupted by cancel/die
- * interrupts.
- */
- for (i = 0; i < NUM_LOCK_PARTITIONS; i++)
- LWLockAcquire(FirstLockMgrLock + i, LW_EXCLUSIVE);
-
- /*
- * Check to see if we've been awoken by anyone in the interim.
- *
- * If we have, we can return and resume our transaction -- happy day.
- * Before we are awoken the process releasing the lock grants it to us so
- * we know that we don't have to wait anymore.
- *
- * We check by looking to see if we've been unlinked from the wait queue.
- * This is quicker than checking our semaphore's state, since no kernel
- * call is needed, and it is safe because we hold the lock partition lock.
- */
- if (MyProc->links.prev == NULL ||
- MyProc->links.next == NULL)
- goto check_done;
-
- #ifdef LOCK_DEBUG
- if (Debug_deadlocks)
- DumpAllLocks();
- #endif
-
- /* Run the deadlock check, and set deadlock_state for use by ProcSleep */
- deadlock_state = DeadLockCheck(MyProc);
-
- if (deadlock_state == DS_HARD_DEADLOCK)
- {
- /*
- * Oops. We have a deadlock.
- *
- * Get this process out of wait state. (Note: we could do this more
- * efficiently by relying on lockAwaited, but use this coding to
- * preserve the flexibility to kill some other transaction than the
- * one detecting the deadlock.)
- *
- * RemoveFromWaitQueue sets MyProc->waitStatus to STATUS_ERROR, so
- * ProcSleep will report an error after we return from the signal
- * handler.
- */
- Assert(MyProc->waitLock != NULL);
- RemoveFromWaitQueue(MyProc, LockTagHashCode(&(MyProc->waitLock->tag)));
-
- /*
- * Unlock my semaphore so that the interrupted ProcSleep() call can
- * finish.
- */
- PGSemaphoreUnlock(&MyProc->sem);
-
- /*
- * We're done here. Transaction abort caused by the error that
- * ProcSleep will raise will cause any other locks we hold to be
- * released, thus allowing other processes to wake up; we don't need
- * to do that here. NOTE: an exception is that releasing locks we
- * hold doesn't consider the possibility of waiters that were blocked
- * behind us on the lock we just failed to get, and might now be
- * wakable because we're not in front of them anymore. However,
- * RemoveFromWaitQueue took care of waking up any such processes.
- */
- }
- else if (log_lock_waits || deadlock_state == DS_BLOCKED_BY_AUTOVACUUM)
- {
- /*
- * Unlock my semaphore so that the interrupted ProcSleep() call can
- * print the log message (we daren't do it here because we are inside
- * a signal handler). It will then sleep again until someone releases
- * the lock.
- *
- * If blocked by autovacuum, this wakeup will enable ProcSleep to send
- * the canceling signal to the autovacuum worker.
- */
- PGSemaphoreUnlock(&MyProc->sem);
- }
-
- /*
- * And release locks. We do this in reverse order for two reasons: (1)
- * Anyone else who needs more than one of the locks will be trying to lock
- * them in increasing order; we don't want to release the other process
- * until it can get all the locks it needs. (2) This avoids O(N^2)
- * behavior inside LWLockRelease.
- */
- check_done:
- for (i = NUM_LOCK_PARTITIONS; --i >= 0;)
- LWLockRelease(FirstLockMgrLock + i);
- }
-
-
- /*
* ProcWaitForSignal - wait for a signal from another backend.
*
* This can share the semaphore normally used for waiting for locks,
--- 1318,1323 ----
*************** ProcSendSignal(int pid)
*** 1499,1899 ****
if (proc != NULL)
PGSemaphoreUnlock(&proc->sem);
}
-
-
- /*****************************************************************************
- * SIGALRM interrupt support
- *
- * Maybe these should be in pqsignal.c?
- *****************************************************************************/
-
- /*
- * Enable the SIGALRM interrupt to fire after the specified delay
- *
- * Delay is given in milliseconds. Caller should be sure a SIGALRM
- * signal handler is installed before this is called.
- *
- * This code properly handles nesting of deadlock timeout alarms within
- * statement timeout alarms.
- *
- * Returns TRUE if okay, FALSE on failure.
- */
- bool
- enable_sig_alarm(int delayms, bool is_statement_timeout)
- {
- TimestampTz fin_time;
- struct itimerval timeval;
-
- if (is_statement_timeout)
- {
- /*
- * Begin statement-level timeout
- *
- * Note that we compute statement_fin_time with reference to the
- * statement_timestamp, but apply the specified delay without any
- * correction; that is, we ignore whatever time has elapsed since
- * statement_timestamp was set. In the normal case only a small
- * interval will have elapsed and so this doesn't matter, but there
- * are corner cases (involving multi-statement query strings with
- * embedded COMMIT or ROLLBACK) where we might re-initialize the
- * statement timeout long after initial receipt of the message. In
- * such cases the enforcement of the statement timeout will be a bit
- * inconsistent. This annoyance is judged not worth the cost of
- * performing an additional gettimeofday() here.
- */
- Assert(!deadlock_timeout_active);
- fin_time = GetCurrentStatementStartTimestamp();
- fin_time = TimestampTzPlusMilliseconds(fin_time, delayms);
- statement_fin_time = fin_time;
- cancel_from_timeout = false;
- statement_timeout_active = true;
- }
- else if (statement_timeout_active)
- {
- /*
- * Begin deadlock timeout with statement-level timeout active
- *
- * Here, we want to interrupt at the closer of the two timeout times.
- * If fin_time >= statement_fin_time then we need not touch the
- * existing timer setting; else set up to interrupt at the deadlock
- * timeout time.
- *
- * NOTE: in this case it is possible that this routine will be
- * interrupted by the previously-set timer alarm. This is okay
- * because the signal handler will do only what it should do according
- * to the state variables. The deadlock checker may get run earlier
- * than normal, but that does no harm.
- */
- timeout_start_time = GetCurrentTimestamp();
- fin_time = TimestampTzPlusMilliseconds(timeout_start_time, delayms);
- deadlock_timeout_active = true;
- if (fin_time >= statement_fin_time)
- return true;
- }
- else
- {
- /* Begin deadlock timeout with no statement-level timeout */
- deadlock_timeout_active = true;
- /* GetCurrentTimestamp can be expensive, so only do it if we must */
- if (log_lock_waits)
- timeout_start_time = GetCurrentTimestamp();
- }
-
- /* If we reach here, okay to set the timer interrupt */
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = delayms / 1000;
- timeval.it_value.tv_usec = (delayms % 1000) * 1000;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- return true;
- }
-
- /*
- * Cancel the SIGALRM timer, either for a deadlock timeout or a statement
- * timeout. If a deadlock timeout is canceled, any active statement timeout
- * remains in force.
- *
- * Returns TRUE if okay, FALSE on failure.
- */
- bool
- disable_sig_alarm(bool is_statement_timeout)
- {
- /*
- * Always disable the interrupt if it is active; this avoids being
- * interrupted by the signal handler and thereby possibly getting
- * confused.
- *
- * We will re-enable the interrupt if necessary in CheckStatementTimeout.
- */
- if (statement_timeout_active || deadlock_timeout_active)
- {
- struct itimerval timeval;
-
- MemSet(&timeval, 0, sizeof(struct itimerval));
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- {
- statement_timeout_active = false;
- cancel_from_timeout = false;
- deadlock_timeout_active = false;
- return false;
- }
- }
-
- /* Always cancel deadlock timeout, in case this is error cleanup */
- deadlock_timeout_active = false;
-
- /* Cancel or reschedule statement timeout */
- if (is_statement_timeout)
- {
- statement_timeout_active = false;
- cancel_from_timeout = false;
- }
- else if (statement_timeout_active)
- {
- if (!CheckStatementTimeout())
- return false;
- }
- return true;
- }
-
-
- /*
- * Check for statement timeout. If the timeout time has come,
- * trigger a query-cancel interrupt; if not, reschedule the SIGALRM
- * interrupt to occur at the right time.
- *
- * Returns true if okay, false if failed to set the interrupt.
- */
- static bool
- CheckStatementTimeout(void)
- {
- TimestampTz now;
-
- if (!statement_timeout_active)
- return true; /* do nothing if not active */
-
- now = GetCurrentTimestamp();
-
- if (now >= statement_fin_time)
- {
- /* Time to die */
- statement_timeout_active = false;
- cancel_from_timeout = true;
- #ifdef HAVE_SETSID
- /* try to signal whole process group */
- kill(-MyProcPid, SIGINT);
- #endif
- kill(MyProcPid, SIGINT);
- }
- else
- {
- /* Not time yet, so (re)schedule the interrupt */
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
-
- /*
- * It's possible that the difference is less than a microsecond;
- * ensure we don't cancel, rather than set, the interrupt.
- */
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- }
-
- return true;
- }
-
-
- /*
- * Signal handler for SIGALRM for normal user backends
- *
- * Process deadlock check and/or statement timeout check, as needed.
- * To avoid various edge cases, we must be careful to do nothing
- * when there is nothing to be done. We also need to be able to
- * reschedule the timer interrupt if called before end of statement.
- */
- void
- handle_sig_alarm(SIGNAL_ARGS)
- {
- int save_errno = errno;
-
- /* SIGALRM is cause for waking anything waiting on the process latch */
- if (MyProc)
- SetLatch(&MyProc->procLatch);
-
- if (deadlock_timeout_active)
- {
- deadlock_timeout_active = false;
- CheckDeadLock();
- }
-
- if (statement_timeout_active)
- (void) CheckStatementTimeout();
-
- errno = save_errno;
- }
-
- /*
- * Signal handler for SIGALRM in Startup process
- *
- * To avoid various edge cases, we must be careful to do nothing
- * when there is nothing to be done. We also need to be able to
- * reschedule the timer interrupt if called before end of statement.
- *
- * We set either deadlock_timeout_active or statement_timeout_active
- * or both. Interrupts are enabled if standby_timeout_active.
- */
- bool
- enable_standby_sig_alarm(TimestampTz now, TimestampTz fin_time, bool deadlock_only)
- {
- TimestampTz deadlock_time = TimestampTzPlusMilliseconds(now,
- DeadlockTimeout);
-
- if (deadlock_only)
- {
- /*
- * Wake up at deadlock_time only, then wait forever
- */
- statement_fin_time = deadlock_time;
- deadlock_timeout_active = true;
- statement_timeout_active = false;
- }
- else if (fin_time > deadlock_time)
- {
- /*
- * Wake up at deadlock_time, then again at fin_time
- */
- statement_fin_time = deadlock_time;
- statement_fin_time2 = fin_time;
- deadlock_timeout_active = true;
- statement_timeout_active = true;
- }
- else
- {
- /*
- * Wake only at fin_time because its fairly soon
- */
- statement_fin_time = fin_time;
- deadlock_timeout_active = false;
- statement_timeout_active = true;
- }
-
- if (deadlock_timeout_active || statement_timeout_active)
- {
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- standby_timeout_active = true;
- }
-
- return true;
- }
-
- bool
- disable_standby_sig_alarm(void)
- {
- /*
- * Always disable the interrupt if it is active; this avoids being
- * interrupted by the signal handler and thereby possibly getting
- * confused.
- *
- * We will re-enable the interrupt if necessary in CheckStandbyTimeout.
- */
- if (standby_timeout_active)
- {
- struct itimerval timeval;
-
- MemSet(&timeval, 0, sizeof(struct itimerval));
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- {
- standby_timeout_active = false;
- return false;
- }
- }
-
- standby_timeout_active = false;
-
- return true;
- }
-
- /*
- * CheckStandbyTimeout() runs unconditionally in the Startup process
- * SIGALRM handler. Timers will only be set when InHotStandby.
- * We simply ignore any signals unless the timer has been set.
- */
- static bool
- CheckStandbyTimeout(void)
- {
- TimestampTz now;
- bool reschedule = false;
-
- standby_timeout_active = false;
-
- now = GetCurrentTimestamp();
-
- /*
- * Reschedule the timer if its not time to wake yet, or if we have both
- * timers set and the first one has just been reached.
- */
- if (now >= statement_fin_time)
- {
- if (deadlock_timeout_active)
- {
- /*
- * We're still waiting when we reach deadlock timeout, so send out
- * a request to have other backends check themselves for deadlock.
- * Then continue waiting until statement_fin_time, if that's set.
- */
- SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
- deadlock_timeout_active = false;
-
- /*
- * Begin second waiting period if required.
- */
- if (statement_timeout_active)
- {
- reschedule = true;
- statement_fin_time = statement_fin_time2;
- }
- }
- else
- {
- /*
- * We've now reached statement_fin_time, so ask all conflicts to
- * leave, so we can press ahead with applying changes in recovery.
- */
- SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
- }
- }
- else
- reschedule = true;
-
- if (reschedule)
- {
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- standby_timeout_active = true;
- }
-
- return true;
- }
-
- void
- handle_standby_sig_alarm(SIGNAL_ARGS)
- {
- int save_errno = errno;
-
- if (standby_timeout_active)
- (void) CheckStandbyTimeout();
-
- errno = save_errno;
- }
--- 1369,1371 ----
diff -dcrpN postgresql/src/backend/storage/lmgr/timeout.c postgresql.1/src/backend/storage/lmgr/timeout.c
*** postgresql/src/backend/storage/lmgr/timeout.c 1970-01-01 01:00:00.000000000 +0100
--- postgresql.1/src/backend/storage/lmgr/timeout.c 2012-05-07 19:39:07.966976479 +0200
***************
*** 0 ****
--- 1,680 ----
+ /*-------------------------------------------------------------------------
+ *
+ * timeout.c
+ * routines to manage timeout sources handled by SIGALRM
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/storage/lmgr/timeout.c
+ *
+ *-------------------------------------------------------------------------
+ */
+ /*
+ * Interface:
+ * enable_timeout(),
+ * disable_timeout(), disable_all_timeouts()
+ * get_timeout_indicator(), get_timeout_start()
+ * and
+ * handle_sig_alarm()
+ */
+ #include "postgres.h"
+
+ #include <sys/time.h>
+
+ #include "access/xact.h"
+ #include "miscadmin.h"
+ #include "storage/proc.h"
+ #include "storage/standby.h"
+ #include "storage/timeout.h"
+ #include "utils/timestamp.h"
+
+ /* GUC variables */
+ int DeadlockTimeout = 1000;
+ int StatementTimeout = 0;
+
+ /*
+ * This is used by ProcSleep() in proc.c
+ * Mark this volatile because it can be changed by the signal handler.
+ */
+ volatile DeadLockState deadlock_state = DS_NOT_YET_CHECKED;
+
+
+ /*
+ * Infrastructure for timeouts
+ */
+
+ static void InitDeadLock(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyDeadLock(bool keep_indicator);
+ static bool CheckDeadLock(void);
+
+ static void InitStatementTimeout(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyStatementTimeout(bool keep_indicator);
+ static bool CheckStatementTimeout(void);
+
+ static void InitStandbyDeadLock(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyStandbyDeadLock(bool keep_indicator);
+ static bool CheckStandbyDeadLock(void);
+
+ static void InitStandbyTimeout(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyStandbyTimeout(bool keep_indicator);
+ static bool CheckStandbyTimeout(void);
+
+ typedef void (*timeout_init)(TimestampTz, TimestampTz);
+ typedef void (*timeout_destroy)(bool);
+ typedef bool (*timeout_check)(void);
+ typedef TimestampTz (*timeout_start)(void);
+
+ typedef struct {
+ TimeoutName index;
+ bool resched_next;
+
+ /* volatile because it may be changed from the signal handler */
+ volatile bool indicator;
+
+ timeout_init timeout_init;
+ timeout_destroy timeout_destroy;
+ timeout_check timeout_check;
+ timeout_start timeout_start;
+
+ TimestampTz start_time;
+ TimestampTz fin_time;
+ } timeout_params;
+
+ /*
+ * List of possible timeout reasons in the order of enum TimeoutName.
+ * The priority of timeouts (in case two of them would trigger at the
+ * same time) is determined by this order: the earlier one in the list
+ * has higher priority.
+ */
+ static timeout_params base_timeouts[TIMEOUT_MAX] = {
+ {
+ DEADLOCK_TIMEOUT, true, false,
+ InitDeadLock, DestroyDeadLock,
+ CheckDeadLock, GetCurrentTimestamp,
+ 0
+ },
+
+ {
+ STATEMENT_TIMEOUT, false, false,
+ InitStatementTimeout, DestroyStatementTimeout,
+ CheckStatementTimeout, GetCurrentStatementStartTimestamp,
+ 0
+ },
+
+ {
+ STANDBY_DEADLOCK_TIMEOUT, true, false,
+ InitStandbyDeadLock, DestroyStandbyDeadLock,
+ CheckStandbyDeadLock, GetCurrentTimestamp,
+ 0
+ },
+
+ {
+ STANDBY_TIMEOUT, false, false,
+ InitStandbyTimeout, DestroyStandbyTimeout,
+ CheckStandbyTimeout, GetCurrentTimestamp,
+ 0
+ }
+ };
+
+ /*
+ * List of active timeouts ordered by their fin_time and priority.
+ */
+ static int n_timeouts = 0;
+ static timeout_params *timeouts[TIMEOUT_MAX];
+
+ /*****************************************************************************
+ * Internal helper functions
+ *****************************************************************************/
+
+ /*
+ * Find the index of a given timeout type in the active array
+ */
+ static int
+ find_active_timeout(TimeoutName tn)
+ {
+ int i;
+
+ for (i = 0; i < n_timeouts; i++)
+ {
+ if (timeouts[i]->index == tn)
+ return i;
+ }
+
+ return -1;
+ }
+
+ #define is_timeout_active(tn) (find_active_timeout(tn) >= 0)
+
+ /*
+ * Insert tn'th timeout into the list of timeouts at the given index
+ * if the previous timeout allows rescheduling the next one in the list.
+ */
+ static void
+ insert_timeout(TimeoutName tn, int index)
+ {
+ int i;
+
+ if (index > 0 && !timeouts[index-1]->resched_next)
+ return;
+
+ for (i = n_timeouts - 1; i >= index; i--)
+ timeouts[i+1] = timeouts[i];
+
+ timeouts[index] = &base_timeouts[tn];
+ n_timeouts++;
+ }
+
+ /*
+ * Remove the index'th element from the timeout list.
+ */
+ static void
+ remove_timeout_index(int index)
+ {
+ int i;
+
+ if (index < 0)
+ return;
+
+ for (i = index + 1; i < n_timeouts; i++)
+ timeouts[i-1] = timeouts[i];
+
+ if (n_timeouts > 0 && index < n_timeouts)
+ n_timeouts--;
+ }
+
+ /*
+ * (Re)schedule the next active timeout
+ */
+ static bool
+ schedule_timeout(TimestampTz now)
+ {
+ long secs;
+ int usecs;
+ struct itimerval timeval;
+
+ /* There is no active timeout, do nothing. */
+ if (n_timeouts == 0)
+ return true;
+
+ TimestampDifference(now, timeouts[0]->fin_time,
+ &secs, &usecs);
+
+ /*
+ * It's possible that the difference is less than a microsecond;
+ * ensure we don't cancel, rather than set, the interrupt.
+ */
+ if (secs == 0 && usecs == 0)
+ usecs = 1;
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ timeval.it_value.tv_sec = secs;
+ timeval.it_value.tv_usec = usecs;
+ if (setitimer(ITIMER_REAL, &timeval, NULL))
+ return false;
+ return true;
+ }
+
+ /*****************************************************************************
+ * Init, Destroy and Check functions for different timeouts.
+ *
+ * NB: all Check* functions are run inside a signal handler, so be very wary
+ * about what is done in them or in called routines.
+ *****************************************************************************/
+
+ /*
+ * Common Init and Destroy functions
+ */
+
+ static void
+ InitTimeout(TimeoutName tn, TimestampTz start_time, TimestampTz fin_time)
+ {
+ base_timeouts[tn].indicator = false;
+ base_timeouts[tn].start_time = start_time;
+ base_timeouts[tn].fin_time = fin_time;
+ }
+
+ static void
+ DestroyTimeout(TimeoutName tn, bool keep_indicator)
+ {
+ if (!keep_indicator)
+ base_timeouts[tn].indicator = false;
+ base_timeouts[tn].start_time = 0;
+ base_timeouts[tn].fin_time = 0;
+ }
+
+ /*
+ * Functions to manage deadlock
+ */
+
+ static void
+ InitDeadLock(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(DEADLOCK_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyDeadLock(bool keep_indicator)
+ {
+ DestroyTimeout(DEADLOCK_TIMEOUT, keep_indicator);
+ }
+
+ /*
+ * CheckDeadLock
+ *
+ * Look to see if there's a deadlock; if not, just return.
+ * (But signal ProcSleep to log a message, if log_lock_waits is true.)
+ * If we have a real deadlock, remove ourselves from the lock's wait queue
+ * and signal an error to ProcSleep.
+ */
+ static bool
+ CheckDeadLock(void)
+ {
+ TimestampTz now;
+ int i;
+
+ now = GetCurrentTimestamp();
+
+ /* If our time has not come yet, do nothing. */
+ if (now < base_timeouts[DEADLOCK_TIMEOUT].fin_time)
+ return false;
+
+ /*
+ * Acquire exclusive lock on the entire shared lock data structures. Must
+ * grab LWLocks in partition-number order to avoid LWLock deadlock.
+ *
+ * Note that the deadlock check interrupt had better not be enabled
+ * anywhere that this process itself holds lock partition locks, else this
+ * will wait forever. Also note that LWLockAcquire creates a critical
+ * section, so that this routine cannot be interrupted by cancel/die
+ * interrupts.
+ */
+ for (i = 0; i < NUM_LOCK_PARTITIONS; i++)
+ LWLockAcquire(FirstLockMgrLock + i, LW_EXCLUSIVE);
+
+ /*
+ * Check to see if we've been awoken by anyone in the interim.
+ *
+ * If we have, we can return and resume our transaction -- happy day.
+ * Before we are awoken the process releasing the lock grants it to us so
+ * we know that we don't have to wait anymore.
+ *
+ * We check by looking to see if we've been unlinked from the wait queue.
+ * This is quicker than checking our semaphore's state, since no kernel
+ * call is needed, and it is safe because we hold the lock partition lock.
+ */
+ if (MyProc->links.prev == NULL ||
+ MyProc->links.next == NULL)
+ goto check_done;
+
+ #ifdef LOCK_DEBUG
+ if (Debug_deadlocks)
+ DumpAllLocks();
+ #endif
+
+ /* Run the deadlock check, and set deadlock_state for use by ProcSleep */
+ deadlock_state = DeadLockCheck(MyProc);
+
+ if (deadlock_state == DS_HARD_DEADLOCK)
+ {
+ /*
+ * Oops. We have a deadlock.
+ *
+ * Get this process out of wait state. (Note: we could do this more
+ * efficiently by relying on lockAwaited, but use this coding to
+ * preserve the flexibility to kill some other transaction than the
+ * one detecting the deadlock.)
+ *
+ * RemoveFromWaitQueue sets MyProc->waitStatus to STATUS_ERROR, so
+ * ProcSleep will report an error after we return from the signal
+ * handler.
+ */
+ Assert(MyProc->waitLock != NULL);
+ RemoveFromWaitQueue(MyProc, LockTagHashCode(&(MyProc->waitLock->tag)));
+
+ /*
+ * Unlock my semaphore so that the interrupted ProcSleep() call can
+ * finish.
+ */
+ PGSemaphoreUnlock(&MyProc->sem);
+
+ /*
+ * We're done here. Transaction abort caused by the error that
+ * ProcSleep will raise will cause any other locks we hold to be
+ * released, thus allowing other processes to wake up; we don't need
+ * to do that here. NOTE: an exception is that releasing locks we
+ * hold doesn't consider the possibility of waiters that were blocked
+ * behind us on the lock we just failed to get, and might now be
+ * wakable because we're not in front of them anymore. However,
+ * RemoveFromWaitQueue took care of waking up any such processes.
+ */
+ }
+ else if (log_lock_waits || deadlock_state == DS_BLOCKED_BY_AUTOVACUUM)
+ {
+ /*
+ * Unlock my semaphore so that the interrupted ProcSleep() call can
+ * print the log message (we daren't do it here because we are inside
+ * a signal handler). It will then sleep again until someone releases
+ * the lock.
+ *
+ * If blocked by autovacuum, this wakeup will enable ProcSleep to send
+ * the canceling signal to the autovacuum worker.
+ */
+ PGSemaphoreUnlock(&MyProc->sem);
+ }
+
+ /*
+ * And release locks. We do this in reverse order for two reasons: (1)
+ * Anyone else who needs more than one of the locks will be trying to lock
+ * them in increasing order; we don't want to release the other process
+ * until it can get all the locks it needs. (2) This avoids O(N^2)
+ * behavior inside LWLockRelease.
+ */
+ check_done:
+ for (i = NUM_LOCK_PARTITIONS; --i >= 0;)
+ LWLockRelease(FirstLockMgrLock + i);
+
+ base_timeouts[DEADLOCK_TIMEOUT].indicator = true;
+
+ return true;
+ }
+
+ /*
+ * Functions to manage statement timeout
+ */
+
+ static void
+ InitStatementTimeout(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(STATEMENT_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyStatementTimeout(bool keep_indicator)
+ {
+ DestroyTimeout(STATEMENT_TIMEOUT, keep_indicator);
+ }
+
+ static bool
+ CheckStatementTimeout(void)
+ {
+ TimestampTz now;
+
+ now = GetCurrentTimestamp();
+
+ if (now < base_timeouts[STATEMENT_TIMEOUT].fin_time)
+ return false;
+
+ base_timeouts[STATEMENT_TIMEOUT].indicator = true;
+ #ifdef HAVE_SETSID
+ /* try to signal whole process group */
+ kill(-MyProcPid, SIGINT);
+ #endif
+ kill(MyProcPid, SIGINT);
+
+ return true;
+ }
+
+ /*
+ * Functions to manage standby deadlock functions
+ */
+
+ static void
+ InitStandbyDeadLock(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(STANDBY_DEADLOCK_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyStandbyDeadLock(bool keep_indicator)
+ {
+ DestroyTimeout(STANDBY_DEADLOCK_TIMEOUT, keep_indicator);
+ }
+
+ static bool
+ CheckStandbyDeadLock(void)
+ {
+ TimestampTz now;
+
+ now = GetCurrentTimestamp();
+
+ if (now < base_timeouts[STANDBY_DEADLOCK_TIMEOUT].fin_time)
+ return false;
+
+ base_timeouts[STANDBY_DEADLOCK_TIMEOUT].indicator = true;
+ SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
+ return true;
+ }
+
+ /*
+ * Functions to manage standby timeout
+ */
+
+ static void
+ InitStandbyTimeout(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(STANDBY_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyStandbyTimeout(bool keep_indicator)
+ {
+ DestroyTimeout(STANDBY_TIMEOUT, keep_indicator);
+ }
+
+ static bool
+ CheckStandbyTimeout(void)
+ {
+ TimestampTz now;
+
+ now = GetCurrentTimestamp();
+
+ if (now < base_timeouts[STANDBY_TIMEOUT].fin_time)
+ return false;
+
+ base_timeouts[STANDBY_TIMEOUT].indicator = true;
+ SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
+ return true;
+ }
+
+ /*****************************************************************************
+ * Public API
+ *****************************************************************************/
+
+
+ /*
+ * Enable the SIGALRM interrupt to fire after the specified delay
+ *
+ * Delay is given in milliseconds. Caller should be sure a SIGALRM
+ * signal handler is installed before this is called.
+ *
+ * This code properly handles nesting of different timeout alarms.
+ *
+ * Returns TRUE if okay, FALSE on failure to set the timer.
+ */
+ bool
+ enable_timeout(TimeoutName tn, int delayms)
+ {
+ TimestampTz start_time;
+ TimestampTz fin_time;
+ int i;
+
+ Assert(!is_timeout_active(tn));
+
+ if (delayms <= 0)
+ return true;
+
+ start_time = base_timeouts[tn].timeout_start();
+ fin_time = TimestampTzPlusMilliseconds(start_time, delayms);
+
+ /* Find out the index where to insert the new timeout. */
+ for (i = 0; i < n_timeouts; i++)
+ {
+ /*
+ * The new timeout triggers earlier than
+ * a previously added one: insert here.
+ */
+ if (fin_time < timeouts[i]->fin_time)
+ break;
+ /*
+ * The new timeout triggers at the same time
+ * as the previously added one but has greater priority.
+ */
+ if (fin_time == timeouts[i]->fin_time && tn < timeouts[i]->index)
+ break;
+ }
+
+ /*
+ * Initialize the timeout parameters
+ */
+ base_timeouts[tn].timeout_init(start_time, fin_time);
+
+ insert_timeout(tn, i);
+
+ if (i > 0)
+ return true;
+
+ /* If we reach here, okay to set the timer interrupt */
+ if (!schedule_timeout(start_time))
+ return false;
+ return true;
+ }
+
+ /*
+ * Cancel the SIGALRM timer for the specific timeout.
+ * If a timeout is canceled, any other active timeout remains in force.
+ *
+ * Returns TRUE if okay, FALSE on failure to set the timer for
+ * the next timeout source.
+ */
+ bool
+ disable_timeout(TimeoutName tn, bool keep_indicator)
+ {
+ int i;
+
+ /*
+ * Always disable the interrupt if it is active; this avoids being
+ * interrupted by the signal handler and thereby possibly getting
+ * confused.
+ *
+ * We will re-enable the interrupt if necessary in ->check_timeout().
+ */
+ if (n_timeouts > 0)
+ {
+ struct itimerval timeval;
+
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ if (setitimer(ITIMER_REAL, &timeval, NULL))
+ {
+ n_timeouts = 0;
+ for (i = 0; i < TIMEOUT_MAX; i++)
+ base_timeouts[i].timeout_destroy(false);
+
+ return false;
+ }
+ }
+
+ /* Find the timeout and remove from the list. */
+ i = find_active_timeout(tn);
+ remove_timeout_index(i);
+
+ /* Do cleanup. */
+ base_timeouts[tn].timeout_destroy(keep_indicator);
+
+ /*
+ * If the first timeout was removed from the list and there is
+ * at least one active left, reschedule it.
+ */
+ if (i == 0 && n_timeouts > 0)
+ if (!schedule_timeout(GetCurrentTimestamp()))
+ return false;
+
+ return true;
+ }
+
+ /*
+ * Disable SIGALRM and remove all timeouts from the list and
+ * reset the timeout indicators.
+ */
+ bool
+ disable_all_timeouts(bool keep_indicators)
+ {
+ struct itimerval timeval;
+ int i;
+ bool ret;
+
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ ret = (setitimer(ITIMER_REAL, &timeval, NULL) == 0);
+
+ n_timeouts = 0;
+ for (i = 0; i < TIMEOUT_MAX; i++)
+ base_timeouts[i].timeout_destroy(keep_indicators);
+
+ return ret;
+ }
+
+ /*
+ * Return the timeout indicator
+ */
+ bool
+ get_timeout_indicator(TimeoutName tn)
+ {
+ return base_timeouts[tn].indicator;
+ }
+
+ /*
+ * Return the start of the timer for this timeout
+ */
+ TimestampTz
+ get_timeout_start(TimeoutName tn)
+ {
+ return base_timeouts[tn].start_time;
+ }
+
+ /*
+ * Signal handler for SIGALRM
+ *
+ * Process the check for the currently active timeout source and
+ * reschedule the next as needed. To avoid various edge cases,
+ * we must be careful to do nothing when there is nothing to be done.
+ */
+ void
+ handle_sig_alarm(SIGNAL_ARGS)
+ {
+ int save_errno = errno;
+
+ /*
+ * SIGALRM is cause for waking anything waiting on the process latch.
+ * Recovery (the startup process) doesn't have MyProc set so
+ * it can also use this signal handler.
+ */
+ if (MyProc)
+ SetLatch(&MyProc->procLatch);
+
+ if (n_timeouts > 0)
+ {
+ bool ret;
+ bool reschedule = true;
+
+ ret = timeouts[0]->timeout_check();
+
+ /* Shift timeouts if the timeout was triggered */
+ if (ret)
+ {
+ reschedule = timeouts[0]->resched_next;
+ /*
+ * Short circuit disable_timeout(..., true) for the
+ * timeout source that just triggered.
+ */
+ remove_timeout_index(0);
+ }
+
+ if (reschedule)
+ schedule_timeout(GetCurrentTimestamp());
+ else
+ disable_all_timeouts(true);
+ }
+
+ errno = save_errno;
+ }
diff -dcrpN postgresql/src/backend/tcop/postgres.c postgresql.1/src/backend/tcop/postgres.c
*** postgresql/src/backend/tcop/postgres.c 2012-05-07 19:35:27.203721210 +0200
--- postgresql.1/src/backend/tcop/postgres.c 2012-05-07 19:39:07.968976493 +0200
***************
*** 64,69 ****
--- 64,70 ----
#include "storage/proc.h"
#include "storage/procsignal.h"
#include "storage/sinval.h"
+ #include "storage/timeout.h"
#include "tcop/fastpath.h"
#include "tcop/pquery.h"
#include "tcop/tcopprot.h"
*************** start_xact_command(void)
*** 2396,2404 ****
/* Set statement timeout running, if any */
/* NB: this mustn't be enabled until we are within an xact */
if (StatementTimeout > 0)
! enable_sig_alarm(StatementTimeout, true);
else
! cancel_from_timeout = false;
xact_started = true;
}
--- 2397,2405 ----
/* Set statement timeout running, if any */
/* NB: this mustn't be enabled until we are within an xact */
if (StatementTimeout > 0)
! enable_timeout(STATEMENT_TIMEOUT, StatementTimeout);
else
! disable_timeout(STATEMENT_TIMEOUT, false);
xact_started = true;
}
*************** finish_xact_command(void)
*** 2410,2416 ****
if (xact_started)
{
/* Cancel any active statement timeout before committing */
! disable_sig_alarm(true);
/* Now commit the command */
ereport(DEBUG3,
--- 2411,2417 ----
if (xact_started)
{
/* Cancel any active statement timeout before committing */
! disable_all_timeouts(false);
/* Now commit the command */
ereport(DEBUG3,
*************** ProcessInterrupts(void)
*** 2891,2897 ****
(errcode(ERRCODE_QUERY_CANCELED),
errmsg("canceling authentication due to timeout")));
}
! if (cancel_from_timeout)
{
ImmediateInterruptOK = false; /* not idle anymore */
DisableNotifyInterrupt();
--- 2892,2898 ----
(errcode(ERRCODE_QUERY_CANCELED),
errmsg("canceling authentication due to timeout")));
}
! if (get_timeout_indicator(STATEMENT_TIMEOUT))
{
ImmediateInterruptOK = false; /* not idle anymore */
DisableNotifyInterrupt();
*************** PostgresMain(int argc, char *argv[], con
*** 3796,3805 ****
/*
* Forget any pending QueryCancel request, since we're returning to
! * the idle loop anyway, and cancel the statement timer if running.
*/
QueryCancelPending = false;
! disable_sig_alarm(true);
QueryCancelPending = false; /* again in case timeout occurred */
/*
--- 3797,3806 ----
/*
* Forget any pending QueryCancel request, since we're returning to
! * the idle loop anyway, and cancel the timer if running.
*/
QueryCancelPending = false;
! disable_all_timeouts(false);
QueryCancelPending = false; /* again in case timeout occurred */
/*
diff -dcrpN postgresql/src/backend/utils/init/postinit.c postgresql.1/src/backend/utils/init/postinit.c
*** postgresql/src/backend/utils/init/postinit.c 2012-04-16 19:57:22.490916093 +0200
--- postgresql.1/src/backend/utils/init/postinit.c 2012-05-07 19:39:07.968976493 +0200
***************
*** 41,51 ****
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lmgr.h"
- #include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "storage/smgr.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
#include "utils/fmgroids.h"
--- 41,51 ----
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lmgr.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "storage/smgr.h"
+ #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
#include "utils/fmgroids.h"
*************** PerformAuthentication(Port *port)
*** 204,210 ****
* during authentication. Since we're inside a transaction and might do
* database access, we have to use the statement_timeout infrastructure.
*/
! if (!enable_sig_alarm(AuthenticationTimeout * 1000, true))
elog(FATAL, "could not set timer for authorization timeout");
/*
--- 204,210 ----
* during authentication. Since we're inside a transaction and might do
* database access, we have to use the statement_timeout infrastructure.
*/
! if (!enable_timeout(STATEMENT_TIMEOUT, AuthenticationTimeout * 1000))
elog(FATAL, "could not set timer for authorization timeout");
/*
*************** PerformAuthentication(Port *port)
*** 215,221 ****
/*
* Done with authentication. Disable the timeout, and log if needed.
*/
! if (!disable_sig_alarm(true))
elog(FATAL, "could not disable timer for authorization timeout");
if (Log_connections)
--- 215,221 ----
/*
* Done with authentication. Disable the timeout, and log if needed.
*/
! if (!disable_timeout(STATEMENT_TIMEOUT, false))
elog(FATAL, "could not disable timer for authorization timeout");
if (Log_connections)
diff -dcrpN postgresql/src/backend/utils/misc/guc.c postgresql.1/src/backend/utils/misc/guc.c
*** postgresql/src/backend/utils/misc/guc.c 2012-04-30 08:18:06.650737934 +0200
--- postgresql.1/src/backend/utils/misc/guc.c 2012-05-07 19:39:07.970976505 +0200
***************
*** 63,68 ****
--- 63,69 ----
#include "storage/standby.h"
#include "storage/fd.h"
#include "storage/predicate.h"
+ #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "tsearch/ts_cache.h"
#include "utils/builtins.h"
diff -dcrpN postgresql/src/include/storage/proc.h postgresql.1/src/include/storage/proc.h
*** postgresql/src/include/storage/proc.h 2012-05-02 21:00:13.521347699 +0200
--- postgresql.1/src/include/storage/proc.h 2012-05-07 19:39:07.970976505 +0200
*************** extern PGPROC *PreparedXactProcs;
*** 215,227 ****
/* configurable options */
- extern int DeadlockTimeout;
- extern int StatementTimeout;
extern bool log_lock_waits;
- extern volatile bool cancel_from_timeout;
-
-
/*
* Function Prototypes
*/
--- 215,222 ----
*************** extern void LockErrorCleanup(void);
*** 249,261 ****
extern void ProcWaitForSignal(void);
extern void ProcSendSignal(int pid);
- extern bool enable_sig_alarm(int delayms, bool is_statement_timeout);
- extern bool disable_sig_alarm(bool is_statement_timeout);
- extern void handle_sig_alarm(SIGNAL_ARGS);
-
- extern bool enable_standby_sig_alarm(TimestampTz now,
- TimestampTz fin_time, bool deadlock_only);
- extern bool disable_standby_sig_alarm(void);
- extern void handle_standby_sig_alarm(SIGNAL_ARGS);
-
#endif /* PROC_H */
--- 244,247 ----
diff -dcrpN postgresql/src/include/storage/timeout.h postgresql.1/src/include/storage/timeout.h
*** postgresql/src/include/storage/timeout.h 1970-01-01 01:00:00.000000000 +0100
--- postgresql.1/src/include/storage/timeout.h 2012-05-07 19:39:07.971976511 +0200
***************
*** 0 ****
--- 1,38 ----
+ /*-------------------------------------------------------------------------
+ *
+ * timeout.h
+ * SIGALRM timeout API
+ *
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/storage/timeout.h
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef _TIMEOUT_H_
+ #define _TIMEOUT_H_
+
+ #include "datatype/timestamp.h"
+
+ /* configurable options */
+ extern int DeadlockTimeout;
+ extern int StatementTimeout;
+
+ typedef enum TimeoutName {
+ DEADLOCK_TIMEOUT,
+ STATEMENT_TIMEOUT,
+ STANDBY_DEADLOCK_TIMEOUT,
+ STANDBY_TIMEOUT,
+ TIMEOUT_MAX
+ } TimeoutName;
+
+ extern bool enable_timeout(TimeoutName tn, int delayms);
+ extern bool disable_timeout(TimeoutName tn, bool keep_indicator);
+ extern bool disable_all_timeouts(bool keep_indicators);
+ extern bool get_timeout_indicator(TimeoutName tn);
+ extern TimestampTz get_timeout_start(TimeoutName tn);
+ extern void handle_sig_alarm(SIGNAL_ARGS);
+
+ #endif /* _TIMEOUT_H_ */
2-lock_timeout-v4.patchtext/x-patch; name=2-lock_timeout-v4.patchDownload
diff -dcrpN postgresql.1/doc/src/sgml/config.sgml postgresql.2/doc/src/sgml/config.sgml
*** postgresql.1/doc/src/sgml/config.sgml 2012-04-30 08:18:06.609737757 +0200
--- postgresql.2/doc/src/sgml/config.sgml 2012-05-07 19:39:39.839157405 +0200
*************** COPY postgres_log FROM '/full/path/to/lo
*** 4958,4964 ****
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.
</para>
<para>
--- 4958,4967 ----
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. 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>
*************** COPY postgres_log FROM '/full/path/to/lo
*** 4969,4974 ****
--- 4972,5005 ----
</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 has to wait more
+ than the specified number of milliseconds, starting from the time the
+ command arrives at the server from the client. If the statement involves
+ more than one such lock, the timeout applies to every one of them.
+ 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 to acquire.
+ 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 off the limitation.
+ </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 -dcrpN 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-05-07 19:39:39.840157411 +0200
*************** LOCK [ TABLE ] [ ONLY ] <replaceable cla
*** 39,46 ****
<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.)
</para>
--- 39,49 ----
<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. 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 -dcrpN 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-05-07 19:39:39.842157422 +0200
*************** FOR SHARE [ OF <replaceable class="param
*** 1199,1204 ****
--- 1199,1211 ----
</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 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 -dcrpN postgresql.1/src/backend/port/posix_sema.c postgresql.2/src/backend/port/posix_sema.c
*** postgresql.1/src/backend/port/posix_sema.c 2012-04-16 19:57:22.438915489 +0200
--- postgresql.2/src/backend/port/posix_sema.c 2012-05-07 19:39:39.843157427 +0200
***************
*** 24,29 ****
--- 24,30 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/timeout.h"
#ifdef USE_NAMED_POSIX_SEMAPHORES
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 313,315 ****
--- 314,341 ----
return true;
}
+
+ /*
+ * PGSemaphoreTimedLock
+ *
+ * Lock a semaphore (decrement count), blocking if count would be < 0
+ * Return if lock_timeout expired
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ int errStatus;
+
+ do
+ {
+ ImmediateInterruptOK = interruptOK;
+ CHECK_FOR_INTERRUPTS();
+ errStatus = sem_wait(PG_SEM_REF(sema));
+ ImmediateInterruptOK = false;
+ } while (errStatus < 0 && errno == EINTR && !get_timeout_indicator(LOCK_TIMEOUT));
+
+ if (get_timeout_indicator(LOCK_TIMEOUT))
+ return;
+ if (errStatus < 0)
+ elog(FATAL, "sem_wait failed: %m");
+ }
diff -dcrpN postgresql.1/src/backend/port/sysv_sema.c postgresql.2/src/backend/port/sysv_sema.c
*** postgresql.1/src/backend/port/sysv_sema.c 2012-04-16 19:57:22.438915489 +0200
--- postgresql.2/src/backend/port/sysv_sema.c 2012-05-07 19:39:39.843157427 +0200
***************
*** 30,35 ****
--- 30,36 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/timeout.h"
#ifndef HAVE_UNION_SEMUN
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 495,497 ****
--- 496,528 ----
return true;
}
+
+ /*
+ * PGSemaphoreTimedLock
+ *
+ * Lock a semaphore (decrement count), blocking if count would be < 0
+ * Return if lock_timeout expired
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ int errStatus;
+ 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;
+ } while (errStatus < 0 && errno == EINTR && !get_timeout_indicator(LOCK_TIMEOUT));
+
+ if (get_timeout_indicator(LOCK_TIMEOUT))
+ return;
+ if (errStatus < 0)
+ elog(FATAL, "semop(id=%d) failed: %m", sema->semId);
+ }
diff -dcrpN postgresql.1/src/backend/port/win32_sema.c postgresql.2/src/backend/port/win32_sema.c
*** postgresql.1/src/backend/port/win32_sema.c 2012-04-16 19:57:22.439915501 +0200
--- postgresql.2/src/backend/port/win32_sema.c 2012-05-07 19:39:39.843157427 +0200
***************
*** 16,21 ****
--- 16,22 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/timeout.h"
static HANDLE *mySemSet; /* IDs of sema sets acquired so far */
static int numSems; /* number of sema sets acquired so far */
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 205,207 ****
--- 206,263 ----
/* 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.
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ DWORD ret;
+ HANDLE wh[2];
+
+ wh[0] = *sema;
+ wh[1] = pgwin32_signal_event;
+
+ /*
+ * 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();
+
+ errno = 0;
+ ret = WaitForMultipleObjectsEx(2, wh, FALSE, INFINITE, TRUE);
+
+ if (ret == WAIT_OBJECT_0)
+ {
+ /* We got it! */
+ return;
+ }
+ else if (ret == WAIT_OBJECT_0 + 1)
+ {
+ /* Signal event is set - we have a signal to deliver */
+ pgwin32_dispatch_queued_signals();
+ errno = EINTR;
+ }
+ else
+ /* Otherwise we are in trouble */
+ errno = EIDRM;
+
+ ImmediateInterruptOK = false;
+ } while (errno == EINTR && !get_timeout_indicator(LOCK_TIMEOUT));
+
+ if (get_timeout_indicator(LOCK_TIMEOUT))
+ return;
+ if (errno != 0)
+ ereport(FATAL,
+ (errmsg("could not lock semaphore: error code %d", (int) GetLastError())));
+ }
diff -dcrpN postgresql.1/src/backend/storage/lmgr/lmgr.c postgresql.2/src/backend/storage/lmgr/lmgr.c
*** postgresql.1/src/backend/storage/lmgr/lmgr.c 2012-04-16 19:57:22.459915733 +0200
--- postgresql.2/src/backend/storage/lmgr/lmgr.c 2012-05-07 19:39:39.844157432 +0200
***************
*** 19,26 ****
--- 19,29 ----
#include "access/transam.h"
#include "access/xact.h"
#include "catalog/catalog.h"
+ #include "catalog/pg_database.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
+ #include "utils/lsyscache.h"
+ #include "storage/proc.h"
#include "storage/procarray.h"
#include "utils/inval.h"
*************** LockRelationOid(Oid relid, LOCKMODE lock
*** 78,83 ****
--- 81,101 ----
res = LockAcquire(&tag, lockmode, false, false);
+ if (res == LOCKACQUIRE_NOT_AVAIL)
+ {
+ char *relname = get_rel_name(relid);
+ if (relname)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ relname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation with OID %u",
+ relid)));
+ }
+
/*
* Now that we have the lock, check for invalidation messages, so that we
* will update or flush any stale relcache entry before we try to use it.
*************** LockRelation(Relation relation, LOCKMODE
*** 174,179 ****
--- 192,203 ----
res = LockAcquire(&tag, lockmode, false, false);
+ if (res == LOCKACQUIRE_NOT_AVAIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ RelationGetRelationName(relation))));
+
/*
* Now that we have the lock, check for invalidation messages; see notes
* in LockRelationOid.
*************** LockRelationIdForSession(LockRelId *reli
*** 251,257 ****
SET_LOCKTAG_RELATION(tag, relid->dbId, relid->relId);
! (void) LockAcquire(&tag, lockmode, true, false);
}
/*
--- 275,294 ----
SET_LOCKTAG_RELATION(tag, relid->dbId, relid->relId);
! if (LockAcquire(&tag, lockmode, true, false) == LOCKACQUIRE_NOT_AVAIL)
! {
! char *relname = get_rel_name(relid->relId);
! if (relname)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on relation \"%s\"",
! relname)));
! else
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on relation with OID %u",
! relid->relId)));
! }
}
/*
*************** LockRelationForExtension(Relation relati
*** 286,292 ****
relation->rd_lockInfo.lockRelId.dbId,
relation->rd_lockInfo.lockRelId.relId);
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 323,333 ----
relation->rd_lockInfo.lockRelId.dbId,
relation->rd_lockInfo.lockRelId.relId);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on index \"%s\"",
! RelationGetRelationName(relation))));
}
/*
*************** LockPage(Relation relation, BlockNumber
*** 320,326 ****
relation->rd_lockInfo.lockRelId.relId,
blkno);
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 361,371 ----
relation->rd_lockInfo.lockRelId.relId,
blkno);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on page %u of relation \"%s\"",
! blkno, RelationGetRelationName(relation))));
}
/*
*************** LockTuple(Relation relation, ItemPointer
*** 376,382 ****
ItemPointerGetBlockNumber(tid),
ItemPointerGetOffsetNumber(tid));
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 421,431 ----
ItemPointerGetBlockNumber(tid),
ItemPointerGetOffsetNumber(tid));
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on row in relation \"%s\"",
! RelationGetRelationName(relation))));
}
/*
*************** XactLockTableInsert(TransactionId xid)
*** 430,436 ****
SET_LOCKTAG_TRANSACTION(tag, xid);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
}
/*
--- 479,488 ----
SET_LOCKTAG_TRANSACTION(tag, xid);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on transaction with ID %u", xid)));
}
/*
*************** XactLockTableWait(TransactionId xid)
*** 474,480 ****
SET_LOCKTAG_TRANSACTION(tag, xid);
! (void) LockAcquire(&tag, ShareLock, false, false);
LockRelease(&tag, ShareLock, false);
--- 526,535 ----
SET_LOCKTAG_TRANSACTION(tag, xid);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on transaction with ID %u", xid)));
LockRelease(&tag, ShareLock, false);
*************** LockDatabaseObject(Oid classid, Oid obji
*** 535,541 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, false, false);
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
--- 590,600 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
*************** LockSharedObject(Oid classid, Oid objid,
*** 576,582 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, false, false);
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
--- 635,645 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
*************** LockSharedObjectForSession(Oid classid,
*** 618,624 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, true, false);
}
/*
--- 681,702 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, true, false) == LOCKACQUIRE_NOT_AVAIL)
! switch(classid)
! {
! case DatabaseRelationId:
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on database with ID %u",
! objid)));
! break;
! default:
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
! break;
! }
}
/*
diff -dcrpN postgresql.1/src/backend/storage/lmgr/lock.c postgresql.2/src/backend/storage/lmgr/lock.c
*** postgresql.1/src/backend/storage/lmgr/lock.c 2012-05-05 13:22:40.776667140 +0200
--- postgresql.2/src/backend/storage/lmgr/lock.c 2012-05-07 19:44:13.166705948 +0200
*************** static PROCLOCK *SetupLockInTable(LockMe
*** 340,346 ****
static void GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner);
static void BeginStrongLockAcquire(LOCALLOCK *locallock, uint32 fasthashcode);
static void FinishStrongLockAcquire(void);
! static void WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner);
static void ReleaseLockIfHeld(LOCALLOCK *locallock, bool sessionLock);
static bool UnGrantLock(LOCK *lock, LOCKMODE lockmode,
PROCLOCK *proclock, LockMethod lockMethodTable);
--- 340,346 ----
static void GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner);
static void BeginStrongLockAcquire(LOCALLOCK *locallock, uint32 fasthashcode);
static void FinishStrongLockAcquire(void);
! static int WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner);
static void ReleaseLockIfHeld(LOCALLOCK *locallock, bool sessionLock);
static bool UnGrantLock(LOCK *lock, LOCKMODE lockmode,
PROCLOCK *proclock, LockMethod lockMethodTable);
*************** ProcLockHashCode(const PROCLOCKTAG *proc
*** 546,552 ****
* dontWait: if true, don't wait to acquire lock
*
* Returns one of:
! * LOCKACQUIRE_NOT_AVAIL lock not available, and dontWait=true
* LOCKACQUIRE_OK lock successfully acquired
* LOCKACQUIRE_ALREADY_HELD incremented count for lock already held
*
--- 546,552 ----
* dontWait: if true, don't wait to acquire lock
*
* Returns one of:
! * LOCKACQUIRE_NOT_AVAIL lock not available, either dontWait=true or timeout
* LOCKACQUIRE_OK lock successfully acquired
* LOCKACQUIRE_ALREADY_HELD incremented count for lock already held
*
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 856,862 ****
locktag->locktag_type,
lockmode);
! WaitOnLock(locallock, owner);
TRACE_POSTGRESQL_LOCK_WAIT_DONE(locktag->locktag_field1,
locktag->locktag_field2,
--- 856,862 ----
locktag->locktag_type,
lockmode);
! status = WaitOnLock(locallock, owner);
TRACE_POSTGRESQL_LOCK_WAIT_DONE(locktag->locktag_field1,
locktag->locktag_field2,
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 871,898 ****
* done when the lock was granted to us --- see notes in WaitOnLock.
*/
! /*
! * Check the proclock entry status, in case something in the ipc
! * communication doesn't work correctly.
! */
! if (!(proclock->holdMask & LOCKBIT_ON(lockmode)))
{
! AbortStrongLockAcquire();
! PROCLOCK_PRINT("LockAcquire: INCONSISTENT", proclock);
! LOCK_PRINT("LockAcquire: INCONSISTENT", lock, lockmode);
! /* Should we retry ? */
! LWLockRelease(partitionLock);
! elog(ERROR, "LockAcquire failed");
}
- PROCLOCK_PRINT("LockAcquire: granted", proclock);
- LOCK_PRINT("LockAcquire: granted", lock, lockmode);
}
! /*
! * Lock state is fully up-to-date now; if we error out after this, no
! * special error cleanup is required.
! */
! FinishStrongLockAcquire();
LWLockRelease(partitionLock);
--- 871,921 ----
* done when the lock was granted to us --- see notes in WaitOnLock.
*/
! switch (status)
{
! case STATUS_OK:
! /*
! * Check the proclock entry status, in case something in the ipc
! * communication doesn't work correctly.
! */
! if (!(proclock->holdMask & LOCKBIT_ON(lockmode)))
! {
! AbortStrongLockAcquire();
! PROCLOCK_PRINT("LockAcquire: INCONSISTENT", proclock);
! LOCK_PRINT("LockAcquire: INCONSISTENT", lock, lockmode);
! /* Should we retry ? */
! LWLockRelease(partitionLock);
! elog(ERROR, "LockAcquire failed");
! }
! PROCLOCK_PRINT("LockAcquire: granted", proclock);
! LOCK_PRINT("LockAcquire: granted", lock, lockmode);
! break;
! case STATUS_WAITING:
! PROCLOCK_PRINT("LockAcquire: timed out", proclock);
! LOCK_PRINT("LockAcquire: timed out", lock, lockmode);
! break;
! default:
! elog(ERROR, "LockAcquire invalid status");
! break;
}
}
! if (status == STATUS_WAITING)
! {
! /*
! * lock_timeout was set and WaitOnLock() indicated
! * we timed out. Clean up manually.
! */
! AbortStrongLockAcquire();
! }
! else
! {
! /*
! * Lock state is fully up-to-date now; if we error out after this, no
! * special error cleanup is required.
! */
! FinishStrongLockAcquire();
! }
LWLockRelease(partitionLock);
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 911,917 ****
locktag->locktag_field2);
}
! return LOCKACQUIRE_OK;
}
/*
--- 934,940 ----
locktag->locktag_field2);
}
! return (status == STATUS_OK ? LOCKACQUIRE_OK : LOCKACQUIRE_NOT_AVAIL);
}
/*
*************** GrantAwaitedLock(void)
*** 1429,1442 ****
* Caller must have set MyProc->heldLocks to reflect locks already held
* on the lockable object by this process.
*
* The appropriate partition lock must be held at entry.
*/
! static void
WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner)
{
LOCKMETHODID lockmethodid = LOCALLOCK_LOCKMETHOD(*locallock);
LockMethod lockMethodTable = LockMethods[lockmethodid];
char *volatile new_status = NULL;
LOCK_PRINT("WaitOnLock: sleeping on lock",
locallock->lock, locallock->tag.mode);
--- 1452,1471 ----
* Caller must have set MyProc->heldLocks to reflect locks already held
* on the lockable object by this process.
*
+ * Result: returns value of ProcSleep()
+ * STATUS_OK if we acquired the lock
+ * STATUS_ERROR if not (deadlock)
+ * STATUS_WAITING if not (timeout)
+ *
* The appropriate partition lock must be held at entry.
*/
! static int
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);
*************** WaitOnLock(LOCALLOCK *locallock, Resourc
*** 1478,1485 ****
*/
PG_TRY();
{
! if (ProcSleep(locallock, lockMethodTable) != STATUS_OK)
{
/*
* We failed as a result of a deadlock, see CheckDeadLock(). Quit
* now.
--- 1507,1519 ----
*/
PG_TRY();
{
! wait_status = ProcSleep(locallock, lockMethodTable);
! switch (wait_status)
{
+ case STATUS_OK:
+ case STATUS_WAITING:
+ break;
+ default:
/*
* We failed as a result of a deadlock, see CheckDeadLock(). Quit
* now.
*************** WaitOnLock(LOCALLOCK *locallock, Resourc
*** 1524,1531 ****
pfree(new_status);
}
! LOCK_PRINT("WaitOnLock: wakeup on lock",
locallock->lock, locallock->tag.mode);
}
/*
--- 1558,1571 ----
pfree(new_status);
}
! 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);
+
+ return wait_status;
}
/*
*************** VirtualXactLock(VirtualTransactionId vxi
*** 3843,3849 ****
LWLockRelease(proc->backendLock);
/* Time to wait. */
! (void) LockAcquire(&tag, ShareLock, false, false);
LockRelease(&tag, ShareLock, false);
return true;
--- 3883,3893 ----
LWLockRelease(proc->backendLock);
/* Time to wait. */
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on virtual transaction with ID %u",
! vxid.localTransactionId)));
LockRelease(&tag, ShareLock, false);
return true;
diff -dcrpN 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-05-07 19:39:07.966976479 +0200
--- postgresql.2/src/backend/storage/lmgr/proc.c 2012-05-07 19:39:39.846157444 +0200
*************** LockErrorCleanup(void)
*** 637,644 ****
if (lockAwaited == NULL)
return;
! /* Turn off the deadlock timer, if it's still running (see ProcSleep) */
disable_timeout(DEADLOCK_TIMEOUT, false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
--- 637,648 ----
if (lockAwaited == NULL)
return;
! /*
! * Turn off the deadlock and lock timeout timers,
! * if they are still running (see ProcSleep)
! */
disable_timeout(DEADLOCK_TIMEOUT, false);
+ disable_timeout(LOCK_TIMEOUT, false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
*************** ProcQueueInit(PROC_QUEUE *queue)
*** 874,880 ****
* 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).
*
* ASSUME: that no one will fiddle with the queue until after
* we release the partition lock.
--- 878,887 ----
* 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)
! * STATUS_WAITING if not (timeout)
*
* ASSUME: that no one will fiddle with the queue until after
* we release the partition lock.
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 896,901 ****
--- 903,909 ----
LOCKMASK myHeldLocks = MyProc->heldLocks;
bool early_deadlock = false;
bool allow_autovacuum_cancel = true;
+ bool timeout_detected = false;
int myWaitStatus;
PGPROC *proc;
int i;
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1037,1044 ****
elog(FATAL, "could not set timer for process wakeup");
/*
! * 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
* saved wakeup might be leftover from a previous operation (for example,
* we aborted ProcWaitForSignal just before someone did ProcSendSignal).
--- 1045,1058 ----
elog(FATAL, "could not set timer for process wakeup");
/*
! * Queue the timer for lock timeout, too.
! */
! if (!enable_timeout(LOCK_TIMEOUT, LockTimeout))
! elog(FATAL, "could not set timer for process wakeup");
!
! /*
! * 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).
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1055,1061 ****
*/
do
{
! PGSemaphoreLock(&MyProc->sem, true);
/*
* waitStatus could change from STATUS_WAITING to something else
--- 1069,1080 ----
*/
do
{
! PGSemaphoreTimedLock(&MyProc->sem, true);
!
! /* Check and keep the lock timeout indicator for later checks */
! timeout_detected = get_timeout_indicator(LOCK_TIMEOUT);
! if (timeout_detected)
! break;
/*
* waitStatus could change from STATUS_WAITING to something else
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1184,1195 ****
} while (myWaitStatus == STATUS_WAITING);
/*
! * Disable the timer, if it's still running
*/
if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for process wakeup");
/*
* Re-acquire the lock table's partition lock. We have to do this to hold
* off cancel/die interrupts before we can mess with lockAwaited (else we
* might have a missed or duplicated locallock update).
--- 1203,1220 ----
} while (myWaitStatus == STATUS_WAITING);
/*
! * Disable the deadlock timer, if it's still running
*/
if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for process wakeup");
/*
+ * Disable the lock timeout timer, if it's still running
+ */
+ if (!disable_timeout(LOCK_TIMEOUT, false))
+ elog(FATAL, "could not disable timer for process wakeup");
+
+ /*
* Re-acquire the lock table's partition lock. We have to do this to hold
* off cancel/die interrupts before we can mess with lockAwaited (else we
* might have a missed or duplicated locallock update).
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1197,1202 ****
--- 1222,1236 ----
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;
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1210,1217 ****
/*
* We don't have to do anything else, because the awaker did all the
* necessary update of the lock table and MyProc.
*/
! return MyProc->waitStatus;
}
--- 1244,1253 ----
/*
* 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 (timeout_detected ? STATUS_WAITING : MyProc->waitStatus);
}
diff -dcrpN postgresql.1/src/backend/storage/lmgr/timeout.c postgresql.2/src/backend/storage/lmgr/timeout.c
*** postgresql.1/src/backend/storage/lmgr/timeout.c 2012-05-07 19:39:07.966976479 +0200
--- postgresql.2/src/backend/storage/lmgr/timeout.c 2012-05-07 19:39:39.846157444 +0200
***************
*** 34,39 ****
--- 34,40 ----
/* GUC variables */
int DeadlockTimeout = 1000;
int StatementTimeout = 0;
+ int LockTimeout = 0;
/*
* This is used by ProcSleep() in proc.c
*************** static void InitDeadLock(TimestampTz sta
*** 50,55 ****
--- 51,60 ----
static void DestroyDeadLock(bool keep_indicator);
static bool CheckDeadLock(void);
+ static void InitLockTimeout(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyLockTimeout(bool keep_indicator);
+ static bool CheckLockTimeout(void);
+
static void InitStatementTimeout(TimestampTz start_time, TimestampTz fin_time);
static void DestroyStatementTimeout(bool keep_indicator);
static bool CheckStatementTimeout(void);
*************** static timeout_params base_timeouts[TIME
*** 98,103 ****
--- 103,115 ----
},
{
+ LOCK_TIMEOUT, false, false,
+ InitLockTimeout, DestroyLockTimeout,
+ CheckLockTimeout, GetCurrentTimestamp,
+ 0
+ },
+
+ {
STATEMENT_TIMEOUT, false, false,
InitStatementTimeout, DestroyStatementTimeout,
CheckStatementTimeout, GetCurrentStatementStartTimestamp,
*************** check_done:
*** 380,385 ****
--- 392,427 ----
return true;
}
+ /*
+ * Functions to manage lock timeout
+ */
+
+ static void
+ InitLockTimeout(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(LOCK_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyLockTimeout(bool keep_indicator)
+ {
+ DestroyTimeout(LOCK_TIMEOUT, keep_indicator);
+ }
+
+ static bool
+ CheckLockTimeout(void)
+ {
+ TimestampTz now;
+
+ now = GetCurrentTimestamp();
+
+ if (now < base_timeouts[LOCK_TIMEOUT].fin_time)
+ return false;
+
+ base_timeouts[LOCK_TIMEOUT].indicator = true;
+ return true;
+ }
+
/*
* Functions to manage statement timeout
*/
diff -dcrpN postgresql.1/src/backend/utils/adt/lockfuncs.c postgresql.2/src/backend/utils/adt/lockfuncs.c
*** postgresql.1/src/backend/utils/adt/lockfuncs.c 2012-04-16 19:57:22.473915895 +0200
--- postgresql.2/src/backend/utils/adt/lockfuncs.c 2012-05-07 19:39:39.848157456 +0200
*************** pg_advisory_lock_int8(PG_FUNCTION_ARGS)
*** 421,427 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ExclusiveLock, true, false);
PG_RETURN_VOID();
}
--- 421,431 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ExclusiveLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_int8(PG_FUNCTION_A
*** 438,444 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
PG_RETURN_VOID();
}
--- 442,452 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_shared_int8(PG_FUNCTION
*** 454,460 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ShareLock, true, false);
PG_RETURN_VOID();
}
--- 462,472 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ShareLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_shared_int8(PG_FUN
*** 471,477 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ShareLock, false, false);
PG_RETURN_VOID();
}
--- 483,493 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_int4(PG_FUNCTION_ARGS)
*** 604,610 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ExclusiveLock, true, false);
PG_RETURN_VOID();
}
--- 620,630 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ExclusiveLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_int4(PG_FUNCTION_A
*** 622,628 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
PG_RETURN_VOID();
}
--- 642,652 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_shared_int4(PG_FUNCTION
*** 639,645 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ShareLock, true, false);
PG_RETURN_VOID();
}
--- 663,673 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ShareLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_shared_int4(PG_FUN
*** 657,663 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ShareLock, false, false);
PG_RETURN_VOID();
}
--- 685,695 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
diff -dcrpN 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-05-07 19:39:07.970976505 +0200
--- postgresql.2/src/backend/utils/misc/guc.c 2012-05-07 19:39:39.852157478 +0200
*************** static struct config_int ConfigureNamesI
*** 1861,1866 ****
--- 1861,1877 ----
},
{
+ {"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 -dcrpN 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-04-30 08:18:06.651737938 +0200
--- postgresql.2/src/backend/utils/misc/postgresql.conf.sample 2012-05-07 19:39:39.853157484 +0200
***************
*** 528,533 ****
--- 528,536 ----
#------------------------------------------------------------------------------
#deadlock_timeout = 1s
+ #lock_timeout = 0 # timeout value for heavy-weight locks
+ # taken by statements. 0 disables timeout
+ # unit in milliseconds, default is 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 -dcrpN postgresql.1/src/include/storage/pg_sema.h postgresql.2/src/include/storage/pg_sema.h
*** postgresql.1/src/include/storage/pg_sema.h 2012-04-16 19:57:22.672918205 +0200
--- postgresql.2/src/include/storage/pg_sema.h 2012-05-07 19:39:39.854157490 +0200
*************** extern void PGSemaphoreUnlock(PGSemaphor
*** 80,83 ****
--- 80,89 ----
/* 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 void PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK);
+
#endif /* PG_SEMA_H */
diff -dcrpN postgresql.1/src/include/storage/timeout.h postgresql.2/src/include/storage/timeout.h
*** postgresql.1/src/include/storage/timeout.h 2012-05-07 19:39:07.971976511 +0200
--- postgresql.2/src/include/storage/timeout.h 2012-05-07 19:39:39.854157490 +0200
***************
*** 19,27 ****
--- 19,29 ----
/* configurable options */
extern int DeadlockTimeout;
extern int StatementTimeout;
+ extern int LockTimeout;
typedef enum TimeoutName {
DEADLOCK_TIMEOUT,
+ LOCK_TIMEOUT,
STATEMENT_TIMEOUT,
STANDBY_DEADLOCK_TIMEOUT,
STANDBY_TIMEOUT,
diff -dcrpN 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-05-07 19:39:39.855157495 +0200
*************** set statement_timeout to 2000;
*** 198,203 ****
--- 198,210 ----
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: could not obtain lock on relation "pxtest3"
+ LINE 1: SELECT * FROM pxtest3;
+ ^
+ reset lock_timeout;
-- Disconnect, we will continue testing in a different backend
\c -
-- There should still be two prepared transactions
*************** set statement_timeout to 2000;
*** 213,218 ****
--- 220,232 ----
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: could not obtain lock on relation "pxtest3"
+ LINE 1: SELECT * FROM pxtest3;
+ ^
+ reset lock_timeout;
-- Commit table creation
COMMIT PREPARED 'regress-one';
\d pxtest2
diff -dcrpN 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-05-07 19:39:39.855157495 +0200
*************** set statement_timeout to 2000;
*** 126,131 ****
--- 126,136 ----
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 -
*************** set statement_timeout to 2000;
*** 137,142 ****
--- 142,152 ----
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
Hi,
another rebasing and applied the GIT changes in
ada8fa08fc6cf5f199b6df935b4d0a730aaa4fec to the
Windows implementation of PGSemaphoreTimedLock.
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/
Attachments:
1-timeout-framework-v5.patchtext/x-patch; name=1-timeout-framework-v5.patchDownload
diff -dcrpN postgresql/src/backend/postmaster/autovacuum.c postgresql.1/src/backend/postmaster/autovacuum.c
*** postgresql/src/backend/postmaster/autovacuum.c 2012-05-11 09:23:17.033668629 +0200
--- postgresql.1/src/backend/postmaster/autovacuum.c 2012-05-11 09:32:17.075682608 +0200
***************
*** 86,94 ****
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
- #include "storage/proc.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "tcop/tcopprot.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
--- 86,94 ----
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
+ #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
*************** AutoVacLauncherMain(int argc, char *argv
*** 484,490 ****
/* Forget any pending QueryCancel request */
QueryCancelPending = false;
! disable_sig_alarm(true);
QueryCancelPending = false; /* again in case timeout occurred */
/* Report the error to the server log */
--- 484,490 ----
/* Forget any pending QueryCancel request */
QueryCancelPending = false;
! disable_all_timeouts(false);
QueryCancelPending = false; /* again in case timeout occurred */
/* Report the error to the server log */
diff -dcrpN postgresql/src/backend/postmaster/postmaster.c postgresql.1/src/backend/postmaster/postmaster.c
*** postgresql/src/backend/postmaster/postmaster.c 2012-04-23 08:28:35.641057679 +0200
--- postgresql.1/src/backend/postmaster/postmaster.c 2012-05-11 09:32:17.079682630 +0200
***************
*** 112,118 ****
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
! #include "storage/proc.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/datetime.h"
--- 112,118 ----
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
! #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/datetime.h"
*************** BackendInitialize(Port *port)
*** 3471,3479 ****
* Ready to begin client interaction. We will give up and exit(1) after a
* time delay, so that a broken client can't hog a connection
* indefinitely. PreAuthDelay and any DNS interactions above don't count
! * against the time limit.
*/
! if (!enable_sig_alarm(AuthenticationTimeout * 1000, false))
elog(FATAL, "could not set timer for startup packet timeout");
/*
--- 3471,3479 ----
* Ready to begin client interaction. We will give up and exit(1) after a
* time delay, so that a broken client can't hog a connection
* indefinitely. PreAuthDelay and any DNS interactions above don't count
! * against the time limit. Use the deadlock timeout interface.
*/
! if (!enable_timeout(DEADLOCK_TIMEOUT, AuthenticationTimeout * 1000))
elog(FATAL, "could not set timer for startup packet timeout");
/*
*************** BackendInitialize(Port *port)
*** 3511,3517 ****
/*
* Disable the timeout, and prevent SIGTERM/SIGQUIT again.
*/
! if (!disable_sig_alarm(false))
elog(FATAL, "could not disable timer for startup packet timeout");
PG_SETMASK(&BlockSig);
}
--- 3511,3517 ----
/*
* Disable the timeout, and prevent SIGTERM/SIGQUIT again.
*/
! if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for startup packet timeout");
PG_SETMASK(&BlockSig);
}
diff -dcrpN postgresql/src/backend/postmaster/startup.c postgresql.1/src/backend/postmaster/startup.c
*** postgresql/src/backend/postmaster/startup.c 2012-04-16 19:57:22.442915536 +0200
--- postgresql.1/src/backend/postmaster/startup.c 2012-05-11 09:32:17.080682636 +0200
***************
*** 27,33 ****
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
! #include "storage/proc.h"
#include "utils/guc.h"
--- 27,33 ----
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
! #include "storage/timeout.h"
#include "utils/guc.h"
*************** StartupProcessMain(void)
*** 195,202 ****
pqsignal(SIGTERM, StartupProcShutdownHandler); /* request shutdown */
pqsignal(SIGQUIT, startupproc_quickdie); /* hard crash time */
if (EnableHotStandby)
! pqsignal(SIGALRM, handle_standby_sig_alarm); /* ignored unless
! * InHotStandby */
else
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
--- 195,202 ----
pqsignal(SIGTERM, StartupProcShutdownHandler); /* request shutdown */
pqsignal(SIGQUIT, startupproc_quickdie); /* hard crash time */
if (EnableHotStandby)
! pqsignal(SIGALRM, handle_sig_alarm); /* ignored unless
! * InHotStandby */
else
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
diff -dcrpN postgresql/src/backend/storage/ipc/standby.c postgresql.1/src/backend/storage/ipc/standby.c
*** postgresql/src/backend/storage/ipc/standby.c 2012-05-02 17:36:48.984503204 +0200
--- postgresql.1/src/backend/storage/ipc/standby.c 2012-05-11 09:32:17.124682881 +0200
***************
*** 23,32 ****
#include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
- #include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/sinvaladt.h"
#include "storage/standby.h"
#include "utils/ps_status.h"
#include "utils/timestamp.h"
--- 23,32 ----
#include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
#include "storage/procarray.h"
#include "storage/sinvaladt.h"
#include "storage/standby.h"
+ #include "storage/timeout.h"
#include "utils/ps_status.h"
#include "utils/timestamp.h"
*************** ResolveRecoveryConflictWithLock(Oid dbOi
*** 394,400 ****
void
ResolveRecoveryConflictWithBufferPin(void)
{
- bool sig_alarm_enabled = false;
TimestampTz ltime;
TimestampTz now;
--- 394,399 ----
*************** ResolveRecoveryConflictWithBufferPin(voi
*** 409,417 ****
* We're willing to wait forever for conflicts, so set timeout for
* deadlock check (only)
*/
! if (enable_standby_sig_alarm(now, now, true))
! sig_alarm_enabled = true;
! else
elog(FATAL, "could not set timer for process wakeup");
}
else if (now >= ltime)
--- 408,414 ----
* We're willing to wait forever for conflicts, so set timeout for
* deadlock check (only)
*/
! if (!enable_timeout(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout))
elog(FATAL, "could not set timer for process wakeup");
}
else if (now >= ltime)
*************** ResolveRecoveryConflictWithBufferPin(voi
*** 423,446 ****
}
else
{
/*
* Wake up at ltime, and check for deadlocks as well if we will be
* waiting longer than deadlock_timeout
*/
! if (enable_standby_sig_alarm(now, ltime, false))
! sig_alarm_enabled = true;
! else
elog(FATAL, "could not set timer for process wakeup");
}
/* Wait to be signaled by UnpinBuffer() */
ProcWaitForSignal();
! if (sig_alarm_enabled)
! {
! if (!disable_standby_sig_alarm())
! elog(FATAL, "could not disable timer for process wakeup");
! }
}
void
--- 420,447 ----
}
else
{
+ long secs, msecs;
+ int usecs;
+
/*
* Wake up at ltime, and check for deadlocks as well if we will be
* waiting longer than deadlock_timeout
*/
! if (!enable_timeout(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout))
! elog(FATAL, "could not set timer for process wakeup");
!
! TimestampDifference(now, ltime, &secs, &usecs);
! msecs = secs * 1000 + usecs / 1000;
!
! if (!enable_timeout(STANDBY_TIMEOUT, msecs))
elog(FATAL, "could not set timer for process wakeup");
}
/* Wait to be signaled by UnpinBuffer() */
ProcWaitForSignal();
! if (!disable_all_timeouts(false))
! elog(FATAL, "could not disable timer for process wakeup");
}
void
diff -dcrpN postgresql/src/backend/storage/lmgr/Makefile postgresql.1/src/backend/storage/lmgr/Makefile
*** postgresql/src/backend/storage/lmgr/Makefile 2012-04-16 19:57:22.458915722 +0200
--- postgresql.1/src/backend/storage/lmgr/Makefile 2012-05-11 09:32:17.134682937 +0200
*************** subdir = src/backend/storage/lmgr
*** 12,18 ****
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
! OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o predicate.o
include $(top_srcdir)/src/backend/common.mk
--- 12,18 ----
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
! OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o predicate.o timeout.o
include $(top_srcdir)/src/backend/common.mk
diff -dcrpN postgresql/src/backend/storage/lmgr/proc.c postgresql.1/src/backend/storage/lmgr/proc.c
*** postgresql/src/backend/storage/lmgr/proc.c 2012-05-10 06:03:07.748716749 +0200
--- postgresql.1/src/backend/storage/lmgr/proc.c 2012-05-11 09:32:17.147683010 +0200
***************
*** 48,59 ****
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/spin.h"
#include "utils/timestamp.h"
/* GUC variables */
- int DeadlockTimeout = 1000;
- int StatementTimeout = 0;
bool log_lock_waits = false;
/* Pointer to this process's PGPROC struct, if any */
--- 48,58 ----
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/spin.h"
+ #include "storage/timeout.h"
#include "utils/timestamp.h"
/* GUC variables */
bool log_lock_waits = false;
/* Pointer to this process's PGPROC struct, if any */
*************** PGPROC *PreparedXactProcs = NULL;
*** 77,103 ****
/* If we are waiting for a lock, this points to the associated LOCALLOCK */
static LOCALLOCK *lockAwaited = NULL;
! /* Mark these volatile because they can be changed by signal handler */
! static volatile bool standby_timeout_active = false;
! static volatile bool statement_timeout_active = false;
! static volatile bool deadlock_timeout_active = false;
! static volatile DeadLockState deadlock_state = DS_NOT_YET_CHECKED;
! volatile bool cancel_from_timeout = false;
!
! /* timeout_start_time is set when log_lock_waits is true */
! static TimestampTz timeout_start_time;
!
! /* statement_fin_time is valid only if statement_timeout_active is true */
! static TimestampTz statement_fin_time;
! static TimestampTz statement_fin_time2; /* valid only in recovery */
!
static void RemoveProcFromArray(int code, Datum arg);
static void ProcKill(int code, Datum arg);
static void AuxiliaryProcKill(int code, Datum arg);
- static bool CheckStatementTimeout(void);
- static bool CheckStandbyTimeout(void);
-
/*
* Report shared-memory space needed by InitProcGlobal.
--- 76,87 ----
/* If we are waiting for a lock, this points to the associated LOCALLOCK */
static LOCALLOCK *lockAwaited = NULL;
! /* Declared in timeout.c */
! extern volatile DeadLockState deadlock_state;
static void RemoveProcFromArray(int code, Datum arg);
static void ProcKill(int code, Datum arg);
static void AuxiliaryProcKill(int code, Datum arg);
/*
* Report shared-memory space needed by InitProcGlobal.
*************** LockErrorCleanup(void)
*** 658,664 ****
return;
/* Turn off the deadlock timer, if it's still running (see ProcSleep) */
! disable_sig_alarm(false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
--- 642,648 ----
return;
/* Turn off the deadlock timer, if it's still running (see ProcSleep) */
! disable_timeout(DEADLOCK_TIMEOUT, false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1053,1059 ****
* 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 (!enable_sig_alarm(DeadlockTimeout, false))
elog(FATAL, "could not set timer for process wakeup");
/*
--- 1037,1043 ----
* 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 (!enable_timeout(DEADLOCK_TIMEOUT, DeadlockTimeout))
elog(FATAL, "could not set timer for process wakeup");
/*
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1143,1149 ****
DescribeLockTag(&buf, &locallock->tag.lock);
modename = GetLockmodeName(locallock->tag.lock.locktag_lockmethodid,
lockmode);
! TimestampDifference(timeout_start_time, GetCurrentTimestamp(),
&secs, &usecs);
msecs = secs * 1000 + usecs / 1000;
usecs = usecs % 1000;
--- 1127,1134 ----
DescribeLockTag(&buf, &locallock->tag.lock);
modename = GetLockmodeName(locallock->tag.lock.locktag_lockmethodid,
lockmode);
! TimestampDifference(get_timeout_start(DEADLOCK_TIMEOUT),
! GetCurrentTimestamp(),
&secs, &usecs);
msecs = secs * 1000 + usecs / 1000;
usecs = usecs % 1000;
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1205,1211 ****
/*
* Disable the timer, if it's still running
*/
! if (!disable_sig_alarm(false))
elog(FATAL, "could not disable timer for process wakeup");
/*
--- 1190,1196 ----
/*
* Disable the timer, if it's still running
*/
! if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for process wakeup");
/*
*************** ProcLockWakeup(LockMethod lockMethodTabl
*** 1337,1457 ****
}
/*
- * CheckDeadLock
- *
- * We only get to this routine if we got SIGALRM after DeadlockTimeout
- * while waiting for a lock to be released by some other process. Look
- * to see if there's a deadlock; if not, just return and continue waiting.
- * (But signal ProcSleep to log a message, if log_lock_waits is true.)
- * If we have a real deadlock, remove ourselves from the lock's wait queue
- * and signal an error to ProcSleep.
- *
- * NB: this is run inside a signal handler, so be very wary about what is done
- * here or in called routines.
- */
- static void
- CheckDeadLock(void)
- {
- int i;
-
- /*
- * Acquire exclusive lock on the entire shared lock data structures. Must
- * grab LWLocks in partition-number order to avoid LWLock deadlock.
- *
- * Note that the deadlock check interrupt had better not be enabled
- * anywhere that this process itself holds lock partition locks, else this
- * will wait forever. Also note that LWLockAcquire creates a critical
- * section, so that this routine cannot be interrupted by cancel/die
- * interrupts.
- */
- for (i = 0; i < NUM_LOCK_PARTITIONS; i++)
- LWLockAcquire(FirstLockMgrLock + i, LW_EXCLUSIVE);
-
- /*
- * Check to see if we've been awoken by anyone in the interim.
- *
- * If we have, we can return and resume our transaction -- happy day.
- * Before we are awoken the process releasing the lock grants it to us so
- * we know that we don't have to wait anymore.
- *
- * We check by looking to see if we've been unlinked from the wait queue.
- * This is quicker than checking our semaphore's state, since no kernel
- * call is needed, and it is safe because we hold the lock partition lock.
- */
- if (MyProc->links.prev == NULL ||
- MyProc->links.next == NULL)
- goto check_done;
-
- #ifdef LOCK_DEBUG
- if (Debug_deadlocks)
- DumpAllLocks();
- #endif
-
- /* Run the deadlock check, and set deadlock_state for use by ProcSleep */
- deadlock_state = DeadLockCheck(MyProc);
-
- if (deadlock_state == DS_HARD_DEADLOCK)
- {
- /*
- * Oops. We have a deadlock.
- *
- * Get this process out of wait state. (Note: we could do this more
- * efficiently by relying on lockAwaited, but use this coding to
- * preserve the flexibility to kill some other transaction than the
- * one detecting the deadlock.)
- *
- * RemoveFromWaitQueue sets MyProc->waitStatus to STATUS_ERROR, so
- * ProcSleep will report an error after we return from the signal
- * handler.
- */
- Assert(MyProc->waitLock != NULL);
- RemoveFromWaitQueue(MyProc, LockTagHashCode(&(MyProc->waitLock->tag)));
-
- /*
- * Unlock my semaphore so that the interrupted ProcSleep() call can
- * finish.
- */
- PGSemaphoreUnlock(&MyProc->sem);
-
- /*
- * We're done here. Transaction abort caused by the error that
- * ProcSleep will raise will cause any other locks we hold to be
- * released, thus allowing other processes to wake up; we don't need
- * to do that here. NOTE: an exception is that releasing locks we
- * hold doesn't consider the possibility of waiters that were blocked
- * behind us on the lock we just failed to get, and might now be
- * wakable because we're not in front of them anymore. However,
- * RemoveFromWaitQueue took care of waking up any such processes.
- */
- }
- else if (log_lock_waits || deadlock_state == DS_BLOCKED_BY_AUTOVACUUM)
- {
- /*
- * Unlock my semaphore so that the interrupted ProcSleep() call can
- * print the log message (we daren't do it here because we are inside
- * a signal handler). It will then sleep again until someone releases
- * the lock.
- *
- * If blocked by autovacuum, this wakeup will enable ProcSleep to send
- * the canceling signal to the autovacuum worker.
- */
- PGSemaphoreUnlock(&MyProc->sem);
- }
-
- /*
- * And release locks. We do this in reverse order for two reasons: (1)
- * Anyone else who needs more than one of the locks will be trying to lock
- * them in increasing order; we don't want to release the other process
- * until it can get all the locks it needs. (2) This avoids O(N^2)
- * behavior inside LWLockRelease.
- */
- check_done:
- for (i = NUM_LOCK_PARTITIONS; --i >= 0;)
- LWLockRelease(FirstLockMgrLock + i);
- }
-
-
- /*
* ProcWaitForSignal - wait for a signal from another backend.
*
* This can share the semaphore normally used for waiting for locks,
--- 1322,1327 ----
*************** ProcSendSignal(int pid)
*** 1503,1903 ****
if (proc != NULL)
PGSemaphoreUnlock(&proc->sem);
}
-
-
- /*****************************************************************************
- * SIGALRM interrupt support
- *
- * Maybe these should be in pqsignal.c?
- *****************************************************************************/
-
- /*
- * Enable the SIGALRM interrupt to fire after the specified delay
- *
- * Delay is given in milliseconds. Caller should be sure a SIGALRM
- * signal handler is installed before this is called.
- *
- * This code properly handles nesting of deadlock timeout alarms within
- * statement timeout alarms.
- *
- * Returns TRUE if okay, FALSE on failure.
- */
- bool
- enable_sig_alarm(int delayms, bool is_statement_timeout)
- {
- TimestampTz fin_time;
- struct itimerval timeval;
-
- if (is_statement_timeout)
- {
- /*
- * Begin statement-level timeout
- *
- * Note that we compute statement_fin_time with reference to the
- * statement_timestamp, but apply the specified delay without any
- * correction; that is, we ignore whatever time has elapsed since
- * statement_timestamp was set. In the normal case only a small
- * interval will have elapsed and so this doesn't matter, but there
- * are corner cases (involving multi-statement query strings with
- * embedded COMMIT or ROLLBACK) where we might re-initialize the
- * statement timeout long after initial receipt of the message. In
- * such cases the enforcement of the statement timeout will be a bit
- * inconsistent. This annoyance is judged not worth the cost of
- * performing an additional gettimeofday() here.
- */
- Assert(!deadlock_timeout_active);
- fin_time = GetCurrentStatementStartTimestamp();
- fin_time = TimestampTzPlusMilliseconds(fin_time, delayms);
- statement_fin_time = fin_time;
- cancel_from_timeout = false;
- statement_timeout_active = true;
- }
- else if (statement_timeout_active)
- {
- /*
- * Begin deadlock timeout with statement-level timeout active
- *
- * Here, we want to interrupt at the closer of the two timeout times.
- * If fin_time >= statement_fin_time then we need not touch the
- * existing timer setting; else set up to interrupt at the deadlock
- * timeout time.
- *
- * NOTE: in this case it is possible that this routine will be
- * interrupted by the previously-set timer alarm. This is okay
- * because the signal handler will do only what it should do according
- * to the state variables. The deadlock checker may get run earlier
- * than normal, but that does no harm.
- */
- timeout_start_time = GetCurrentTimestamp();
- fin_time = TimestampTzPlusMilliseconds(timeout_start_time, delayms);
- deadlock_timeout_active = true;
- if (fin_time >= statement_fin_time)
- return true;
- }
- else
- {
- /* Begin deadlock timeout with no statement-level timeout */
- deadlock_timeout_active = true;
- /* GetCurrentTimestamp can be expensive, so only do it if we must */
- if (log_lock_waits)
- timeout_start_time = GetCurrentTimestamp();
- }
-
- /* If we reach here, okay to set the timer interrupt */
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = delayms / 1000;
- timeval.it_value.tv_usec = (delayms % 1000) * 1000;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- return true;
- }
-
- /*
- * Cancel the SIGALRM timer, either for a deadlock timeout or a statement
- * timeout. If a deadlock timeout is canceled, any active statement timeout
- * remains in force.
- *
- * Returns TRUE if okay, FALSE on failure.
- */
- bool
- disable_sig_alarm(bool is_statement_timeout)
- {
- /*
- * Always disable the interrupt if it is active; this avoids being
- * interrupted by the signal handler and thereby possibly getting
- * confused.
- *
- * We will re-enable the interrupt if necessary in CheckStatementTimeout.
- */
- if (statement_timeout_active || deadlock_timeout_active)
- {
- struct itimerval timeval;
-
- MemSet(&timeval, 0, sizeof(struct itimerval));
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- {
- statement_timeout_active = false;
- cancel_from_timeout = false;
- deadlock_timeout_active = false;
- return false;
- }
- }
-
- /* Always cancel deadlock timeout, in case this is error cleanup */
- deadlock_timeout_active = false;
-
- /* Cancel or reschedule statement timeout */
- if (is_statement_timeout)
- {
- statement_timeout_active = false;
- cancel_from_timeout = false;
- }
- else if (statement_timeout_active)
- {
- if (!CheckStatementTimeout())
- return false;
- }
- return true;
- }
-
-
- /*
- * Check for statement timeout. If the timeout time has come,
- * trigger a query-cancel interrupt; if not, reschedule the SIGALRM
- * interrupt to occur at the right time.
- *
- * Returns true if okay, false if failed to set the interrupt.
- */
- static bool
- CheckStatementTimeout(void)
- {
- TimestampTz now;
-
- if (!statement_timeout_active)
- return true; /* do nothing if not active */
-
- now = GetCurrentTimestamp();
-
- if (now >= statement_fin_time)
- {
- /* Time to die */
- statement_timeout_active = false;
- cancel_from_timeout = true;
- #ifdef HAVE_SETSID
- /* try to signal whole process group */
- kill(-MyProcPid, SIGINT);
- #endif
- kill(MyProcPid, SIGINT);
- }
- else
- {
- /* Not time yet, so (re)schedule the interrupt */
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
-
- /*
- * It's possible that the difference is less than a microsecond;
- * ensure we don't cancel, rather than set, the interrupt.
- */
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- }
-
- return true;
- }
-
-
- /*
- * Signal handler for SIGALRM for normal user backends
- *
- * Process deadlock check and/or statement timeout check, as needed.
- * To avoid various edge cases, we must be careful to do nothing
- * when there is nothing to be done. We also need to be able to
- * reschedule the timer interrupt if called before end of statement.
- */
- void
- handle_sig_alarm(SIGNAL_ARGS)
- {
- int save_errno = errno;
-
- /* SIGALRM is cause for waking anything waiting on the process latch */
- if (MyProc)
- SetLatch(&MyProc->procLatch);
-
- if (deadlock_timeout_active)
- {
- deadlock_timeout_active = false;
- CheckDeadLock();
- }
-
- if (statement_timeout_active)
- (void) CheckStatementTimeout();
-
- errno = save_errno;
- }
-
- /*
- * Signal handler for SIGALRM in Startup process
- *
- * To avoid various edge cases, we must be careful to do nothing
- * when there is nothing to be done. We also need to be able to
- * reschedule the timer interrupt if called before end of statement.
- *
- * We set either deadlock_timeout_active or statement_timeout_active
- * or both. Interrupts are enabled if standby_timeout_active.
- */
- bool
- enable_standby_sig_alarm(TimestampTz now, TimestampTz fin_time, bool deadlock_only)
- {
- TimestampTz deadlock_time = TimestampTzPlusMilliseconds(now,
- DeadlockTimeout);
-
- if (deadlock_only)
- {
- /*
- * Wake up at deadlock_time only, then wait forever
- */
- statement_fin_time = deadlock_time;
- deadlock_timeout_active = true;
- statement_timeout_active = false;
- }
- else if (fin_time > deadlock_time)
- {
- /*
- * Wake up at deadlock_time, then again at fin_time
- */
- statement_fin_time = deadlock_time;
- statement_fin_time2 = fin_time;
- deadlock_timeout_active = true;
- statement_timeout_active = true;
- }
- else
- {
- /*
- * Wake only at fin_time because its fairly soon
- */
- statement_fin_time = fin_time;
- deadlock_timeout_active = false;
- statement_timeout_active = true;
- }
-
- if (deadlock_timeout_active || statement_timeout_active)
- {
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- standby_timeout_active = true;
- }
-
- return true;
- }
-
- bool
- disable_standby_sig_alarm(void)
- {
- /*
- * Always disable the interrupt if it is active; this avoids being
- * interrupted by the signal handler and thereby possibly getting
- * confused.
- *
- * We will re-enable the interrupt if necessary in CheckStandbyTimeout.
- */
- if (standby_timeout_active)
- {
- struct itimerval timeval;
-
- MemSet(&timeval, 0, sizeof(struct itimerval));
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- {
- standby_timeout_active = false;
- return false;
- }
- }
-
- standby_timeout_active = false;
-
- return true;
- }
-
- /*
- * CheckStandbyTimeout() runs unconditionally in the Startup process
- * SIGALRM handler. Timers will only be set when InHotStandby.
- * We simply ignore any signals unless the timer has been set.
- */
- static bool
- CheckStandbyTimeout(void)
- {
- TimestampTz now;
- bool reschedule = false;
-
- standby_timeout_active = false;
-
- now = GetCurrentTimestamp();
-
- /*
- * Reschedule the timer if its not time to wake yet, or if we have both
- * timers set and the first one has just been reached.
- */
- if (now >= statement_fin_time)
- {
- if (deadlock_timeout_active)
- {
- /*
- * We're still waiting when we reach deadlock timeout, so send out
- * a request to have other backends check themselves for deadlock.
- * Then continue waiting until statement_fin_time, if that's set.
- */
- SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
- deadlock_timeout_active = false;
-
- /*
- * Begin second waiting period if required.
- */
- if (statement_timeout_active)
- {
- reschedule = true;
- statement_fin_time = statement_fin_time2;
- }
- }
- else
- {
- /*
- * We've now reached statement_fin_time, so ask all conflicts to
- * leave, so we can press ahead with applying changes in recovery.
- */
- SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
- }
- }
- else
- reschedule = true;
-
- if (reschedule)
- {
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- standby_timeout_active = true;
- }
-
- return true;
- }
-
- void
- handle_standby_sig_alarm(SIGNAL_ARGS)
- {
- int save_errno = errno;
-
- if (standby_timeout_active)
- (void) CheckStandbyTimeout();
-
- errno = save_errno;
- }
--- 1373,1375 ----
diff -dcrpN postgresql/src/backend/storage/lmgr/timeout.c postgresql.1/src/backend/storage/lmgr/timeout.c
*** postgresql/src/backend/storage/lmgr/timeout.c 1970-01-01 01:00:00.000000000 +0100
--- postgresql.1/src/backend/storage/lmgr/timeout.c 2012-05-11 09:32:17.159683077 +0200
***************
*** 0 ****
--- 1,680 ----
+ /*-------------------------------------------------------------------------
+ *
+ * timeout.c
+ * routines to manage timeout sources handled by SIGALRM
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/storage/lmgr/timeout.c
+ *
+ *-------------------------------------------------------------------------
+ */
+ /*
+ * Interface:
+ * enable_timeout(),
+ * disable_timeout(), disable_all_timeouts()
+ * get_timeout_indicator(), get_timeout_start()
+ * and
+ * handle_sig_alarm()
+ */
+ #include "postgres.h"
+
+ #include <sys/time.h>
+
+ #include "access/xact.h"
+ #include "miscadmin.h"
+ #include "storage/proc.h"
+ #include "storage/standby.h"
+ #include "storage/timeout.h"
+ #include "utils/timestamp.h"
+
+ /* GUC variables */
+ int DeadlockTimeout = 1000;
+ int StatementTimeout = 0;
+
+ /*
+ * This is used by ProcSleep() in proc.c
+ * Mark this volatile because it can be changed by the signal handler.
+ */
+ volatile DeadLockState deadlock_state = DS_NOT_YET_CHECKED;
+
+
+ /*
+ * Infrastructure for timeouts
+ */
+
+ static void InitDeadLock(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyDeadLock(bool keep_indicator);
+ static bool CheckDeadLock(void);
+
+ static void InitStatementTimeout(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyStatementTimeout(bool keep_indicator);
+ static bool CheckStatementTimeout(void);
+
+ static void InitStandbyDeadLock(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyStandbyDeadLock(bool keep_indicator);
+ static bool CheckStandbyDeadLock(void);
+
+ static void InitStandbyTimeout(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyStandbyTimeout(bool keep_indicator);
+ static bool CheckStandbyTimeout(void);
+
+ typedef void (*timeout_init)(TimestampTz, TimestampTz);
+ typedef void (*timeout_destroy)(bool);
+ typedef bool (*timeout_check)(void);
+ typedef TimestampTz (*timeout_start)(void);
+
+ typedef struct {
+ TimeoutName index;
+ bool resched_next;
+
+ /* volatile because it may be changed from the signal handler */
+ volatile bool indicator;
+
+ timeout_init timeout_init;
+ timeout_destroy timeout_destroy;
+ timeout_check timeout_check;
+ timeout_start timeout_start;
+
+ TimestampTz start_time;
+ TimestampTz fin_time;
+ } timeout_params;
+
+ /*
+ * List of possible timeout reasons in the order of enum TimeoutName.
+ * The priority of timeouts (in case two of them would trigger at the
+ * same time) is determined by this order: the earlier one in the list
+ * has higher priority.
+ */
+ static timeout_params base_timeouts[TIMEOUT_MAX] = {
+ {
+ DEADLOCK_TIMEOUT, true, false,
+ InitDeadLock, DestroyDeadLock,
+ CheckDeadLock, GetCurrentTimestamp,
+ 0
+ },
+
+ {
+ STATEMENT_TIMEOUT, false, false,
+ InitStatementTimeout, DestroyStatementTimeout,
+ CheckStatementTimeout, GetCurrentStatementStartTimestamp,
+ 0
+ },
+
+ {
+ STANDBY_DEADLOCK_TIMEOUT, true, false,
+ InitStandbyDeadLock, DestroyStandbyDeadLock,
+ CheckStandbyDeadLock, GetCurrentTimestamp,
+ 0
+ },
+
+ {
+ STANDBY_TIMEOUT, false, false,
+ InitStandbyTimeout, DestroyStandbyTimeout,
+ CheckStandbyTimeout, GetCurrentTimestamp,
+ 0
+ }
+ };
+
+ /*
+ * List of active timeouts ordered by their fin_time and priority.
+ */
+ static int n_timeouts = 0;
+ static timeout_params *timeouts[TIMEOUT_MAX];
+
+ /*****************************************************************************
+ * Internal helper functions
+ *****************************************************************************/
+
+ /*
+ * Find the index of a given timeout type in the active array
+ */
+ static int
+ find_active_timeout(TimeoutName tn)
+ {
+ int i;
+
+ for (i = 0; i < n_timeouts; i++)
+ {
+ if (timeouts[i]->index == tn)
+ return i;
+ }
+
+ return -1;
+ }
+
+ #define is_timeout_active(tn) (find_active_timeout(tn) >= 0)
+
+ /*
+ * Insert tn'th timeout into the list of timeouts at the given index
+ * if the previous timeout allows rescheduling the next one in the list.
+ */
+ static void
+ insert_timeout(TimeoutName tn, int index)
+ {
+ int i;
+
+ if (index > 0 && !timeouts[index-1]->resched_next)
+ return;
+
+ for (i = n_timeouts - 1; i >= index; i--)
+ timeouts[i+1] = timeouts[i];
+
+ timeouts[index] = &base_timeouts[tn];
+ n_timeouts++;
+ }
+
+ /*
+ * Remove the index'th element from the timeout list.
+ */
+ static void
+ remove_timeout_index(int index)
+ {
+ int i;
+
+ if (index < 0)
+ return;
+
+ for (i = index + 1; i < n_timeouts; i++)
+ timeouts[i-1] = timeouts[i];
+
+ if (n_timeouts > 0 && index < n_timeouts)
+ n_timeouts--;
+ }
+
+ /*
+ * (Re)schedule the next active timeout
+ */
+ static bool
+ schedule_timeout(TimestampTz now)
+ {
+ long secs;
+ int usecs;
+ struct itimerval timeval;
+
+ /* There is no active timeout, do nothing. */
+ if (n_timeouts == 0)
+ return true;
+
+ TimestampDifference(now, timeouts[0]->fin_time,
+ &secs, &usecs);
+
+ /*
+ * It's possible that the difference is less than a microsecond;
+ * ensure we don't cancel, rather than set, the interrupt.
+ */
+ if (secs == 0 && usecs == 0)
+ usecs = 1;
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ timeval.it_value.tv_sec = secs;
+ timeval.it_value.tv_usec = usecs;
+ if (setitimer(ITIMER_REAL, &timeval, NULL))
+ return false;
+ return true;
+ }
+
+ /*****************************************************************************
+ * Init, Destroy and Check functions for different timeouts.
+ *
+ * NB: all Check* functions are run inside a signal handler, so be very wary
+ * about what is done in them or in called routines.
+ *****************************************************************************/
+
+ /*
+ * Common Init and Destroy functions
+ */
+
+ static void
+ InitTimeout(TimeoutName tn, TimestampTz start_time, TimestampTz fin_time)
+ {
+ base_timeouts[tn].indicator = false;
+ base_timeouts[tn].start_time = start_time;
+ base_timeouts[tn].fin_time = fin_time;
+ }
+
+ static void
+ DestroyTimeout(TimeoutName tn, bool keep_indicator)
+ {
+ if (!keep_indicator)
+ base_timeouts[tn].indicator = false;
+ base_timeouts[tn].start_time = 0;
+ base_timeouts[tn].fin_time = 0;
+ }
+
+ /*
+ * Functions to manage deadlock
+ */
+
+ static void
+ InitDeadLock(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(DEADLOCK_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyDeadLock(bool keep_indicator)
+ {
+ DestroyTimeout(DEADLOCK_TIMEOUT, keep_indicator);
+ }
+
+ /*
+ * CheckDeadLock
+ *
+ * Look to see if there's a deadlock; if not, just return.
+ * (But signal ProcSleep to log a message, if log_lock_waits is true.)
+ * If we have a real deadlock, remove ourselves from the lock's wait queue
+ * and signal an error to ProcSleep.
+ */
+ static bool
+ CheckDeadLock(void)
+ {
+ TimestampTz now;
+ int i;
+
+ now = GetCurrentTimestamp();
+
+ /* If our time has not come yet, do nothing. */
+ if (now < base_timeouts[DEADLOCK_TIMEOUT].fin_time)
+ return false;
+
+ /*
+ * Acquire exclusive lock on the entire shared lock data structures. Must
+ * grab LWLocks in partition-number order to avoid LWLock deadlock.
+ *
+ * Note that the deadlock check interrupt had better not be enabled
+ * anywhere that this process itself holds lock partition locks, else this
+ * will wait forever. Also note that LWLockAcquire creates a critical
+ * section, so that this routine cannot be interrupted by cancel/die
+ * interrupts.
+ */
+ for (i = 0; i < NUM_LOCK_PARTITIONS; i++)
+ LWLockAcquire(FirstLockMgrLock + i, LW_EXCLUSIVE);
+
+ /*
+ * Check to see if we've been awoken by anyone in the interim.
+ *
+ * If we have, we can return and resume our transaction -- happy day.
+ * Before we are awoken the process releasing the lock grants it to us so
+ * we know that we don't have to wait anymore.
+ *
+ * We check by looking to see if we've been unlinked from the wait queue.
+ * This is quicker than checking our semaphore's state, since no kernel
+ * call is needed, and it is safe because we hold the lock partition lock.
+ */
+ if (MyProc->links.prev == NULL ||
+ MyProc->links.next == NULL)
+ goto check_done;
+
+ #ifdef LOCK_DEBUG
+ if (Debug_deadlocks)
+ DumpAllLocks();
+ #endif
+
+ /* Run the deadlock check, and set deadlock_state for use by ProcSleep */
+ deadlock_state = DeadLockCheck(MyProc);
+
+ if (deadlock_state == DS_HARD_DEADLOCK)
+ {
+ /*
+ * Oops. We have a deadlock.
+ *
+ * Get this process out of wait state. (Note: we could do this more
+ * efficiently by relying on lockAwaited, but use this coding to
+ * preserve the flexibility to kill some other transaction than the
+ * one detecting the deadlock.)
+ *
+ * RemoveFromWaitQueue sets MyProc->waitStatus to STATUS_ERROR, so
+ * ProcSleep will report an error after we return from the signal
+ * handler.
+ */
+ Assert(MyProc->waitLock != NULL);
+ RemoveFromWaitQueue(MyProc, LockTagHashCode(&(MyProc->waitLock->tag)));
+
+ /*
+ * Unlock my semaphore so that the interrupted ProcSleep() call can
+ * finish.
+ */
+ PGSemaphoreUnlock(&MyProc->sem);
+
+ /*
+ * We're done here. Transaction abort caused by the error that
+ * ProcSleep will raise will cause any other locks we hold to be
+ * released, thus allowing other processes to wake up; we don't need
+ * to do that here. NOTE: an exception is that releasing locks we
+ * hold doesn't consider the possibility of waiters that were blocked
+ * behind us on the lock we just failed to get, and might now be
+ * wakable because we're not in front of them anymore. However,
+ * RemoveFromWaitQueue took care of waking up any such processes.
+ */
+ }
+ else if (log_lock_waits || deadlock_state == DS_BLOCKED_BY_AUTOVACUUM)
+ {
+ /*
+ * Unlock my semaphore so that the interrupted ProcSleep() call can
+ * print the log message (we daren't do it here because we are inside
+ * a signal handler). It will then sleep again until someone releases
+ * the lock.
+ *
+ * If blocked by autovacuum, this wakeup will enable ProcSleep to send
+ * the canceling signal to the autovacuum worker.
+ */
+ PGSemaphoreUnlock(&MyProc->sem);
+ }
+
+ /*
+ * And release locks. We do this in reverse order for two reasons: (1)
+ * Anyone else who needs more than one of the locks will be trying to lock
+ * them in increasing order; we don't want to release the other process
+ * until it can get all the locks it needs. (2) This avoids O(N^2)
+ * behavior inside LWLockRelease.
+ */
+ check_done:
+ for (i = NUM_LOCK_PARTITIONS; --i >= 0;)
+ LWLockRelease(FirstLockMgrLock + i);
+
+ base_timeouts[DEADLOCK_TIMEOUT].indicator = true;
+
+ return true;
+ }
+
+ /*
+ * Functions to manage statement timeout
+ */
+
+ static void
+ InitStatementTimeout(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(STATEMENT_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyStatementTimeout(bool keep_indicator)
+ {
+ DestroyTimeout(STATEMENT_TIMEOUT, keep_indicator);
+ }
+
+ static bool
+ CheckStatementTimeout(void)
+ {
+ TimestampTz now;
+
+ now = GetCurrentTimestamp();
+
+ if (now < base_timeouts[STATEMENT_TIMEOUT].fin_time)
+ return false;
+
+ base_timeouts[STATEMENT_TIMEOUT].indicator = true;
+ #ifdef HAVE_SETSID
+ /* try to signal whole process group */
+ kill(-MyProcPid, SIGINT);
+ #endif
+ kill(MyProcPid, SIGINT);
+
+ return true;
+ }
+
+ /*
+ * Functions to manage standby deadlock functions
+ */
+
+ static void
+ InitStandbyDeadLock(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(STANDBY_DEADLOCK_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyStandbyDeadLock(bool keep_indicator)
+ {
+ DestroyTimeout(STANDBY_DEADLOCK_TIMEOUT, keep_indicator);
+ }
+
+ static bool
+ CheckStandbyDeadLock(void)
+ {
+ TimestampTz now;
+
+ now = GetCurrentTimestamp();
+
+ if (now < base_timeouts[STANDBY_DEADLOCK_TIMEOUT].fin_time)
+ return false;
+
+ base_timeouts[STANDBY_DEADLOCK_TIMEOUT].indicator = true;
+ SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
+ return true;
+ }
+
+ /*
+ * Functions to manage standby timeout
+ */
+
+ static void
+ InitStandbyTimeout(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(STANDBY_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyStandbyTimeout(bool keep_indicator)
+ {
+ DestroyTimeout(STANDBY_TIMEOUT, keep_indicator);
+ }
+
+ static bool
+ CheckStandbyTimeout(void)
+ {
+ TimestampTz now;
+
+ now = GetCurrentTimestamp();
+
+ if (now < base_timeouts[STANDBY_TIMEOUT].fin_time)
+ return false;
+
+ base_timeouts[STANDBY_TIMEOUT].indicator = true;
+ SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
+ return true;
+ }
+
+ /*****************************************************************************
+ * Public API
+ *****************************************************************************/
+
+
+ /*
+ * Enable the SIGALRM interrupt to fire after the specified delay
+ *
+ * Delay is given in milliseconds. Caller should be sure a SIGALRM
+ * signal handler is installed before this is called.
+ *
+ * This code properly handles nesting of different timeout alarms.
+ *
+ * Returns TRUE if okay, FALSE on failure to set the timer.
+ */
+ bool
+ enable_timeout(TimeoutName tn, int delayms)
+ {
+ TimestampTz start_time;
+ TimestampTz fin_time;
+ int i;
+
+ Assert(!is_timeout_active(tn));
+
+ if (delayms <= 0)
+ return true;
+
+ start_time = base_timeouts[tn].timeout_start();
+ fin_time = TimestampTzPlusMilliseconds(start_time, delayms);
+
+ /* Find out the index where to insert the new timeout. */
+ for (i = 0; i < n_timeouts; i++)
+ {
+ /*
+ * The new timeout triggers earlier than
+ * a previously added one: insert here.
+ */
+ if (fin_time < timeouts[i]->fin_time)
+ break;
+ /*
+ * The new timeout triggers at the same time
+ * as the previously added one but has greater priority.
+ */
+ if (fin_time == timeouts[i]->fin_time && tn < timeouts[i]->index)
+ break;
+ }
+
+ /*
+ * Initialize the timeout parameters
+ */
+ base_timeouts[tn].timeout_init(start_time, fin_time);
+
+ insert_timeout(tn, i);
+
+ if (i > 0)
+ return true;
+
+ /* If we reach here, okay to set the timer interrupt */
+ if (!schedule_timeout(start_time))
+ return false;
+ return true;
+ }
+
+ /*
+ * Cancel the SIGALRM timer for the specific timeout.
+ * If a timeout is canceled, any other active timeout remains in force.
+ *
+ * Returns TRUE if okay, FALSE on failure to set the timer for
+ * the next timeout source.
+ */
+ bool
+ disable_timeout(TimeoutName tn, bool keep_indicator)
+ {
+ int i;
+
+ /*
+ * Always disable the interrupt if it is active; this avoids being
+ * interrupted by the signal handler and thereby possibly getting
+ * confused.
+ *
+ * We will re-enable the interrupt if necessary in ->check_timeout().
+ */
+ if (n_timeouts > 0)
+ {
+ struct itimerval timeval;
+
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ if (setitimer(ITIMER_REAL, &timeval, NULL))
+ {
+ n_timeouts = 0;
+ for (i = 0; i < TIMEOUT_MAX; i++)
+ base_timeouts[i].timeout_destroy(false);
+
+ return false;
+ }
+ }
+
+ /* Find the timeout and remove from the list. */
+ i = find_active_timeout(tn);
+ remove_timeout_index(i);
+
+ /* Do cleanup. */
+ base_timeouts[tn].timeout_destroy(keep_indicator);
+
+ /*
+ * If the first timeout was removed from the list and there is
+ * at least one active left, reschedule it.
+ */
+ if (i == 0 && n_timeouts > 0)
+ if (!schedule_timeout(GetCurrentTimestamp()))
+ return false;
+
+ return true;
+ }
+
+ /*
+ * Disable SIGALRM and remove all timeouts from the list and
+ * reset the timeout indicators.
+ */
+ bool
+ disable_all_timeouts(bool keep_indicators)
+ {
+ struct itimerval timeval;
+ int i;
+ bool ret;
+
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ ret = (setitimer(ITIMER_REAL, &timeval, NULL) == 0);
+
+ n_timeouts = 0;
+ for (i = 0; i < TIMEOUT_MAX; i++)
+ base_timeouts[i].timeout_destroy(keep_indicators);
+
+ return ret;
+ }
+
+ /*
+ * Return the timeout indicator
+ */
+ bool
+ get_timeout_indicator(TimeoutName tn)
+ {
+ return base_timeouts[tn].indicator;
+ }
+
+ /*
+ * Return the start of the timer for this timeout
+ */
+ TimestampTz
+ get_timeout_start(TimeoutName tn)
+ {
+ return base_timeouts[tn].start_time;
+ }
+
+ /*
+ * Signal handler for SIGALRM
+ *
+ * Process the check for the currently active timeout source and
+ * reschedule the next as needed. To avoid various edge cases,
+ * we must be careful to do nothing when there is nothing to be done.
+ */
+ void
+ handle_sig_alarm(SIGNAL_ARGS)
+ {
+ int save_errno = errno;
+
+ /*
+ * SIGALRM is cause for waking anything waiting on the process latch.
+ * Recovery (the startup process) doesn't have MyProc set so
+ * it can also use this signal handler.
+ */
+ if (MyProc)
+ SetLatch(&MyProc->procLatch);
+
+ if (n_timeouts > 0)
+ {
+ bool ret;
+ bool reschedule = true;
+
+ ret = timeouts[0]->timeout_check();
+
+ /* Shift timeouts if the timeout was triggered */
+ if (ret)
+ {
+ reschedule = timeouts[0]->resched_next;
+ /*
+ * Short circuit disable_timeout(..., true) for the
+ * timeout source that just triggered.
+ */
+ remove_timeout_index(0);
+ }
+
+ if (reschedule)
+ schedule_timeout(GetCurrentTimestamp());
+ else
+ disable_all_timeouts(true);
+ }
+
+ errno = save_errno;
+ }
diff -dcrpN postgresql/src/backend/tcop/postgres.c postgresql.1/src/backend/tcop/postgres.c
*** postgresql/src/backend/tcop/postgres.c 2012-05-07 19:35:27.203721210 +0200
--- postgresql.1/src/backend/tcop/postgres.c 2012-05-11 09:32:17.172683149 +0200
***************
*** 64,69 ****
--- 64,70 ----
#include "storage/proc.h"
#include "storage/procsignal.h"
#include "storage/sinval.h"
+ #include "storage/timeout.h"
#include "tcop/fastpath.h"
#include "tcop/pquery.h"
#include "tcop/tcopprot.h"
*************** start_xact_command(void)
*** 2396,2404 ****
/* Set statement timeout running, if any */
/* NB: this mustn't be enabled until we are within an xact */
if (StatementTimeout > 0)
! enable_sig_alarm(StatementTimeout, true);
else
! cancel_from_timeout = false;
xact_started = true;
}
--- 2397,2405 ----
/* Set statement timeout running, if any */
/* NB: this mustn't be enabled until we are within an xact */
if (StatementTimeout > 0)
! enable_timeout(STATEMENT_TIMEOUT, StatementTimeout);
else
! disable_timeout(STATEMENT_TIMEOUT, false);
xact_started = true;
}
*************** finish_xact_command(void)
*** 2410,2416 ****
if (xact_started)
{
/* Cancel any active statement timeout before committing */
! disable_sig_alarm(true);
/* Now commit the command */
ereport(DEBUG3,
--- 2411,2417 ----
if (xact_started)
{
/* Cancel any active statement timeout before committing */
! disable_all_timeouts(false);
/* Now commit the command */
ereport(DEBUG3,
*************** ProcessInterrupts(void)
*** 2891,2897 ****
(errcode(ERRCODE_QUERY_CANCELED),
errmsg("canceling authentication due to timeout")));
}
! if (cancel_from_timeout)
{
ImmediateInterruptOK = false; /* not idle anymore */
DisableNotifyInterrupt();
--- 2892,2898 ----
(errcode(ERRCODE_QUERY_CANCELED),
errmsg("canceling authentication due to timeout")));
}
! if (get_timeout_indicator(STATEMENT_TIMEOUT))
{
ImmediateInterruptOK = false; /* not idle anymore */
DisableNotifyInterrupt();
*************** PostgresMain(int argc, char *argv[], con
*** 3796,3805 ****
/*
* Forget any pending QueryCancel request, since we're returning to
! * the idle loop anyway, and cancel the statement timer if running.
*/
QueryCancelPending = false;
! disable_sig_alarm(true);
QueryCancelPending = false; /* again in case timeout occurred */
/*
--- 3797,3806 ----
/*
* Forget any pending QueryCancel request, since we're returning to
! * the idle loop anyway, and cancel the timer if running.
*/
QueryCancelPending = false;
! disable_all_timeouts(false);
QueryCancelPending = false; /* again in case timeout occurred */
/*
diff -dcrpN postgresql/src/backend/utils/init/postinit.c postgresql.1/src/backend/utils/init/postinit.c
*** postgresql/src/backend/utils/init/postinit.c 2012-04-16 19:57:22.490916093 +0200
--- postgresql.1/src/backend/utils/init/postinit.c 2012-05-11 09:32:17.183683211 +0200
***************
*** 41,51 ****
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lmgr.h"
- #include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "storage/smgr.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
#include "utils/fmgroids.h"
--- 41,51 ----
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lmgr.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "storage/smgr.h"
+ #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
#include "utils/fmgroids.h"
*************** PerformAuthentication(Port *port)
*** 204,210 ****
* during authentication. Since we're inside a transaction and might do
* database access, we have to use the statement_timeout infrastructure.
*/
! if (!enable_sig_alarm(AuthenticationTimeout * 1000, true))
elog(FATAL, "could not set timer for authorization timeout");
/*
--- 204,210 ----
* during authentication. Since we're inside a transaction and might do
* database access, we have to use the statement_timeout infrastructure.
*/
! if (!enable_timeout(STATEMENT_TIMEOUT, AuthenticationTimeout * 1000))
elog(FATAL, "could not set timer for authorization timeout");
/*
*************** PerformAuthentication(Port *port)
*** 215,221 ****
/*
* Done with authentication. Disable the timeout, and log if needed.
*/
! if (!disable_sig_alarm(true))
elog(FATAL, "could not disable timer for authorization timeout");
if (Log_connections)
--- 215,221 ----
/*
* Done with authentication. Disable the timeout, and log if needed.
*/
! if (!disable_timeout(STATEMENT_TIMEOUT, false))
elog(FATAL, "could not disable timer for authorization timeout");
if (Log_connections)
diff -dcrpN postgresql/src/backend/utils/misc/guc.c postgresql.1/src/backend/utils/misc/guc.c
*** postgresql/src/backend/utils/misc/guc.c 2012-04-30 08:18:06.650737934 +0200
--- postgresql.1/src/backend/utils/misc/guc.c 2012-05-11 09:32:17.198683295 +0200
***************
*** 63,68 ****
--- 63,69 ----
#include "storage/standby.h"
#include "storage/fd.h"
#include "storage/predicate.h"
+ #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "tsearch/ts_cache.h"
#include "utils/builtins.h"
diff -dcrpN postgresql/src/include/storage/proc.h postgresql.1/src/include/storage/proc.h
*** postgresql/src/include/storage/proc.h 2012-05-10 06:03:07.768716871 +0200
--- postgresql.1/src/include/storage/proc.h 2012-05-11 09:32:17.209683356 +0200
*************** extern PGPROC *PreparedXactProcs;
*** 217,229 ****
/* configurable options */
- extern int DeadlockTimeout;
- extern int StatementTimeout;
extern bool log_lock_waits;
- extern volatile bool cancel_from_timeout;
-
-
/*
* Function Prototypes
*/
--- 217,224 ----
*************** extern void LockErrorCleanup(void);
*** 251,263 ****
extern void ProcWaitForSignal(void);
extern void ProcSendSignal(int pid);
- extern bool enable_sig_alarm(int delayms, bool is_statement_timeout);
- extern bool disable_sig_alarm(bool is_statement_timeout);
- extern void handle_sig_alarm(SIGNAL_ARGS);
-
- extern bool enable_standby_sig_alarm(TimestampTz now,
- TimestampTz fin_time, bool deadlock_only);
- extern bool disable_standby_sig_alarm(void);
- extern void handle_standby_sig_alarm(SIGNAL_ARGS);
-
#endif /* PROC_H */
--- 246,249 ----
diff -dcrpN postgresql/src/include/storage/timeout.h postgresql.1/src/include/storage/timeout.h
*** postgresql/src/include/storage/timeout.h 1970-01-01 01:00:00.000000000 +0100
--- postgresql.1/src/include/storage/timeout.h 2012-05-11 09:32:17.219683411 +0200
***************
*** 0 ****
--- 1,38 ----
+ /*-------------------------------------------------------------------------
+ *
+ * timeout.h
+ * SIGALRM timeout API
+ *
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/storage/timeout.h
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef _TIMEOUT_H_
+ #define _TIMEOUT_H_
+
+ #include "datatype/timestamp.h"
+
+ /* configurable options */
+ extern int DeadlockTimeout;
+ extern int StatementTimeout;
+
+ typedef enum TimeoutName {
+ DEADLOCK_TIMEOUT,
+ STATEMENT_TIMEOUT,
+ STANDBY_DEADLOCK_TIMEOUT,
+ STANDBY_TIMEOUT,
+ TIMEOUT_MAX
+ } TimeoutName;
+
+ extern bool enable_timeout(TimeoutName tn, int delayms);
+ extern bool disable_timeout(TimeoutName tn, bool keep_indicator);
+ extern bool disable_all_timeouts(bool keep_indicators);
+ extern bool get_timeout_indicator(TimeoutName tn);
+ extern TimestampTz get_timeout_start(TimeoutName tn);
+ extern void handle_sig_alarm(SIGNAL_ARGS);
+
+ #endif /* _TIMEOUT_H_ */
2-lock_timeout-v5.patchtext/x-patch; name=2-lock_timeout-v5.patchDownload
diff -dcrpN postgresql.1/doc/src/sgml/config.sgml postgresql.2/doc/src/sgml/config.sgml
*** postgresql.1/doc/src/sgml/config.sgml 2012-05-11 09:23:17.030668613 +0200
--- postgresql.2/doc/src/sgml/config.sgml 2012-05-11 09:32:47.331851602 +0200
*************** COPY postgres_log FROM '/full/path/to/lo
*** 4980,4986 ****
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.
</para>
<para>
--- 4980,4989 ----
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. 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>
*************** COPY postgres_log FROM '/full/path/to/lo
*** 4991,4996 ****
--- 4994,5027 ----
</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 has to wait more
+ than the specified number of milliseconds, starting from the time the
+ command arrives at the server from the client. If the statement involves
+ more than one such lock, the timeout applies to every one of them.
+ 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 to acquire.
+ 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 off the limitation.
+ </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 -dcrpN 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-05-11 09:32:47.332851607 +0200
*************** LOCK [ TABLE ] [ ONLY ] <replaceable cla
*** 39,46 ****
<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.)
</para>
--- 39,49 ----
<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. 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 -dcrpN 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-05-11 09:32:47.333851613 +0200
*************** FOR SHARE [ OF <replaceable class="param
*** 1199,1204 ****
--- 1199,1211 ----
</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 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 -dcrpN postgresql.1/src/backend/port/posix_sema.c postgresql.2/src/backend/port/posix_sema.c
*** postgresql.1/src/backend/port/posix_sema.c 2012-04-16 19:57:22.438915489 +0200
--- postgresql.2/src/backend/port/posix_sema.c 2012-05-11 09:32:47.333851613 +0200
***************
*** 24,29 ****
--- 24,30 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/timeout.h"
#ifdef USE_NAMED_POSIX_SEMAPHORES
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 313,315 ****
--- 314,341 ----
return true;
}
+
+ /*
+ * PGSemaphoreTimedLock
+ *
+ * Lock a semaphore (decrement count), blocking if count would be < 0
+ * Return if lock_timeout expired
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ int errStatus;
+
+ do
+ {
+ ImmediateInterruptOK = interruptOK;
+ CHECK_FOR_INTERRUPTS();
+ errStatus = sem_wait(PG_SEM_REF(sema));
+ ImmediateInterruptOK = false;
+ } while (errStatus < 0 && errno == EINTR && !get_timeout_indicator(LOCK_TIMEOUT));
+
+ if (get_timeout_indicator(LOCK_TIMEOUT))
+ return;
+ if (errStatus < 0)
+ elog(FATAL, "sem_wait failed: %m");
+ }
diff -dcrpN postgresql.1/src/backend/port/sysv_sema.c postgresql.2/src/backend/port/sysv_sema.c
*** postgresql.1/src/backend/port/sysv_sema.c 2012-04-16 19:57:22.438915489 +0200
--- postgresql.2/src/backend/port/sysv_sema.c 2012-05-11 09:32:47.334851620 +0200
***************
*** 30,35 ****
--- 30,36 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/timeout.h"
#ifndef HAVE_UNION_SEMUN
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 495,497 ****
--- 496,528 ----
return true;
}
+
+ /*
+ * PGSemaphoreTimedLock
+ *
+ * Lock a semaphore (decrement count), blocking if count would be < 0
+ * Return if lock_timeout expired
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ int errStatus;
+ 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;
+ } while (errStatus < 0 && errno == EINTR && !get_timeout_indicator(LOCK_TIMEOUT));
+
+ if (get_timeout_indicator(LOCK_TIMEOUT))
+ return;
+ if (errStatus < 0)
+ elog(FATAL, "semop(id=%d) failed: %m", sema->semId);
+ }
diff -dcrpN postgresql.1/src/backend/port/win32_sema.c postgresql.2/src/backend/port/win32_sema.c
*** postgresql.1/src/backend/port/win32_sema.c 2012-05-11 09:23:17.033668629 +0200
--- postgresql.2/src/backend/port/win32_sema.c 2012-05-11 09:35:24.747727078 +0200
***************
*** 16,21 ****
--- 16,22 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/timeout.h"
static HANDLE *mySemSet; /* IDs of sema sets acquired so far */
static int numSems; /* number of sema sets acquired so far */
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 209,211 ****
--- 210,271 ----
/* 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.
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ DWORD ret;
+ HANDLE wh[2];
+
+ /*
+ * 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;
+ } while (errno == EINTR && !get_timeout_indicator(LOCK_TIMEOUT));
+
+ if (get_timeout_indicator(LOCK_TIMEOUT))
+ return;
+ if (errno != 0)
+ ereport(FATAL,
+ (errmsg("could not lock semaphore: error code %d", (int) GetLastError())));
+ }
diff -dcrpN postgresql.1/src/backend/storage/lmgr/lmgr.c postgresql.2/src/backend/storage/lmgr/lmgr.c
*** postgresql.1/src/backend/storage/lmgr/lmgr.c 2012-04-16 19:57:22.459915733 +0200
--- postgresql.2/src/backend/storage/lmgr/lmgr.c 2012-05-11 09:32:47.336851632 +0200
***************
*** 19,26 ****
--- 19,29 ----
#include "access/transam.h"
#include "access/xact.h"
#include "catalog/catalog.h"
+ #include "catalog/pg_database.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
+ #include "utils/lsyscache.h"
+ #include "storage/proc.h"
#include "storage/procarray.h"
#include "utils/inval.h"
*************** LockRelationOid(Oid relid, LOCKMODE lock
*** 78,83 ****
--- 81,101 ----
res = LockAcquire(&tag, lockmode, false, false);
+ if (res == LOCKACQUIRE_NOT_AVAIL)
+ {
+ char *relname = get_rel_name(relid);
+ if (relname)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ relname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation with OID %u",
+ relid)));
+ }
+
/*
* Now that we have the lock, check for invalidation messages, so that we
* will update or flush any stale relcache entry before we try to use it.
*************** LockRelation(Relation relation, LOCKMODE
*** 174,179 ****
--- 192,203 ----
res = LockAcquire(&tag, lockmode, false, false);
+ if (res == LOCKACQUIRE_NOT_AVAIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ RelationGetRelationName(relation))));
+
/*
* Now that we have the lock, check for invalidation messages; see notes
* in LockRelationOid.
*************** LockRelationIdForSession(LockRelId *reli
*** 251,257 ****
SET_LOCKTAG_RELATION(tag, relid->dbId, relid->relId);
! (void) LockAcquire(&tag, lockmode, true, false);
}
/*
--- 275,294 ----
SET_LOCKTAG_RELATION(tag, relid->dbId, relid->relId);
! if (LockAcquire(&tag, lockmode, true, false) == LOCKACQUIRE_NOT_AVAIL)
! {
! char *relname = get_rel_name(relid->relId);
! if (relname)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on relation \"%s\"",
! relname)));
! else
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on relation with OID %u",
! relid->relId)));
! }
}
/*
*************** LockRelationForExtension(Relation relati
*** 286,292 ****
relation->rd_lockInfo.lockRelId.dbId,
relation->rd_lockInfo.lockRelId.relId);
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 323,333 ----
relation->rd_lockInfo.lockRelId.dbId,
relation->rd_lockInfo.lockRelId.relId);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on index \"%s\"",
! RelationGetRelationName(relation))));
}
/*
*************** LockPage(Relation relation, BlockNumber
*** 320,326 ****
relation->rd_lockInfo.lockRelId.relId,
blkno);
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 361,371 ----
relation->rd_lockInfo.lockRelId.relId,
blkno);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on page %u of relation \"%s\"",
! blkno, RelationGetRelationName(relation))));
}
/*
*************** LockTuple(Relation relation, ItemPointer
*** 376,382 ****
ItemPointerGetBlockNumber(tid),
ItemPointerGetOffsetNumber(tid));
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 421,431 ----
ItemPointerGetBlockNumber(tid),
ItemPointerGetOffsetNumber(tid));
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on row in relation \"%s\"",
! RelationGetRelationName(relation))));
}
/*
*************** XactLockTableInsert(TransactionId xid)
*** 430,436 ****
SET_LOCKTAG_TRANSACTION(tag, xid);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
}
/*
--- 479,488 ----
SET_LOCKTAG_TRANSACTION(tag, xid);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on transaction with ID %u", xid)));
}
/*
*************** XactLockTableWait(TransactionId xid)
*** 474,480 ****
SET_LOCKTAG_TRANSACTION(tag, xid);
! (void) LockAcquire(&tag, ShareLock, false, false);
LockRelease(&tag, ShareLock, false);
--- 526,535 ----
SET_LOCKTAG_TRANSACTION(tag, xid);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on transaction with ID %u", xid)));
LockRelease(&tag, ShareLock, false);
*************** LockDatabaseObject(Oid classid, Oid obji
*** 535,541 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, false, false);
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
--- 590,600 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
*************** LockSharedObject(Oid classid, Oid objid,
*** 576,582 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, false, false);
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
--- 635,645 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
*************** LockSharedObjectForSession(Oid classid,
*** 618,624 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, true, false);
}
/*
--- 681,702 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, true, false) == LOCKACQUIRE_NOT_AVAIL)
! switch(classid)
! {
! case DatabaseRelationId:
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on database with ID %u",
! objid)));
! break;
! default:
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
! break;
! }
}
/*
diff -dcrpN postgresql.1/src/backend/storage/lmgr/lock.c postgresql.2/src/backend/storage/lmgr/lock.c
*** postgresql.1/src/backend/storage/lmgr/lock.c 2012-05-05 13:22:40.776667140 +0200
--- postgresql.2/src/backend/storage/lmgr/lock.c 2012-05-11 09:32:47.339851648 +0200
*************** static PROCLOCK *SetupLockInTable(LockMe
*** 340,346 ****
static void GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner);
static void BeginStrongLockAcquire(LOCALLOCK *locallock, uint32 fasthashcode);
static void FinishStrongLockAcquire(void);
! static void WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner);
static void ReleaseLockIfHeld(LOCALLOCK *locallock, bool sessionLock);
static bool UnGrantLock(LOCK *lock, LOCKMODE lockmode,
PROCLOCK *proclock, LockMethod lockMethodTable);
--- 340,346 ----
static void GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner);
static void BeginStrongLockAcquire(LOCALLOCK *locallock, uint32 fasthashcode);
static void FinishStrongLockAcquire(void);
! static int WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner);
static void ReleaseLockIfHeld(LOCALLOCK *locallock, bool sessionLock);
static bool UnGrantLock(LOCK *lock, LOCKMODE lockmode,
PROCLOCK *proclock, LockMethod lockMethodTable);
*************** ProcLockHashCode(const PROCLOCKTAG *proc
*** 546,552 ****
* dontWait: if true, don't wait to acquire lock
*
* Returns one of:
! * LOCKACQUIRE_NOT_AVAIL lock not available, and dontWait=true
* LOCKACQUIRE_OK lock successfully acquired
* LOCKACQUIRE_ALREADY_HELD incremented count for lock already held
*
--- 546,552 ----
* dontWait: if true, don't wait to acquire lock
*
* Returns one of:
! * LOCKACQUIRE_NOT_AVAIL lock not available, either dontWait=true or timeout
* LOCKACQUIRE_OK lock successfully acquired
* LOCKACQUIRE_ALREADY_HELD incremented count for lock already held
*
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 856,862 ****
locktag->locktag_type,
lockmode);
! WaitOnLock(locallock, owner);
TRACE_POSTGRESQL_LOCK_WAIT_DONE(locktag->locktag_field1,
locktag->locktag_field2,
--- 856,862 ----
locktag->locktag_type,
lockmode);
! status = WaitOnLock(locallock, owner);
TRACE_POSTGRESQL_LOCK_WAIT_DONE(locktag->locktag_field1,
locktag->locktag_field2,
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 871,898 ****
* done when the lock was granted to us --- see notes in WaitOnLock.
*/
! /*
! * Check the proclock entry status, in case something in the ipc
! * communication doesn't work correctly.
! */
! if (!(proclock->holdMask & LOCKBIT_ON(lockmode)))
{
! AbortStrongLockAcquire();
! PROCLOCK_PRINT("LockAcquire: INCONSISTENT", proclock);
! LOCK_PRINT("LockAcquire: INCONSISTENT", lock, lockmode);
! /* Should we retry ? */
! LWLockRelease(partitionLock);
! elog(ERROR, "LockAcquire failed");
}
- PROCLOCK_PRINT("LockAcquire: granted", proclock);
- LOCK_PRINT("LockAcquire: granted", lock, lockmode);
}
! /*
! * Lock state is fully up-to-date now; if we error out after this, no
! * special error cleanup is required.
! */
! FinishStrongLockAcquire();
LWLockRelease(partitionLock);
--- 871,921 ----
* done when the lock was granted to us --- see notes in WaitOnLock.
*/
! switch (status)
{
! case STATUS_OK:
! /*
! * Check the proclock entry status, in case something in the ipc
! * communication doesn't work correctly.
! */
! if (!(proclock->holdMask & LOCKBIT_ON(lockmode)))
! {
! AbortStrongLockAcquire();
! PROCLOCK_PRINT("LockAcquire: INCONSISTENT", proclock);
! LOCK_PRINT("LockAcquire: INCONSISTENT", lock, lockmode);
! /* Should we retry ? */
! LWLockRelease(partitionLock);
! elog(ERROR, "LockAcquire failed");
! }
! PROCLOCK_PRINT("LockAcquire: granted", proclock);
! LOCK_PRINT("LockAcquire: granted", lock, lockmode);
! break;
! case STATUS_WAITING:
! PROCLOCK_PRINT("LockAcquire: timed out", proclock);
! LOCK_PRINT("LockAcquire: timed out", lock, lockmode);
! break;
! default:
! elog(ERROR, "LockAcquire invalid status");
! break;
}
}
! if (status == STATUS_WAITING)
! {
! /*
! * lock_timeout was set and WaitOnLock() indicated
! * we timed out. Clean up manually.
! */
! AbortStrongLockAcquire();
! }
! else
! {
! /*
! * Lock state is fully up-to-date now; if we error out after this, no
! * special error cleanup is required.
! */
! FinishStrongLockAcquire();
! }
LWLockRelease(partitionLock);
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 911,917 ****
locktag->locktag_field2);
}
! return LOCKACQUIRE_OK;
}
/*
--- 934,940 ----
locktag->locktag_field2);
}
! return (status == STATUS_OK ? LOCKACQUIRE_OK : LOCKACQUIRE_NOT_AVAIL);
}
/*
*************** GrantAwaitedLock(void)
*** 1429,1442 ****
* Caller must have set MyProc->heldLocks to reflect locks already held
* on the lockable object by this process.
*
* The appropriate partition lock must be held at entry.
*/
! static void
WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner)
{
LOCKMETHODID lockmethodid = LOCALLOCK_LOCKMETHOD(*locallock);
LockMethod lockMethodTable = LockMethods[lockmethodid];
char *volatile new_status = NULL;
LOCK_PRINT("WaitOnLock: sleeping on lock",
locallock->lock, locallock->tag.mode);
--- 1452,1471 ----
* Caller must have set MyProc->heldLocks to reflect locks already held
* on the lockable object by this process.
*
+ * Result: returns value of ProcSleep()
+ * STATUS_OK if we acquired the lock
+ * STATUS_ERROR if not (deadlock)
+ * STATUS_WAITING if not (timeout)
+ *
* The appropriate partition lock must be held at entry.
*/
! static int
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);
*************** WaitOnLock(LOCALLOCK *locallock, Resourc
*** 1478,1485 ****
*/
PG_TRY();
{
! if (ProcSleep(locallock, lockMethodTable) != STATUS_OK)
{
/*
* We failed as a result of a deadlock, see CheckDeadLock(). Quit
* now.
--- 1507,1519 ----
*/
PG_TRY();
{
! wait_status = ProcSleep(locallock, lockMethodTable);
! switch (wait_status)
{
+ case STATUS_OK:
+ case STATUS_WAITING:
+ break;
+ default:
/*
* We failed as a result of a deadlock, see CheckDeadLock(). Quit
* now.
*************** WaitOnLock(LOCALLOCK *locallock, Resourc
*** 1524,1531 ****
pfree(new_status);
}
! LOCK_PRINT("WaitOnLock: wakeup on lock",
locallock->lock, locallock->tag.mode);
}
/*
--- 1558,1571 ----
pfree(new_status);
}
! 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);
+
+ return wait_status;
}
/*
*************** VirtualXactLock(VirtualTransactionId vxi
*** 3843,3849 ****
LWLockRelease(proc->backendLock);
/* Time to wait. */
! (void) LockAcquire(&tag, ShareLock, false, false);
LockRelease(&tag, ShareLock, false);
return true;
--- 3883,3893 ----
LWLockRelease(proc->backendLock);
/* Time to wait. */
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on virtual transaction with ID %u",
! vxid.localTransactionId)));
LockRelease(&tag, ShareLock, false);
return true;
diff -dcrpN 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-05-11 09:32:17.147683010 +0200
--- postgresql.2/src/backend/storage/lmgr/proc.c 2012-05-11 09:32:47.340851652 +0200
*************** LockErrorCleanup(void)
*** 641,648 ****
if (lockAwaited == NULL)
return;
! /* Turn off the deadlock timer, if it's still running (see ProcSleep) */
disable_timeout(DEADLOCK_TIMEOUT, false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
--- 641,652 ----
if (lockAwaited == NULL)
return;
! /*
! * Turn off the deadlock and lock timeout timers,
! * if they are still running (see ProcSleep)
! */
disable_timeout(DEADLOCK_TIMEOUT, false);
+ disable_timeout(LOCK_TIMEOUT, false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
*************** ProcQueueInit(PROC_QUEUE *queue)
*** 878,884 ****
* 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).
*
* ASSUME: that no one will fiddle with the queue until after
* we release the partition lock.
--- 882,891 ----
* 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)
! * STATUS_WAITING if not (timeout)
*
* ASSUME: that no one will fiddle with the queue until after
* we release the partition lock.
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 900,905 ****
--- 907,913 ----
LOCKMASK myHeldLocks = MyProc->heldLocks;
bool early_deadlock = false;
bool allow_autovacuum_cancel = true;
+ bool timeout_detected = false;
int myWaitStatus;
PGPROC *proc;
int i;
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1041,1048 ****
elog(FATAL, "could not set timer for process wakeup");
/*
! * 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
* saved wakeup might be leftover from a previous operation (for example,
* we aborted ProcWaitForSignal just before someone did ProcSendSignal).
--- 1049,1062 ----
elog(FATAL, "could not set timer for process wakeup");
/*
! * Queue the timer for lock timeout, too.
! */
! if (!enable_timeout(LOCK_TIMEOUT, LockTimeout))
! elog(FATAL, "could not set timer for process wakeup");
!
! /*
! * 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).
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1059,1065 ****
*/
do
{
! PGSemaphoreLock(&MyProc->sem, true);
/*
* waitStatus could change from STATUS_WAITING to something else
--- 1073,1084 ----
*/
do
{
! PGSemaphoreTimedLock(&MyProc->sem, true);
!
! /* Check and keep the lock timeout indicator for later checks */
! timeout_detected = get_timeout_indicator(LOCK_TIMEOUT);
! if (timeout_detected)
! break;
/*
* waitStatus could change from STATUS_WAITING to something else
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1188,1199 ****
} while (myWaitStatus == STATUS_WAITING);
/*
! * Disable the timer, if it's still running
*/
if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for process wakeup");
/*
* Re-acquire the lock table's partition lock. We have to do this to hold
* off cancel/die interrupts before we can mess with lockAwaited (else we
* might have a missed or duplicated locallock update).
--- 1207,1224 ----
} while (myWaitStatus == STATUS_WAITING);
/*
! * Disable the deadlock timer, if it's still running
*/
if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for process wakeup");
/*
+ * Disable the lock timeout timer, if it's still running
+ */
+ if (!disable_timeout(LOCK_TIMEOUT, false))
+ elog(FATAL, "could not disable timer for process wakeup");
+
+ /*
* Re-acquire the lock table's partition lock. We have to do this to hold
* off cancel/die interrupts before we can mess with lockAwaited (else we
* might have a missed or duplicated locallock update).
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1201,1206 ****
--- 1226,1240 ----
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;
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1214,1221 ****
/*
* We don't have to do anything else, because the awaker did all the
* necessary update of the lock table and MyProc.
*/
! return MyProc->waitStatus;
}
--- 1248,1257 ----
/*
* 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 (timeout_detected ? STATUS_WAITING : MyProc->waitStatus);
}
diff -dcrpN postgresql.1/src/backend/storage/lmgr/timeout.c postgresql.2/src/backend/storage/lmgr/timeout.c
*** postgresql.1/src/backend/storage/lmgr/timeout.c 2012-05-11 09:32:17.159683077 +0200
--- postgresql.2/src/backend/storage/lmgr/timeout.c 2012-05-11 09:32:47.341851656 +0200
***************
*** 34,39 ****
--- 34,40 ----
/* GUC variables */
int DeadlockTimeout = 1000;
int StatementTimeout = 0;
+ int LockTimeout = 0;
/*
* This is used by ProcSleep() in proc.c
*************** static void InitDeadLock(TimestampTz sta
*** 50,55 ****
--- 51,60 ----
static void DestroyDeadLock(bool keep_indicator);
static bool CheckDeadLock(void);
+ static void InitLockTimeout(TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyLockTimeout(bool keep_indicator);
+ static bool CheckLockTimeout(void);
+
static void InitStatementTimeout(TimestampTz start_time, TimestampTz fin_time);
static void DestroyStatementTimeout(bool keep_indicator);
static bool CheckStatementTimeout(void);
*************** static timeout_params base_timeouts[TIME
*** 98,103 ****
--- 103,115 ----
},
{
+ LOCK_TIMEOUT, false, false,
+ InitLockTimeout, DestroyLockTimeout,
+ CheckLockTimeout, GetCurrentTimestamp,
+ 0
+ },
+
+ {
STATEMENT_TIMEOUT, false, false,
InitStatementTimeout, DestroyStatementTimeout,
CheckStatementTimeout, GetCurrentStatementStartTimestamp,
*************** check_done:
*** 380,385 ****
--- 392,427 ----
return true;
}
+ /*
+ * Functions to manage lock timeout
+ */
+
+ static void
+ InitLockTimeout(TimestampTz start_time, TimestampTz fin_time)
+ {
+ InitTimeout(LOCK_TIMEOUT, start_time, fin_time);
+ }
+
+ static void
+ DestroyLockTimeout(bool keep_indicator)
+ {
+ DestroyTimeout(LOCK_TIMEOUT, keep_indicator);
+ }
+
+ static bool
+ CheckLockTimeout(void)
+ {
+ TimestampTz now;
+
+ now = GetCurrentTimestamp();
+
+ if (now < base_timeouts[LOCK_TIMEOUT].fin_time)
+ return false;
+
+ base_timeouts[LOCK_TIMEOUT].indicator = true;
+ return true;
+ }
+
/*
* Functions to manage statement timeout
*/
diff -dcrpN postgresql.1/src/backend/utils/adt/lockfuncs.c postgresql.2/src/backend/utils/adt/lockfuncs.c
*** postgresql.1/src/backend/utils/adt/lockfuncs.c 2012-04-16 19:57:22.473915895 +0200
--- postgresql.2/src/backend/utils/adt/lockfuncs.c 2012-05-11 09:32:47.343851669 +0200
*************** pg_advisory_lock_int8(PG_FUNCTION_ARGS)
*** 421,427 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ExclusiveLock, true, false);
PG_RETURN_VOID();
}
--- 421,431 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ExclusiveLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_int8(PG_FUNCTION_A
*** 438,444 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
PG_RETURN_VOID();
}
--- 442,452 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_shared_int8(PG_FUNCTION
*** 454,460 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ShareLock, true, false);
PG_RETURN_VOID();
}
--- 462,472 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ShareLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_shared_int8(PG_FUN
*** 471,477 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ShareLock, false, false);
PG_RETURN_VOID();
}
--- 483,493 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_int4(PG_FUNCTION_ARGS)
*** 604,610 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ExclusiveLock, true, false);
PG_RETURN_VOID();
}
--- 620,630 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ExclusiveLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_int4(PG_FUNCTION_A
*** 622,628 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
PG_RETURN_VOID();
}
--- 642,652 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_shared_int4(PG_FUNCTION
*** 639,645 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ShareLock, true, false);
PG_RETURN_VOID();
}
--- 663,673 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ShareLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_shared_int4(PG_FUN
*** 657,663 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ShareLock, false, false);
PG_RETURN_VOID();
}
--- 685,695 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
diff -dcrpN 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-05-11 09:32:17.198683295 +0200
--- postgresql.2/src/backend/utils/misc/guc.c 2012-05-11 09:32:47.347851693 +0200
*************** static struct config_int ConfigureNamesI
*** 1861,1866 ****
--- 1861,1877 ----
},
{
+ {"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 -dcrpN 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-04-30 08:18:06.651737938 +0200
--- postgresql.2/src/backend/utils/misc/postgresql.conf.sample 2012-05-11 09:32:47.348851698 +0200
***************
*** 528,533 ****
--- 528,536 ----
#------------------------------------------------------------------------------
#deadlock_timeout = 1s
+ #lock_timeout = 0 # timeout value for heavy-weight locks
+ # taken by statements. 0 disables timeout
+ # unit in milliseconds, default is 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 -dcrpN postgresql.1/src/include/storage/pg_sema.h postgresql.2/src/include/storage/pg_sema.h
*** postgresql.1/src/include/storage/pg_sema.h 2012-04-16 19:57:22.672918205 +0200
--- postgresql.2/src/include/storage/pg_sema.h 2012-05-11 09:32:47.348851698 +0200
*************** extern void PGSemaphoreUnlock(PGSemaphor
*** 80,83 ****
--- 80,89 ----
/* 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 void PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK);
+
#endif /* PG_SEMA_H */
diff -dcrpN postgresql.1/src/include/storage/timeout.h postgresql.2/src/include/storage/timeout.h
*** postgresql.1/src/include/storage/timeout.h 2012-05-11 09:32:17.219683411 +0200
--- postgresql.2/src/include/storage/timeout.h 2012-05-11 09:32:47.349851703 +0200
***************
*** 19,27 ****
--- 19,29 ----
/* configurable options */
extern int DeadlockTimeout;
extern int StatementTimeout;
+ extern int LockTimeout;
typedef enum TimeoutName {
DEADLOCK_TIMEOUT,
+ LOCK_TIMEOUT,
STATEMENT_TIMEOUT,
STANDBY_DEADLOCK_TIMEOUT,
STANDBY_TIMEOUT,
diff -dcrpN 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-05-11 09:32:47.349851703 +0200
*************** set statement_timeout to 2000;
*** 198,203 ****
--- 198,210 ----
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: could not obtain lock on relation "pxtest3"
+ LINE 1: SELECT * FROM pxtest3;
+ ^
+ reset lock_timeout;
-- Disconnect, we will continue testing in a different backend
\c -
-- There should still be two prepared transactions
*************** set statement_timeout to 2000;
*** 213,218 ****
--- 220,232 ----
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: could not obtain lock on relation "pxtest3"
+ LINE 1: SELECT * FROM pxtest3;
+ ^
+ reset lock_timeout;
-- Commit table creation
COMMIT PREPARED 'regress-one';
\d pxtest2
diff -dcrpN 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-05-11 09:32:47.350851708 +0200
*************** set statement_timeout to 2000;
*** 126,131 ****
--- 126,136 ----
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 -
*************** set statement_timeout to 2000;
*** 137,142 ****
--- 142,152 ----
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
Excerpts from Boszormenyi Zoltan's message of vie may 11 03:54:13 -0400 2012:
Hi,
another rebasing and applied the GIT changes in
ada8fa08fc6cf5f199b6df935b4d0a730aaa4fec to the
Windows implementation of PGSemaphoreTimedLock.
Hi,
I gave the framework patch a look. One thing I'm not sure about is the
way you've defined the API. It seems a bit strange to have a nice and
clean, generic interface in timeout.h; and then have the internal
implementation of the API cluttered with details such as what to do when
the deadlock timeout expires. Wouldn't it be much better if we could
keep the details of CheckDeadLock itself within proc.c, for example?
Same for the other specific Check*Timeout functions. It seems to me
that the only timeout.c specific requirement is to be able to poke
base_timeouts[].indicator and fin_time. Maybe that can get done in
timeout.c and then have the generic checker call the module-specific
checker. ... I see that you have things to do before and after setting
"indicator". Maybe you could split the module-specific Check functions
in two and have timeout.c call each in turn. Other than that I don't
see that this should pose any difficulty.
Also, I see you're calling GetCurrentTimestamp() in the checkers; maybe
more than once per signal if you have multiple timeouts enabled. Maybe
it'd be better to call it in once handle_sig_alarm and then pass the
value down, if necessary (AFAICS if you restructure the code as I
suggest above, you don't need to get the value down the the
module-specific code).
As for the Init*Timeout() and Destroy*Timeout() functions, they seem
a bit pointless -- I mean if they just always call the generic
InitTimeout and DestroyTimeout functions, why not just define the struct
in a way that makes the specific functions unnecessary? Maybe you even
already have all that's necessary ... I think you just change
base_timeouts[i].timeout_destroy(false); to DestroyTimeout(i, false) and
so on.
Minor nitpick: the "Interface:" comment in timeout.c seems useless.
That kind of thing tends to get overlooked and obsolete over time. We
have examples of such things all over the place. I'd just rip it and
have timeout.h be the interface documentation.
--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support
BTW it doesn't seem that datatype/timestamp.h is really necessary in
timeout.h.
--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support
2012-06-18 19:46 keltezéssel, Alvaro Herrera írta:
Excerpts from Boszormenyi Zoltan's message of vie may 11 03:54:13 -0400 2012:
Hi,
another rebasing and applied the GIT changes in
ada8fa08fc6cf5f199b6df935b4d0a730aaa4fec to the
Windows implementation of PGSemaphoreTimedLock.Hi,
I gave the framework patch a look. One thing I'm not sure about is the
way you've defined the API. It seems a bit strange to have a nice and
clean, generic interface in timeout.h; and then have the internal
implementation of the API cluttered with details such as what to do when
the deadlock timeout expires. Wouldn't it be much better if we could
keep the details of CheckDeadLock itself within proc.c, for example?
Do you mean adding a callback function argument to for enable_timeout()
would be better?
Same for the other specific Check*Timeout functions. It seems to me
that the only timeout.c specific requirement is to be able to poke
base_timeouts[].indicator and fin_time. Maybe that can get done in
timeout.c and then have the generic checker call the module-specific
checker.
Or instead of static functions, Check* functions can be external
to timeout.c. It seemed to be a good idea to move all the timeout
related functions to timeout.c.
... I see that you have things to do before and after setting
"indicator". Maybe you could split the module-specific Check functions
in two and have timeout.c call each in turn. Other than that I don't
see that this should pose any difficulty.Also, I see you're calling GetCurrentTimestamp() in the checkers; maybe
more than once per signal if you have multiple timeouts enabled.
Actually, GetCurrentTimestamp() is not called multiple times,
because only the first timeout source in the queue can get triggered.
Maybe
it'd be better to call it in once handle_sig_alarm and then pass the
value down, if necessary (AFAICS if you restructure the code as I
suggest above, you don't need to get the value down the the
module-specific code).
But yes, this way it can be cleaner.
As for the Init*Timeout() and Destroy*Timeout() functions, they seem
a bit pointless -- I mean if they just always call the generic
InitTimeout and DestroyTimeout functions, why not just define the struct
in a way that makes the specific functions unnecessary? Maybe you even
already have all that's necessary ... I think you just change
base_timeouts[i].timeout_destroy(false); to DestroyTimeout(i, false) and
so on.
OK, I will experiment with it.
Minor nitpick: the "Interface:" comment in timeout.c seems useless.
That kind of thing tends to get overlooked and obsolete over time. We
have examples of such things all over the place. I'd just rip it and
have timeout.h be the interface documentation.
OK.
BTW it doesn't seem that datatype/timestamp.h is really necessary in
timeout.h.
You are wrong. get_timeout_start() returns TimestampTz and it's
defined in datatype/timestamp.h.
Thanks for the review, I will send the new code soon.
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/
Hi,
attached are the new patches.
The base patch is now simplified and is about 10K smaller:
- no more individual Init* and Destroy* functions
- Check* functions don't check for "now", it's done centrally by the signal handler
- CheckDeadLock() is moved back to proc.c and made public for timeout.c
- a new header storage/proctimeout.h is introduced, so timeout.c can know
about CheckDeadLock()
The lock_timeout patch gained a new feature and enum GUC:
SET lock_timeout_option = { 'per_lock' | 'per_statement' } ;
'per_lock' is the default and carries the previous behaviour: the timeout value
applies to all locks individually. The statement may take up to N*timeout.
'per_statement' behaves like statement_timeout. The important difference is
that statement_timeout may trigger during the executor phase when a long
result set is already being returned to the client and the transfer is cut because
of the timeout. On the other hand, lock_timeout may only trigger during locking
the objects and if all locks were granted under the specified time, the result set
is then returned to the client.
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/
Attachments:
1-timeout-framework-v6.patchtext/x-patch; name=1-timeout-framework-v6.patchDownload
diff -dcrpN postgresql/src/backend/postmaster/autovacuum.c postgresql.1/src/backend/postmaster/autovacuum.c
*** postgresql/src/backend/postmaster/autovacuum.c 2012-06-11 06:22:48.137921483 +0200
--- postgresql.1/src/backend/postmaster/autovacuum.c 2012-06-17 19:35:22.985926386 +0200
***************
*** 86,94 ****
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
- #include "storage/proc.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "tcop/tcopprot.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
--- 86,94 ----
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
+ #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
*************** AutoVacLauncherMain(int argc, char *argv
*** 484,490 ****
/* Forget any pending QueryCancel request */
QueryCancelPending = false;
! disable_sig_alarm(true);
QueryCancelPending = false; /* again in case timeout occurred */
/* Report the error to the server log */
--- 484,490 ----
/* Forget any pending QueryCancel request */
QueryCancelPending = false;
! disable_all_timeouts(false);
QueryCancelPending = false; /* again in case timeout occurred */
/* Report the error to the server log */
diff -dcrpN postgresql/src/backend/postmaster/postmaster.c postgresql.1/src/backend/postmaster/postmaster.c
*** postgresql/src/backend/postmaster/postmaster.c 2012-06-11 06:22:48.148921535 +0200
--- postgresql.1/src/backend/postmaster/postmaster.c 2012-06-17 19:35:22.988926403 +0200
***************
*** 112,118 ****
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
! #include "storage/proc.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/datetime.h"
--- 112,118 ----
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
! #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/datetime.h"
*************** BackendInitialize(Port *port)
*** 3486,3494 ****
* Ready to begin client interaction. We will give up and exit(1) after a
* time delay, so that a broken client can't hog a connection
* indefinitely. PreAuthDelay and any DNS interactions above don't count
! * against the time limit.
*/
! if (!enable_sig_alarm(AuthenticationTimeout * 1000, false))
elog(FATAL, "could not set timer for startup packet timeout");
/*
--- 3486,3494 ----
* Ready to begin client interaction. We will give up and exit(1) after a
* time delay, so that a broken client can't hog a connection
* indefinitely. PreAuthDelay and any DNS interactions above don't count
! * against the time limit. Use the deadlock timeout interface.
*/
! if (!enable_timeout(DEADLOCK_TIMEOUT, AuthenticationTimeout * 1000))
elog(FATAL, "could not set timer for startup packet timeout");
/*
*************** BackendInitialize(Port *port)
*** 3526,3532 ****
/*
* Disable the timeout, and prevent SIGTERM/SIGQUIT again.
*/
! if (!disable_sig_alarm(false))
elog(FATAL, "could not disable timer for startup packet timeout");
PG_SETMASK(&BlockSig);
}
--- 3526,3532 ----
/*
* Disable the timeout, and prevent SIGTERM/SIGQUIT again.
*/
! if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for startup packet timeout");
PG_SETMASK(&BlockSig);
}
diff -dcrpN postgresql/src/backend/postmaster/startup.c postgresql.1/src/backend/postmaster/startup.c
*** postgresql/src/backend/postmaster/startup.c 2012-04-16 19:57:22.442915536 +0200
--- postgresql.1/src/backend/postmaster/startup.c 2012-06-17 19:35:22.991926418 +0200
***************
*** 27,33 ****
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
! #include "storage/proc.h"
#include "utils/guc.h"
--- 27,33 ----
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
! #include "storage/timeout.h"
#include "utils/guc.h"
*************** StartupProcessMain(void)
*** 195,202 ****
pqsignal(SIGTERM, StartupProcShutdownHandler); /* request shutdown */
pqsignal(SIGQUIT, startupproc_quickdie); /* hard crash time */
if (EnableHotStandby)
! pqsignal(SIGALRM, handle_standby_sig_alarm); /* ignored unless
! * InHotStandby */
else
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
--- 195,202 ----
pqsignal(SIGTERM, StartupProcShutdownHandler); /* request shutdown */
pqsignal(SIGQUIT, startupproc_quickdie); /* hard crash time */
if (EnableHotStandby)
! pqsignal(SIGALRM, handle_sig_alarm); /* ignored unless
! * InHotStandby */
else
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
diff -dcrpN postgresql/src/backend/storage/ipc/standby.c postgresql.1/src/backend/storage/ipc/standby.c
*** postgresql/src/backend/storage/ipc/standby.c 2012-06-11 06:22:48.154921564 +0200
--- postgresql.1/src/backend/storage/ipc/standby.c 2012-06-17 19:35:22.992926423 +0200
***************
*** 23,32 ****
#include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
- #include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/sinvaladt.h"
#include "storage/standby.h"
#include "utils/ps_status.h"
#include "utils/timestamp.h"
--- 23,32 ----
#include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
#include "storage/procarray.h"
#include "storage/sinvaladt.h"
#include "storage/standby.h"
+ #include "storage/timeout.h"
#include "utils/ps_status.h"
#include "utils/timestamp.h"
*************** ResolveRecoveryConflictWithLock(Oid dbOi
*** 394,400 ****
void
ResolveRecoveryConflictWithBufferPin(void)
{
- bool sig_alarm_enabled = false;
TimestampTz ltime;
TimestampTz now;
--- 394,399 ----
*************** ResolveRecoveryConflictWithBufferPin(voi
*** 409,417 ****
* We're willing to wait forever for conflicts, so set timeout for
* deadlock check (only)
*/
! if (enable_standby_sig_alarm(now, now, true))
! sig_alarm_enabled = true;
! else
elog(FATAL, "could not set timer for process wakeup");
}
else if (now >= ltime)
--- 408,414 ----
* We're willing to wait forever for conflicts, so set timeout for
* deadlock check (only)
*/
! if (!enable_timeout(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout))
elog(FATAL, "could not set timer for process wakeup");
}
else if (now >= ltime)
*************** ResolveRecoveryConflictWithBufferPin(voi
*** 423,446 ****
}
else
{
/*
* Wake up at ltime, and check for deadlocks as well if we will be
* waiting longer than deadlock_timeout
*/
! if (enable_standby_sig_alarm(now, ltime, false))
! sig_alarm_enabled = true;
! else
elog(FATAL, "could not set timer for process wakeup");
}
/* Wait to be signaled by UnpinBuffer() */
ProcWaitForSignal();
! if (sig_alarm_enabled)
! {
! if (!disable_standby_sig_alarm())
! elog(FATAL, "could not disable timer for process wakeup");
! }
}
void
--- 420,447 ----
}
else
{
+ long secs, msecs;
+ int usecs;
+
/*
* Wake up at ltime, and check for deadlocks as well if we will be
* waiting longer than deadlock_timeout
*/
! if (!enable_timeout(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout))
! elog(FATAL, "could not set timer for process wakeup");
!
! TimestampDifference(now, ltime, &secs, &usecs);
! msecs = secs * 1000 + usecs / 1000;
!
! if (!enable_timeout(STANDBY_TIMEOUT, msecs))
elog(FATAL, "could not set timer for process wakeup");
}
/* Wait to be signaled by UnpinBuffer() */
ProcWaitForSignal();
! if (!disable_all_timeouts(false))
! elog(FATAL, "could not disable timer for process wakeup");
}
void
diff -dcrpN postgresql/src/backend/storage/lmgr/Makefile postgresql.1/src/backend/storage/lmgr/Makefile
*** postgresql/src/backend/storage/lmgr/Makefile 2012-04-16 19:57:22.458915722 +0200
--- postgresql.1/src/backend/storage/lmgr/Makefile 2012-06-17 19:35:22.992926423 +0200
*************** subdir = src/backend/storage/lmgr
*** 12,18 ****
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
! OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o predicate.o
include $(top_srcdir)/src/backend/common.mk
--- 12,18 ----
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
! OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o predicate.o timeout.o
include $(top_srcdir)/src/backend/common.mk
diff -dcrpN postgresql/src/backend/storage/lmgr/proc.c postgresql.1/src/backend/storage/lmgr/proc.c
*** postgresql/src/backend/storage/lmgr/proc.c 2012-06-11 06:22:48.156921573 +0200
--- postgresql.1/src/backend/storage/lmgr/proc.c 2012-06-19 11:34:32.253831350 +0200
***************
*** 47,59 ****
#include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/spin.h"
#include "utils/timestamp.h"
/* GUC variables */
- int DeadlockTimeout = 1000;
- int StatementTimeout = 0;
bool log_lock_waits = false;
/* Pointer to this process's PGPROC and PGXACT structs, if any */
--- 47,59 ----
#include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
+ #include "storage/proctimeout.h"
#include "storage/spin.h"
+ #include "storage/timeout.h"
#include "utils/timestamp.h"
/* GUC variables */
bool log_lock_waits = false;
/* Pointer to this process's PGPROC and PGXACT structs, if any */
*************** PGPROC *PreparedXactProcs = NULL;
*** 77,103 ****
/* If we are waiting for a lock, this points to the associated LOCALLOCK */
static LOCALLOCK *lockAwaited = NULL;
! /* Mark these volatile because they can be changed by signal handler */
! static volatile bool standby_timeout_active = false;
! static volatile bool statement_timeout_active = false;
! static volatile bool deadlock_timeout_active = false;
! static volatile DeadLockState deadlock_state = DS_NOT_YET_CHECKED;
! volatile bool cancel_from_timeout = false;
!
! /* timeout_start_time is set when log_lock_waits is true */
! static TimestampTz timeout_start_time;
!
! /* statement_fin_time is valid only if statement_timeout_active is true */
! static TimestampTz statement_fin_time;
! static TimestampTz statement_fin_time2; /* valid only in recovery */
!
static void RemoveProcFromArray(int code, Datum arg);
static void ProcKill(int code, Datum arg);
static void AuxiliaryProcKill(int code, Datum arg);
- static bool CheckStatementTimeout(void);
- static bool CheckStandbyTimeout(void);
-
/*
* Report shared-memory space needed by InitProcGlobal.
--- 77,88 ----
/* If we are waiting for a lock, this points to the associated LOCALLOCK */
static LOCALLOCK *lockAwaited = NULL;
! /* Mark this volatile because it can be changed by signal handler */
! static volatile DeadLockState deadlock_state;
static void RemoveProcFromArray(int code, Datum arg);
static void ProcKill(int code, Datum arg);
static void AuxiliaryProcKill(int code, Datum arg);
/*
* Report shared-memory space needed by InitProcGlobal.
*************** LockErrorCleanup(void)
*** 654,660 ****
return;
/* Turn off the deadlock timer, if it's still running (see ProcSleep) */
! disable_sig_alarm(false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
--- 639,645 ----
return;
/* Turn off the deadlock timer, if it's still running (see ProcSleep) */
! disable_timeout(DEADLOCK_TIMEOUT, false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1049,1055 ****
* 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 (!enable_sig_alarm(DeadlockTimeout, false))
elog(FATAL, "could not set timer for process wakeup");
/*
--- 1034,1040 ----
* 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 (!enable_timeout(DEADLOCK_TIMEOUT, DeadlockTimeout))
elog(FATAL, "could not set timer for process wakeup");
/*
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1139,1145 ****
DescribeLockTag(&buf, &locallock->tag.lock);
modename = GetLockmodeName(locallock->tag.lock.locktag_lockmethodid,
lockmode);
! TimestampDifference(timeout_start_time, GetCurrentTimestamp(),
&secs, &usecs);
msecs = secs * 1000 + usecs / 1000;
usecs = usecs % 1000;
--- 1124,1131 ----
DescribeLockTag(&buf, &locallock->tag.lock);
modename = GetLockmodeName(locallock->tag.lock.locktag_lockmethodid,
lockmode);
! TimestampDifference(get_timeout_start(DEADLOCK_TIMEOUT),
! GetCurrentTimestamp(),
&secs, &usecs);
msecs = secs * 1000 + usecs / 1000;
usecs = usecs % 1000;
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1201,1207 ****
/*
* Disable the timer, if it's still running
*/
! if (!disable_sig_alarm(false))
elog(FATAL, "could not disable timer for process wakeup");
/*
--- 1187,1193 ----
/*
* Disable the timer, if it's still running
*/
! if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for process wakeup");
/*
*************** ProcLockWakeup(LockMethod lockMethodTabl
*** 1345,1351 ****
* NB: this is run inside a signal handler, so be very wary about what is done
* here or in called routines.
*/
! static void
CheckDeadLock(void)
{
int i;
--- 1331,1337 ----
* NB: this is run inside a signal handler, so be very wary about what is done
* here or in called routines.
*/
! void
CheckDeadLock(void)
{
int i;
*************** ProcSendSignal(int pid)
*** 1499,1899 ****
if (proc != NULL)
PGSemaphoreUnlock(&proc->sem);
}
-
-
- /*****************************************************************************
- * SIGALRM interrupt support
- *
- * Maybe these should be in pqsignal.c?
- *****************************************************************************/
-
- /*
- * Enable the SIGALRM interrupt to fire after the specified delay
- *
- * Delay is given in milliseconds. Caller should be sure a SIGALRM
- * signal handler is installed before this is called.
- *
- * This code properly handles nesting of deadlock timeout alarms within
- * statement timeout alarms.
- *
- * Returns TRUE if okay, FALSE on failure.
- */
- bool
- enable_sig_alarm(int delayms, bool is_statement_timeout)
- {
- TimestampTz fin_time;
- struct itimerval timeval;
-
- if (is_statement_timeout)
- {
- /*
- * Begin statement-level timeout
- *
- * Note that we compute statement_fin_time with reference to the
- * statement_timestamp, but apply the specified delay without any
- * correction; that is, we ignore whatever time has elapsed since
- * statement_timestamp was set. In the normal case only a small
- * interval will have elapsed and so this doesn't matter, but there
- * are corner cases (involving multi-statement query strings with
- * embedded COMMIT or ROLLBACK) where we might re-initialize the
- * statement timeout long after initial receipt of the message. In
- * such cases the enforcement of the statement timeout will be a bit
- * inconsistent. This annoyance is judged not worth the cost of
- * performing an additional gettimeofday() here.
- */
- Assert(!deadlock_timeout_active);
- fin_time = GetCurrentStatementStartTimestamp();
- fin_time = TimestampTzPlusMilliseconds(fin_time, delayms);
- statement_fin_time = fin_time;
- cancel_from_timeout = false;
- statement_timeout_active = true;
- }
- else if (statement_timeout_active)
- {
- /*
- * Begin deadlock timeout with statement-level timeout active
- *
- * Here, we want to interrupt at the closer of the two timeout times.
- * If fin_time >= statement_fin_time then we need not touch the
- * existing timer setting; else set up to interrupt at the deadlock
- * timeout time.
- *
- * NOTE: in this case it is possible that this routine will be
- * interrupted by the previously-set timer alarm. This is okay
- * because the signal handler will do only what it should do according
- * to the state variables. The deadlock checker may get run earlier
- * than normal, but that does no harm.
- */
- timeout_start_time = GetCurrentTimestamp();
- fin_time = TimestampTzPlusMilliseconds(timeout_start_time, delayms);
- deadlock_timeout_active = true;
- if (fin_time >= statement_fin_time)
- return true;
- }
- else
- {
- /* Begin deadlock timeout with no statement-level timeout */
- deadlock_timeout_active = true;
- /* GetCurrentTimestamp can be expensive, so only do it if we must */
- if (log_lock_waits)
- timeout_start_time = GetCurrentTimestamp();
- }
-
- /* If we reach here, okay to set the timer interrupt */
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = delayms / 1000;
- timeval.it_value.tv_usec = (delayms % 1000) * 1000;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- return true;
- }
-
- /*
- * Cancel the SIGALRM timer, either for a deadlock timeout or a statement
- * timeout. If a deadlock timeout is canceled, any active statement timeout
- * remains in force.
- *
- * Returns TRUE if okay, FALSE on failure.
- */
- bool
- disable_sig_alarm(bool is_statement_timeout)
- {
- /*
- * Always disable the interrupt if it is active; this avoids being
- * interrupted by the signal handler and thereby possibly getting
- * confused.
- *
- * We will re-enable the interrupt if necessary in CheckStatementTimeout.
- */
- if (statement_timeout_active || deadlock_timeout_active)
- {
- struct itimerval timeval;
-
- MemSet(&timeval, 0, sizeof(struct itimerval));
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- {
- statement_timeout_active = false;
- cancel_from_timeout = false;
- deadlock_timeout_active = false;
- return false;
- }
- }
-
- /* Always cancel deadlock timeout, in case this is error cleanup */
- deadlock_timeout_active = false;
-
- /* Cancel or reschedule statement timeout */
- if (is_statement_timeout)
- {
- statement_timeout_active = false;
- cancel_from_timeout = false;
- }
- else if (statement_timeout_active)
- {
- if (!CheckStatementTimeout())
- return false;
- }
- return true;
- }
-
-
- /*
- * Check for statement timeout. If the timeout time has come,
- * trigger a query-cancel interrupt; if not, reschedule the SIGALRM
- * interrupt to occur at the right time.
- *
- * Returns true if okay, false if failed to set the interrupt.
- */
- static bool
- CheckStatementTimeout(void)
- {
- TimestampTz now;
-
- if (!statement_timeout_active)
- return true; /* do nothing if not active */
-
- now = GetCurrentTimestamp();
-
- if (now >= statement_fin_time)
- {
- /* Time to die */
- statement_timeout_active = false;
- cancel_from_timeout = true;
- #ifdef HAVE_SETSID
- /* try to signal whole process group */
- kill(-MyProcPid, SIGINT);
- #endif
- kill(MyProcPid, SIGINT);
- }
- else
- {
- /* Not time yet, so (re)schedule the interrupt */
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
-
- /*
- * It's possible that the difference is less than a microsecond;
- * ensure we don't cancel, rather than set, the interrupt.
- */
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- }
-
- return true;
- }
-
-
- /*
- * Signal handler for SIGALRM for normal user backends
- *
- * Process deadlock check and/or statement timeout check, as needed.
- * To avoid various edge cases, we must be careful to do nothing
- * when there is nothing to be done. We also need to be able to
- * reschedule the timer interrupt if called before end of statement.
- */
- void
- handle_sig_alarm(SIGNAL_ARGS)
- {
- int save_errno = errno;
-
- /* SIGALRM is cause for waking anything waiting on the process latch */
- if (MyProc)
- SetLatch(&MyProc->procLatch);
-
- if (deadlock_timeout_active)
- {
- deadlock_timeout_active = false;
- CheckDeadLock();
- }
-
- if (statement_timeout_active)
- (void) CheckStatementTimeout();
-
- errno = save_errno;
- }
-
- /*
- * Signal handler for SIGALRM in Startup process
- *
- * To avoid various edge cases, we must be careful to do nothing
- * when there is nothing to be done. We also need to be able to
- * reschedule the timer interrupt if called before end of statement.
- *
- * We set either deadlock_timeout_active or statement_timeout_active
- * or both. Interrupts are enabled if standby_timeout_active.
- */
- bool
- enable_standby_sig_alarm(TimestampTz now, TimestampTz fin_time, bool deadlock_only)
- {
- TimestampTz deadlock_time = TimestampTzPlusMilliseconds(now,
- DeadlockTimeout);
-
- if (deadlock_only)
- {
- /*
- * Wake up at deadlock_time only, then wait forever
- */
- statement_fin_time = deadlock_time;
- deadlock_timeout_active = true;
- statement_timeout_active = false;
- }
- else if (fin_time > deadlock_time)
- {
- /*
- * Wake up at deadlock_time, then again at fin_time
- */
- statement_fin_time = deadlock_time;
- statement_fin_time2 = fin_time;
- deadlock_timeout_active = true;
- statement_timeout_active = true;
- }
- else
- {
- /*
- * Wake only at fin_time because its fairly soon
- */
- statement_fin_time = fin_time;
- deadlock_timeout_active = false;
- statement_timeout_active = true;
- }
-
- if (deadlock_timeout_active || statement_timeout_active)
- {
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- standby_timeout_active = true;
- }
-
- return true;
- }
-
- bool
- disable_standby_sig_alarm(void)
- {
- /*
- * Always disable the interrupt if it is active; this avoids being
- * interrupted by the signal handler and thereby possibly getting
- * confused.
- *
- * We will re-enable the interrupt if necessary in CheckStandbyTimeout.
- */
- if (standby_timeout_active)
- {
- struct itimerval timeval;
-
- MemSet(&timeval, 0, sizeof(struct itimerval));
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- {
- standby_timeout_active = false;
- return false;
- }
- }
-
- standby_timeout_active = false;
-
- return true;
- }
-
- /*
- * CheckStandbyTimeout() runs unconditionally in the Startup process
- * SIGALRM handler. Timers will only be set when InHotStandby.
- * We simply ignore any signals unless the timer has been set.
- */
- static bool
- CheckStandbyTimeout(void)
- {
- TimestampTz now;
- bool reschedule = false;
-
- standby_timeout_active = false;
-
- now = GetCurrentTimestamp();
-
- /*
- * Reschedule the timer if its not time to wake yet, or if we have both
- * timers set and the first one has just been reached.
- */
- if (now >= statement_fin_time)
- {
- if (deadlock_timeout_active)
- {
- /*
- * We're still waiting when we reach deadlock timeout, so send out
- * a request to have other backends check themselves for deadlock.
- * Then continue waiting until statement_fin_time, if that's set.
- */
- SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
- deadlock_timeout_active = false;
-
- /*
- * Begin second waiting period if required.
- */
- if (statement_timeout_active)
- {
- reschedule = true;
- statement_fin_time = statement_fin_time2;
- }
- }
- else
- {
- /*
- * We've now reached statement_fin_time, so ask all conflicts to
- * leave, so we can press ahead with applying changes in recovery.
- */
- SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
- }
- }
- else
- reschedule = true;
-
- if (reschedule)
- {
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- standby_timeout_active = true;
- }
-
- return true;
- }
-
- void
- handle_standby_sig_alarm(SIGNAL_ARGS)
- {
- int save_errno = errno;
-
- if (standby_timeout_active)
- (void) CheckStandbyTimeout();
-
- errno = save_errno;
- }
--- 1485,1487 ----
diff -dcrpN postgresql/src/backend/storage/lmgr/timeout.c postgresql.1/src/backend/storage/lmgr/timeout.c
*** postgresql/src/backend/storage/lmgr/timeout.c 1970-01-01 01:00:00.000000000 +0100
--- postgresql.1/src/backend/storage/lmgr/timeout.c 2012-06-19 13:07:18.215857100 +0200
***************
*** 0 ****
--- 1,460 ----
+ /*-------------------------------------------------------------------------
+ *
+ * timeout.c
+ * routines to manage timeout sources handled by SIGALRM
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/storage/lmgr/timeout.c
+ *
+ *-------------------------------------------------------------------------
+ */
+ #include "postgres.h"
+
+ #include <sys/time.h>
+
+ #include "access/xact.h"
+ #include "miscadmin.h"
+ #include "storage/proc.h"
+ #include "storage/proctimeout.h"
+ #include "storage/standby.h"
+ #include "storage/timeout.h"
+ #include "utils/timestamp.h"
+
+ /* GUC variables */
+ int DeadlockTimeout = 1000;
+ int StatementTimeout = 0;
+
+ /*
+ * Infrastructure for timeouts
+ */
+
+ static void InitTimeout(TimeoutName tn, TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyTimeout(TimeoutName tn, bool keep_indicator);
+
+ /* CheckDeadLock() is declared in storage/proctimeout.h */
+ static void CheckStatementTimeout(void);
+ static void CheckStandbyDeadLock(void);
+ static void CheckStandbyTimeout(void);
+
+ typedef void (*timeout_check)(void);
+ typedef TimestampTz (*timeout_start)(void);
+
+ typedef struct {
+ TimeoutName index;
+ bool resched_next;
+
+ /* volatile because it may be changed from the signal handler */
+ volatile bool indicator;
+
+ timeout_check timeout_check;
+ timeout_start timeout_start;
+
+ TimestampTz start_time;
+ TimestampTz fin_time;
+ } timeout_params;
+
+ /*
+ * List of possible timeout reasons in the order of enum TimeoutName.
+ * The priority of timeouts (in case two of them would trigger at the
+ * same time) is determined by this order: the earlier one in the list
+ * has higher priority.
+ */
+ static timeout_params base_timeouts[TIMEOUT_MAX] = {
+ {
+ DEADLOCK_TIMEOUT, true, false,
+ CheckDeadLock, GetCurrentTimestamp,
+ 0
+ },
+
+ {
+ STATEMENT_TIMEOUT, false, false,
+ CheckStatementTimeout, GetCurrentStatementStartTimestamp,
+ 0
+ },
+
+ {
+ STANDBY_DEADLOCK_TIMEOUT, true, false,
+ CheckStandbyDeadLock, GetCurrentTimestamp,
+ 0
+ },
+
+ {
+ STANDBY_TIMEOUT, false, false,
+ CheckStandbyTimeout, GetCurrentTimestamp,
+ 0
+ }
+ };
+
+ /*
+ * List of active timeouts ordered by their fin_time and priority.
+ */
+ static int n_timeouts = 0;
+ static timeout_params *timeouts[TIMEOUT_MAX];
+
+ /*****************************************************************************
+ * Internal helper functions
+ *****************************************************************************/
+
+ /*
+ * Find the index of a given timeout type in the active array
+ */
+ static int
+ find_active_timeout(TimeoutName tn)
+ {
+ int i;
+
+ for (i = 0; i < n_timeouts; i++)
+ {
+ if (timeouts[i]->index == tn)
+ return i;
+ }
+
+ return -1;
+ }
+
+ #define is_timeout_active(tn) (find_active_timeout(tn) >= 0)
+
+ /*
+ * Insert tn'th timeout into the list of timeouts at the given index
+ * if the previous timeout allows rescheduling the next one in the list.
+ */
+ static void
+ insert_timeout(TimeoutName tn, int index)
+ {
+ int i;
+
+ if (index > 0 && !timeouts[index-1]->resched_next)
+ return;
+
+ for (i = n_timeouts - 1; i >= index; i--)
+ timeouts[i+1] = timeouts[i];
+
+ timeouts[index] = &base_timeouts[tn];
+ n_timeouts++;
+ }
+
+ /*
+ * Remove the index'th element from the timeout list.
+ */
+ static void
+ remove_timeout_index(int index)
+ {
+ int i;
+
+ if (index < 0)
+ return;
+
+ for (i = index + 1; i < n_timeouts; i++)
+ timeouts[i-1] = timeouts[i];
+
+ if (n_timeouts > 0 && index < n_timeouts)
+ n_timeouts--;
+ }
+
+ /*
+ * (Re)schedule the next active timeout
+ */
+ static bool
+ schedule_timeout(TimestampTz now)
+ {
+ long secs;
+ int usecs;
+ struct itimerval timeval;
+
+ /* There is no active timeout, do nothing. */
+ if (n_timeouts == 0)
+ return true;
+
+ TimestampDifference(now, timeouts[0]->fin_time,
+ &secs, &usecs);
+
+ /*
+ * It's possible that the difference is less than a microsecond;
+ * ensure we don't cancel, rather than set, the interrupt.
+ */
+ if (secs == 0 && usecs == 0)
+ usecs = 1;
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ timeval.it_value.tv_sec = secs;
+ timeval.it_value.tv_usec = usecs;
+ if (setitimer(ITIMER_REAL, &timeval, NULL))
+ return false;
+ return true;
+ }
+
+ /*****************************************************************************
+ * Init, Destroy and Check functions for different timeouts.
+ *
+ * NB: all Check* functions are run inside a signal handler, so be very wary
+ * about what is done in them or in called routines.
+ *****************************************************************************/
+
+ /*
+ * Common Init and Destroy functions
+ */
+
+ static void
+ InitTimeout(TimeoutName tn, TimestampTz start_time, TimestampTz fin_time)
+ {
+ base_timeouts[tn].indicator = false;
+ base_timeouts[tn].start_time = start_time;
+ base_timeouts[tn].fin_time = fin_time;
+ }
+
+ static void
+ DestroyTimeout(TimeoutName tn, bool keep_indicator)
+ {
+ if (!keep_indicator)
+ base_timeouts[tn].indicator = false;
+ base_timeouts[tn].start_time = 0;
+ base_timeouts[tn].fin_time = 0;
+ }
+
+ /*
+ * CheckStatementTimeout
+ */
+ static void
+ CheckStatementTimeout(void)
+ {
+ #ifdef HAVE_SETSID
+ /* try to signal whole process group */
+ kill(-MyProcPid, SIGINT);
+ #endif
+ kill(MyProcPid, SIGINT);
+ }
+
+ /*
+ * CheckStandbyDeadLock
+ */
+ static void
+ CheckStandbyDeadLock(void)
+ {
+ SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
+ }
+
+ /*
+ * CheckStandbyTimeout
+ */
+ static void
+ CheckStandbyTimeout(void)
+ {
+ SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
+ }
+
+ /*****************************************************************************
+ * Public API
+ *****************************************************************************/
+
+
+ /*
+ * Enable the SIGALRM interrupt to fire after the specified delay
+ *
+ * Delay is given in milliseconds. Caller should be sure a SIGALRM
+ * signal handler is installed before this is called.
+ *
+ * This code properly handles nesting of different timeout alarms.
+ *
+ * Returns TRUE if okay, FALSE on failure to set the timer.
+ */
+ bool
+ enable_timeout(TimeoutName tn, int delayms)
+ {
+ TimestampTz start_time;
+ TimestampTz fin_time;
+ int i;
+
+ Assert(!is_timeout_active(tn));
+
+ if (delayms <= 0)
+ return true;
+
+ start_time = base_timeouts[tn].timeout_start();
+ fin_time = TimestampTzPlusMilliseconds(start_time, delayms);
+
+ /* Find out the index where to insert the new timeout. */
+ for (i = 0; i < n_timeouts; i++)
+ {
+ /*
+ * The new timeout triggers earlier than
+ * a previously added one: insert here.
+ */
+ if (fin_time < timeouts[i]->fin_time)
+ break;
+ /*
+ * The new timeout triggers at the same time
+ * as the previously added one but has greater priority.
+ */
+ if (fin_time == timeouts[i]->fin_time && tn < timeouts[i]->index)
+ break;
+ }
+
+ /*
+ * Initialize the timeout parameters
+ */
+ InitTimeout(tn, start_time, fin_time);
+
+ insert_timeout(tn, i);
+
+ if (i > 0)
+ return true;
+
+ /* If we reach here, okay to set the timer interrupt */
+ if (!schedule_timeout(start_time))
+ return false;
+ return true;
+ }
+
+ /*
+ * Cancel the SIGALRM timer for the specific timeout.
+ * If a timeout is canceled, any other active timeout remains in force.
+ *
+ * Returns TRUE if okay, FALSE on failure to set the timer for
+ * the next timeout source.
+ */
+ bool
+ disable_timeout(TimeoutName tn, bool keep_indicator)
+ {
+ int i;
+
+ /*
+ * Always disable the interrupt if it is active; this avoids being
+ * interrupted by the signal handler and thereby possibly getting
+ * confused.
+ *
+ * We will re-enable the interrupt if necessary in ->check_timeout().
+ */
+ if (n_timeouts > 0)
+ {
+ struct itimerval timeval;
+
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ if (setitimer(ITIMER_REAL, &timeval, NULL))
+ {
+ n_timeouts = 0;
+ for (i = 0; i < TIMEOUT_MAX; i++)
+ DestroyTimeout(i, false);
+
+ return false;
+ }
+ }
+
+ /* Find the timeout and remove from the list. */
+ i = find_active_timeout(tn);
+ remove_timeout_index(i);
+
+ /* Do cleanup. */
+ DestroyTimeout(tn, keep_indicator);
+
+ /*
+ * If the first timeout was removed from the list and there is
+ * at least one active left, reschedule it.
+ */
+ if (i == 0 && n_timeouts > 0)
+ if (!schedule_timeout(GetCurrentTimestamp()))
+ return false;
+
+ return true;
+ }
+
+ /*
+ * Disable SIGALRM and remove all timeouts from the list and
+ * reset the timeout indicators.
+ */
+ bool
+ disable_all_timeouts(bool keep_indicators)
+ {
+ struct itimerval timeval;
+ int i;
+ bool ret;
+
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ ret = (setitimer(ITIMER_REAL, &timeval, NULL) == 0);
+
+ n_timeouts = 0;
+ for (i = 0; i < TIMEOUT_MAX; i++)
+ DestroyTimeout(i, keep_indicators);
+
+ return ret;
+ }
+
+ /*
+ * Return the timeout indicator
+ */
+ bool
+ get_timeout_indicator(TimeoutName tn)
+ {
+ return base_timeouts[tn].indicator;
+ }
+
+ /*
+ * Return the start of the timer for this timeout
+ */
+ TimestampTz
+ get_timeout_start(TimeoutName tn)
+ {
+ return base_timeouts[tn].start_time;
+ }
+
+ /*
+ * Signal handler for SIGALRM
+ *
+ * Process the check for the currently active timeout source and
+ * reschedule the next as needed. To avoid various edge cases,
+ * we must be careful to do nothing when there is nothing to be done.
+ */
+ void
+ handle_sig_alarm(SIGNAL_ARGS)
+ {
+ int save_errno = errno;
+
+ /*
+ * SIGALRM is cause for waking anything waiting on the process latch.
+ * Recovery (the startup process) doesn't have MyProc set so
+ * it can also use this signal handler.
+ */
+ if (MyProc)
+ SetLatch(&MyProc->procLatch);
+
+ if (n_timeouts > 0)
+ {
+ TimestampTz now;
+ bool reschedule;
+
+ now = GetCurrentTimestamp();
+
+ /* If our time has not come yet, do nothing. */
+ if (now < timeouts[0]->fin_time)
+ goto out;
+
+ /*
+ * Set the trigger indicator before calling the checker function.
+ * Setting if after may have side effects that lead to detecting
+ * a different event, like "user pressed Ctrl-C" is detected instead
+ * of the statement timeout having triggered.
+ */
+ timeouts[0]->indicator = true;
+
+ /* Call the timeout checker. */
+ timeouts[0]->timeout_check();
+
+ /*
+ * Remove the current timeout source and reschedule
+ * the next if needed. Short circuit disable_timeout(..., true)
+ * for the timeout source that just triggered.
+ */
+ reschedule = timeouts[0]->resched_next;
+ remove_timeout_index(0);
+
+ if (reschedule)
+ schedule_timeout(now);
+ else
+ disable_all_timeouts(true);
+ }
+
+ out:
+ errno = save_errno;
+ }
diff -dcrpN postgresql/src/backend/tcop/postgres.c postgresql.1/src/backend/tcop/postgres.c
*** postgresql/src/backend/tcop/postgres.c 2012-06-11 06:22:48.157921579 +0200
--- postgresql.1/src/backend/tcop/postgres.c 2012-06-17 19:35:22.999926465 +0200
***************
*** 64,69 ****
--- 64,70 ----
#include "storage/proc.h"
#include "storage/procsignal.h"
#include "storage/sinval.h"
+ #include "storage/timeout.h"
#include "tcop/fastpath.h"
#include "tcop/pquery.h"
#include "tcop/tcopprot.h"
*************** start_xact_command(void)
*** 2396,2404 ****
/* Set statement timeout running, if any */
/* NB: this mustn't be enabled until we are within an xact */
if (StatementTimeout > 0)
! enable_sig_alarm(StatementTimeout, true);
else
! cancel_from_timeout = false;
xact_started = true;
}
--- 2397,2405 ----
/* Set statement timeout running, if any */
/* NB: this mustn't be enabled until we are within an xact */
if (StatementTimeout > 0)
! enable_timeout(STATEMENT_TIMEOUT, StatementTimeout);
else
! disable_timeout(STATEMENT_TIMEOUT, false);
xact_started = true;
}
*************** finish_xact_command(void)
*** 2410,2416 ****
if (xact_started)
{
/* Cancel any active statement timeout before committing */
! disable_sig_alarm(true);
/* Now commit the command */
ereport(DEBUG3,
--- 2411,2417 ----
if (xact_started)
{
/* Cancel any active statement timeout before committing */
! disable_all_timeouts(false);
/* Now commit the command */
ereport(DEBUG3,
*************** ProcessInterrupts(void)
*** 2891,2897 ****
(errcode(ERRCODE_QUERY_CANCELED),
errmsg("canceling authentication due to timeout")));
}
! if (cancel_from_timeout)
{
ImmediateInterruptOK = false; /* not idle anymore */
DisableNotifyInterrupt();
--- 2892,2898 ----
(errcode(ERRCODE_QUERY_CANCELED),
errmsg("canceling authentication due to timeout")));
}
! if (get_timeout_indicator(STATEMENT_TIMEOUT))
{
ImmediateInterruptOK = false; /* not idle anymore */
DisableNotifyInterrupt();
*************** PostgresMain(int argc, char *argv[], con
*** 3799,3808 ****
/*
* Forget any pending QueryCancel request, since we're returning to
! * the idle loop anyway, and cancel the statement timer if running.
*/
QueryCancelPending = false;
! disable_sig_alarm(true);
QueryCancelPending = false; /* again in case timeout occurred */
/*
--- 3800,3809 ----
/*
* Forget any pending QueryCancel request, since we're returning to
! * the idle loop anyway, and cancel the timer if running.
*/
QueryCancelPending = false;
! disable_all_timeouts(false);
QueryCancelPending = false; /* again in case timeout occurred */
/*
diff -dcrpN postgresql/src/backend/utils/init/postinit.c postgresql.1/src/backend/utils/init/postinit.c
*** postgresql/src/backend/utils/init/postinit.c 2012-04-16 19:57:22.490916093 +0200
--- postgresql.1/src/backend/utils/init/postinit.c 2012-06-17 19:35:23.000926471 +0200
***************
*** 41,51 ****
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lmgr.h"
- #include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "storage/smgr.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
#include "utils/fmgroids.h"
--- 41,51 ----
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lmgr.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "storage/smgr.h"
+ #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
#include "utils/fmgroids.h"
*************** PerformAuthentication(Port *port)
*** 204,210 ****
* during authentication. Since we're inside a transaction and might do
* database access, we have to use the statement_timeout infrastructure.
*/
! if (!enable_sig_alarm(AuthenticationTimeout * 1000, true))
elog(FATAL, "could not set timer for authorization timeout");
/*
--- 204,210 ----
* during authentication. Since we're inside a transaction and might do
* database access, we have to use the statement_timeout infrastructure.
*/
! if (!enable_timeout(STATEMENT_TIMEOUT, AuthenticationTimeout * 1000))
elog(FATAL, "could not set timer for authorization timeout");
/*
*************** PerformAuthentication(Port *port)
*** 215,221 ****
/*
* Done with authentication. Disable the timeout, and log if needed.
*/
! if (!disable_sig_alarm(true))
elog(FATAL, "could not disable timer for authorization timeout");
if (Log_connections)
--- 215,221 ----
/*
* Done with authentication. Disable the timeout, and log if needed.
*/
! if (!disable_timeout(STATEMENT_TIMEOUT, false))
elog(FATAL, "could not disable timer for authorization timeout");
if (Log_connections)
diff -dcrpN postgresql/src/backend/utils/misc/guc.c postgresql.1/src/backend/utils/misc/guc.c
*** postgresql/src/backend/utils/misc/guc.c 2012-06-11 06:22:48.193921753 +0200
--- postgresql.1/src/backend/utils/misc/guc.c 2012-06-17 19:35:23.003926485 +0200
***************
*** 63,68 ****
--- 63,69 ----
#include "storage/standby.h"
#include "storage/fd.h"
#include "storage/predicate.h"
+ #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "tsearch/ts_cache.h"
#include "utils/builtins.h"
diff -dcrpN postgresql/src/include/storage/proc.h postgresql.1/src/include/storage/proc.h
*** postgresql/src/include/storage/proc.h 2012-06-11 06:22:48.223921899 +0200
--- postgresql.1/src/include/storage/proc.h 2012-06-17 19:35:23.004926491 +0200
*************** extern PGPROC *PreparedXactProcs;
*** 218,230 ****
/* configurable options */
- extern int DeadlockTimeout;
- extern int StatementTimeout;
extern bool log_lock_waits;
- extern volatile bool cancel_from_timeout;
-
-
/*
* Function Prototypes
*/
--- 218,225 ----
*************** extern void LockErrorCleanup(void);
*** 252,264 ****
extern void ProcWaitForSignal(void);
extern void ProcSendSignal(int pid);
- extern bool enable_sig_alarm(int delayms, bool is_statement_timeout);
- extern bool disable_sig_alarm(bool is_statement_timeout);
- extern void handle_sig_alarm(SIGNAL_ARGS);
-
- extern bool enable_standby_sig_alarm(TimestampTz now,
- TimestampTz fin_time, bool deadlock_only);
- extern bool disable_standby_sig_alarm(void);
- extern void handle_standby_sig_alarm(SIGNAL_ARGS);
-
#endif /* PROC_H */
--- 247,250 ----
diff -dcrpN postgresql/src/include/storage/proctimeout.h postgresql.1/src/include/storage/proctimeout.h
*** postgresql/src/include/storage/proctimeout.h 1970-01-01 01:00:00.000000000 +0100
--- postgresql.1/src/include/storage/proctimeout.h 2012-06-19 11:34:20.741762311 +0200
***************
*** 0 ****
--- 1,19 ----
+ /*-------------------------------------------------------------------------
+ *
+ * proctimeout.h
+ * declaration of CheckDeadLock()
+ *
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/storage/proctimeout.h
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef _PROCTIMEOUT_H_
+ #define _PROCTIMEOUT_H_
+
+ extern void CheckDeadLock(void);
+
+ #endif /* _PROCTIMEOUT_H_ */
diff -dcrpN postgresql/src/include/storage/timeout.h postgresql.1/src/include/storage/timeout.h
*** postgresql/src/include/storage/timeout.h 1970-01-01 01:00:00.000000000 +0100
--- postgresql.1/src/include/storage/timeout.h 2012-06-19 11:14:15.901547376 +0200
***************
*** 0 ****
--- 1,38 ----
+ /*-------------------------------------------------------------------------
+ *
+ * timeout.h
+ * SIGALRM timeout API
+ *
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/storage/timeout.h
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef _TIMEOUT_H_
+ #define _TIMEOUT_H_
+
+ #include "datatype/timestamp.h"
+
+ /* configurable options */
+ extern int DeadlockTimeout;
+ extern int StatementTimeout;
+
+ typedef enum TimeoutName {
+ DEADLOCK_TIMEOUT,
+ STATEMENT_TIMEOUT,
+ STANDBY_DEADLOCK_TIMEOUT,
+ STANDBY_TIMEOUT,
+ TIMEOUT_MAX
+ } TimeoutName;
+
+ extern bool enable_timeout(TimeoutName tn, int delayms);
+ extern bool disable_timeout(TimeoutName tn, bool keep_indicator);
+ extern bool disable_all_timeouts(bool keep_indicators);
+ extern bool get_timeout_indicator(TimeoutName tn);
+ extern TimestampTz get_timeout_start(TimeoutName tn);
+ extern void handle_sig_alarm(SIGNAL_ARGS);
+
+ #endif /* _TIMEOUT_H_ */
2-lock_timeout-v6.patchtext/x-patch; name=2-lock_timeout-v6.patchDownload
diff -dcrpN postgresql.1/doc/src/sgml/config.sgml postgresql.2/doc/src/sgml/config.sgml
*** postgresql.1/doc/src/sgml/config.sgml 2012-05-11 09:23:17.030668613 +0200
--- postgresql.2/doc/src/sgml/config.sgml 2012-06-19 12:42:44.616676967 +0200
*************** COPY postgres_log FROM '/full/path/to/lo
*** 4980,4986 ****
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.
</para>
<para>
--- 4980,4989 ----
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. 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>
*************** COPY postgres_log FROM '/full/path/to/lo
*** 4991,4996 ****
--- 4994,5053 ----
</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 the 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 -dcrpN 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-06-17 19:36:22.742266146 +0200
*************** LOCK [ TABLE ] [ ONLY ] <replaceable cla
*** 39,46 ****
<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.)
</para>
--- 39,49 ----
<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. 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 -dcrpN 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-06-19 12:44:15.232222764 +0200
*************** FOR SHARE [ OF <replaceable class="param
*** 1199,1204 ****
--- 1199,1212 ----
</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 -dcrpN postgresql.1/src/backend/port/posix_sema.c postgresql.2/src/backend/port/posix_sema.c
*** postgresql.1/src/backend/port/posix_sema.c 2012-04-16 19:57:22.438915489 +0200
--- postgresql.2/src/backend/port/posix_sema.c 2012-06-17 19:36:22.744266158 +0200
***************
*** 24,29 ****
--- 24,30 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/timeout.h"
#ifdef USE_NAMED_POSIX_SEMAPHORES
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 313,315 ****
--- 314,341 ----
return true;
}
+
+ /*
+ * PGSemaphoreTimedLock
+ *
+ * Lock a semaphore (decrement count), blocking if count would be < 0
+ * Return if lock_timeout expired
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ int errStatus;
+
+ do
+ {
+ ImmediateInterruptOK = interruptOK;
+ CHECK_FOR_INTERRUPTS();
+ errStatus = sem_wait(PG_SEM_REF(sema));
+ ImmediateInterruptOK = false;
+ } while (errStatus < 0 && errno == EINTR && !get_timeout_indicator(LOCK_TIMEOUT));
+
+ if (get_timeout_indicator(LOCK_TIMEOUT))
+ return;
+ if (errStatus < 0)
+ elog(FATAL, "sem_wait failed: %m");
+ }
diff -dcrpN postgresql.1/src/backend/port/sysv_sema.c postgresql.2/src/backend/port/sysv_sema.c
*** postgresql.1/src/backend/port/sysv_sema.c 2012-05-14 08:20:56.284830580 +0200
--- postgresql.2/src/backend/port/sysv_sema.c 2012-06-17 19:36:22.745266163 +0200
***************
*** 27,32 ****
--- 27,33 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/timeout.h"
#ifndef HAVE_UNION_SEMUN
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 492,494 ****
--- 493,525 ----
return true;
}
+
+ /*
+ * PGSemaphoreTimedLock
+ *
+ * Lock a semaphore (decrement count), blocking if count would be < 0
+ * Return if lock_timeout expired
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ int errStatus;
+ 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;
+ } while (errStatus < 0 && errno == EINTR && !get_timeout_indicator(LOCK_TIMEOUT));
+
+ if (get_timeout_indicator(LOCK_TIMEOUT))
+ return;
+ if (errStatus < 0)
+ elog(FATAL, "semop(id=%d) failed: %m", sema->semId);
+ }
diff -dcrpN postgresql.1/src/backend/port/win32_sema.c postgresql.2/src/backend/port/win32_sema.c
*** postgresql.1/src/backend/port/win32_sema.c 2012-06-11 06:22:48.137921483 +0200
--- postgresql.2/src/backend/port/win32_sema.c 2012-06-17 19:36:22.746266169 +0200
***************
*** 16,21 ****
--- 16,22 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/timeout.h"
static HANDLE *mySemSet; /* IDs of sema sets acquired so far */
static int numSems; /* number of sema sets acquired so far */
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 209,211 ****
--- 210,271 ----
/* 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.
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ DWORD ret;
+ HANDLE wh[2];
+
+ /*
+ * 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;
+ } while (errno == EINTR && !get_timeout_indicator(LOCK_TIMEOUT));
+
+ if (get_timeout_indicator(LOCK_TIMEOUT))
+ return;
+ if (errno != 0)
+ ereport(FATAL,
+ (errmsg("could not lock semaphore: error code %d", (int) GetLastError())));
+ }
diff -dcrpN postgresql.1/src/backend/storage/lmgr/lmgr.c postgresql.2/src/backend/storage/lmgr/lmgr.c
*** postgresql.1/src/backend/storage/lmgr/lmgr.c 2012-04-16 19:57:22.459915733 +0200
--- postgresql.2/src/backend/storage/lmgr/lmgr.c 2012-06-17 19:36:22.747266175 +0200
***************
*** 19,26 ****
--- 19,29 ----
#include "access/transam.h"
#include "access/xact.h"
#include "catalog/catalog.h"
+ #include "catalog/pg_database.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
+ #include "utils/lsyscache.h"
+ #include "storage/proc.h"
#include "storage/procarray.h"
#include "utils/inval.h"
*************** LockRelationOid(Oid relid, LOCKMODE lock
*** 78,83 ****
--- 81,101 ----
res = LockAcquire(&tag, lockmode, false, false);
+ if (res == LOCKACQUIRE_NOT_AVAIL)
+ {
+ char *relname = get_rel_name(relid);
+ if (relname)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ relname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation with OID %u",
+ relid)));
+ }
+
/*
* Now that we have the lock, check for invalidation messages, so that we
* will update or flush any stale relcache entry before we try to use it.
*************** LockRelation(Relation relation, LOCKMODE
*** 174,179 ****
--- 192,203 ----
res = LockAcquire(&tag, lockmode, false, false);
+ if (res == LOCKACQUIRE_NOT_AVAIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ RelationGetRelationName(relation))));
+
/*
* Now that we have the lock, check for invalidation messages; see notes
* in LockRelationOid.
*************** LockRelationIdForSession(LockRelId *reli
*** 251,257 ****
SET_LOCKTAG_RELATION(tag, relid->dbId, relid->relId);
! (void) LockAcquire(&tag, lockmode, true, false);
}
/*
--- 275,294 ----
SET_LOCKTAG_RELATION(tag, relid->dbId, relid->relId);
! if (LockAcquire(&tag, lockmode, true, false) == LOCKACQUIRE_NOT_AVAIL)
! {
! char *relname = get_rel_name(relid->relId);
! if (relname)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on relation \"%s\"",
! relname)));
! else
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on relation with OID %u",
! relid->relId)));
! }
}
/*
*************** LockRelationForExtension(Relation relati
*** 286,292 ****
relation->rd_lockInfo.lockRelId.dbId,
relation->rd_lockInfo.lockRelId.relId);
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 323,333 ----
relation->rd_lockInfo.lockRelId.dbId,
relation->rd_lockInfo.lockRelId.relId);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on index \"%s\"",
! RelationGetRelationName(relation))));
}
/*
*************** LockPage(Relation relation, BlockNumber
*** 320,326 ****
relation->rd_lockInfo.lockRelId.relId,
blkno);
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 361,371 ----
relation->rd_lockInfo.lockRelId.relId,
blkno);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on page %u of relation \"%s\"",
! blkno, RelationGetRelationName(relation))));
}
/*
*************** LockTuple(Relation relation, ItemPointer
*** 376,382 ****
ItemPointerGetBlockNumber(tid),
ItemPointerGetOffsetNumber(tid));
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 421,431 ----
ItemPointerGetBlockNumber(tid),
ItemPointerGetOffsetNumber(tid));
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on row in relation \"%s\"",
! RelationGetRelationName(relation))));
}
/*
*************** XactLockTableInsert(TransactionId xid)
*** 430,436 ****
SET_LOCKTAG_TRANSACTION(tag, xid);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
}
/*
--- 479,488 ----
SET_LOCKTAG_TRANSACTION(tag, xid);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on transaction with ID %u", xid)));
}
/*
*************** XactLockTableWait(TransactionId xid)
*** 474,480 ****
SET_LOCKTAG_TRANSACTION(tag, xid);
! (void) LockAcquire(&tag, ShareLock, false, false);
LockRelease(&tag, ShareLock, false);
--- 526,535 ----
SET_LOCKTAG_TRANSACTION(tag, xid);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on transaction with ID %u", xid)));
LockRelease(&tag, ShareLock, false);
*************** LockDatabaseObject(Oid classid, Oid obji
*** 535,541 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, false, false);
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
--- 590,600 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
*************** LockSharedObject(Oid classid, Oid objid,
*** 576,582 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, false, false);
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
--- 635,645 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
*************** LockSharedObjectForSession(Oid classid,
*** 618,624 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, true, false);
}
/*
--- 681,702 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, true, false) == LOCKACQUIRE_NOT_AVAIL)
! switch(classid)
! {
! case DatabaseRelationId:
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on database with ID %u",
! objid)));
! break;
! default:
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
! break;
! }
}
/*
diff -dcrpN postgresql.1/src/backend/storage/lmgr/lock.c postgresql.2/src/backend/storage/lmgr/lock.c
*** postgresql.1/src/backend/storage/lmgr/lock.c 2012-06-11 06:22:48.154921564 +0200
--- postgresql.2/src/backend/storage/lmgr/lock.c 2012-06-17 19:36:22.750266191 +0200
*************** static PROCLOCK *SetupLockInTable(LockMe
*** 343,349 ****
static void GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner);
static void BeginStrongLockAcquire(LOCALLOCK *locallock, uint32 fasthashcode);
static void FinishStrongLockAcquire(void);
! static void WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner);
static void ReleaseLockIfHeld(LOCALLOCK *locallock, bool sessionLock);
static bool UnGrantLock(LOCK *lock, LOCKMODE lockmode,
PROCLOCK *proclock, LockMethod lockMethodTable);
--- 343,349 ----
static void GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner);
static void BeginStrongLockAcquire(LOCALLOCK *locallock, uint32 fasthashcode);
static void FinishStrongLockAcquire(void);
! static int WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner);
static void ReleaseLockIfHeld(LOCALLOCK *locallock, bool sessionLock);
static bool UnGrantLock(LOCK *lock, LOCKMODE lockmode,
PROCLOCK *proclock, LockMethod lockMethodTable);
*************** ProcLockHashCode(const PROCLOCKTAG *proc
*** 549,555 ****
* dontWait: if true, don't wait to acquire lock
*
* Returns one of:
! * LOCKACQUIRE_NOT_AVAIL lock not available, and dontWait=true
* LOCKACQUIRE_OK lock successfully acquired
* LOCKACQUIRE_ALREADY_HELD incremented count for lock already held
*
--- 549,555 ----
* dontWait: if true, don't wait to acquire lock
*
* Returns one of:
! * LOCKACQUIRE_NOT_AVAIL lock not available, either dontWait=true or timeout
* LOCKACQUIRE_OK lock successfully acquired
* LOCKACQUIRE_ALREADY_HELD incremented count for lock already held
*
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 863,869 ****
locktag->locktag_type,
lockmode);
! WaitOnLock(locallock, owner);
TRACE_POSTGRESQL_LOCK_WAIT_DONE(locktag->locktag_field1,
locktag->locktag_field2,
--- 863,869 ----
locktag->locktag_type,
lockmode);
! status = WaitOnLock(locallock, owner);
TRACE_POSTGRESQL_LOCK_WAIT_DONE(locktag->locktag_field1,
locktag->locktag_field2,
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 878,905 ****
* done when the lock was granted to us --- see notes in WaitOnLock.
*/
! /*
! * Check the proclock entry status, in case something in the ipc
! * communication doesn't work correctly.
! */
! if (!(proclock->holdMask & LOCKBIT_ON(lockmode)))
{
! AbortStrongLockAcquire();
! PROCLOCK_PRINT("LockAcquire: INCONSISTENT", proclock);
! LOCK_PRINT("LockAcquire: INCONSISTENT", lock, lockmode);
! /* Should we retry ? */
! LWLockRelease(partitionLock);
! elog(ERROR, "LockAcquire failed");
}
- PROCLOCK_PRINT("LockAcquire: granted", proclock);
- LOCK_PRINT("LockAcquire: granted", lock, lockmode);
}
! /*
! * Lock state is fully up-to-date now; if we error out after this, no
! * special error cleanup is required.
! */
! FinishStrongLockAcquire();
LWLockRelease(partitionLock);
--- 878,928 ----
* done when the lock was granted to us --- see notes in WaitOnLock.
*/
! switch (status)
{
! case STATUS_OK:
! /*
! * Check the proclock entry status, in case something in the ipc
! * communication doesn't work correctly.
! */
! if (!(proclock->holdMask & LOCKBIT_ON(lockmode)))
! {
! AbortStrongLockAcquire();
! PROCLOCK_PRINT("LockAcquire: INCONSISTENT", proclock);
! LOCK_PRINT("LockAcquire: INCONSISTENT", lock, lockmode);
! /* Should we retry ? */
! LWLockRelease(partitionLock);
! elog(ERROR, "LockAcquire failed");
! }
! PROCLOCK_PRINT("LockAcquire: granted", proclock);
! LOCK_PRINT("LockAcquire: granted", lock, lockmode);
! break;
! case STATUS_WAITING:
! PROCLOCK_PRINT("LockAcquire: timed out", proclock);
! LOCK_PRINT("LockAcquire: timed out", lock, lockmode);
! break;
! default:
! elog(ERROR, "LockAcquire invalid status");
! break;
}
}
! if (status == STATUS_WAITING)
! {
! /*
! * lock_timeout was set and WaitOnLock() indicated
! * we timed out. Clean up manually.
! */
! AbortStrongLockAcquire();
! }
! else
! {
! /*
! * Lock state is fully up-to-date now; if we error out after this, no
! * special error cleanup is required.
! */
! FinishStrongLockAcquire();
! }
LWLockRelease(partitionLock);
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 918,924 ****
locktag->locktag_field2);
}
! return LOCKACQUIRE_OK;
}
/*
--- 941,947 ----
locktag->locktag_field2);
}
! return (status == STATUS_OK ? LOCKACQUIRE_OK : LOCKACQUIRE_NOT_AVAIL);
}
/*
*************** GrantAwaitedLock(void)
*** 1437,1450 ****
* Caller must have set MyProc->heldLocks to reflect locks already held
* on the lockable object by this process.
*
* The appropriate partition lock must be held at entry.
*/
! static void
WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner)
{
LOCKMETHODID lockmethodid = LOCALLOCK_LOCKMETHOD(*locallock);
LockMethod lockMethodTable = LockMethods[lockmethodid];
char *volatile new_status = NULL;
LOCK_PRINT("WaitOnLock: sleeping on lock",
locallock->lock, locallock->tag.mode);
--- 1460,1479 ----
* Caller must have set MyProc->heldLocks to reflect locks already held
* on the lockable object by this process.
*
+ * Result: returns value of ProcSleep()
+ * STATUS_OK if we acquired the lock
+ * STATUS_ERROR if not (deadlock)
+ * STATUS_WAITING if not (timeout)
+ *
* The appropriate partition lock must be held at entry.
*/
! static int
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);
*************** WaitOnLock(LOCALLOCK *locallock, Resourc
*** 1486,1493 ****
*/
PG_TRY();
{
! if (ProcSleep(locallock, lockMethodTable) != STATUS_OK)
{
/*
* We failed as a result of a deadlock, see CheckDeadLock(). Quit
* now.
--- 1515,1527 ----
*/
PG_TRY();
{
! wait_status = ProcSleep(locallock, lockMethodTable);
! switch (wait_status)
{
+ case STATUS_OK:
+ case STATUS_WAITING:
+ break;
+ default:
/*
* We failed as a result of a deadlock, see CheckDeadLock(). Quit
* now.
*************** WaitOnLock(LOCALLOCK *locallock, Resourc
*** 1532,1539 ****
pfree(new_status);
}
! LOCK_PRINT("WaitOnLock: wakeup on lock",
locallock->lock, locallock->tag.mode);
}
/*
--- 1566,1579 ----
pfree(new_status);
}
! 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);
+
+ return wait_status;
}
/*
*************** VirtualXactLock(VirtualTransactionId vxi
*** 3853,3859 ****
LWLockRelease(proc->backendLock);
/* Time to wait. */
! (void) LockAcquire(&tag, ShareLock, false, false);
LockRelease(&tag, ShareLock, false);
return true;
--- 3893,3903 ----
LWLockRelease(proc->backendLock);
/* Time to wait. */
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on virtual transaction with ID %u",
! vxid.localTransactionId)));
LockRelease(&tag, ShareLock, false);
return true;
diff -dcrpN 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-06-19 11:34:32.253831350 +0200
--- postgresql.2/src/backend/storage/lmgr/proc.c 2012-06-19 12:13:16.515989607 +0200
*************** LockErrorCleanup(void)
*** 638,645 ****
if (lockAwaited == NULL)
return;
! /* Turn off the deadlock timer, if it's still running (see ProcSleep) */
disable_timeout(DEADLOCK_TIMEOUT, false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
--- 638,649 ----
if (lockAwaited == NULL)
return;
! /*
! * Turn off the deadlock and lock timeout timers,
! * if they are still running (see ProcSleep)
! */
disable_timeout(DEADLOCK_TIMEOUT, false);
+ disable_timeout(LOCK_TIMEOUT, false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
*************** ProcQueueInit(PROC_QUEUE *queue)
*** 875,881 ****
* 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).
*
* ASSUME: that no one will fiddle with the queue until after
* we release the partition lock.
--- 879,888 ----
* 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)
! * STATUS_WAITING if not (timeout)
*
* ASSUME: that no one will fiddle with the queue until after
* we release the partition lock.
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 897,902 ****
--- 904,910 ----
LOCKMASK myHeldLocks = MyProc->heldLocks;
bool early_deadlock = false;
bool allow_autovacuum_cancel = true;
+ bool timeout_detected = false;
int myWaitStatus;
PGPROC *proc;
int i;
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1038,1045 ****
elog(FATAL, "could not set timer for process wakeup");
/*
! * 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
* saved wakeup might be leftover from a previous operation (for example,
* we aborted ProcWaitForSignal just before someone did ProcSendSignal).
--- 1046,1059 ----
elog(FATAL, "could not set timer for process wakeup");
/*
! * Queue the timer for lock timeout, too.
! */
! if (!enable_timeout(LOCK_TIMEOUT, LockTimeout))
! elog(FATAL, "could not set timer for process wakeup");
!
! /*
! * 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).
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1056,1062 ****
*/
do
{
! PGSemaphoreLock(&MyProc->sem, true);
/*
* waitStatus could change from STATUS_WAITING to something else
--- 1070,1081 ----
*/
do
{
! PGSemaphoreTimedLock(&MyProc->sem, true);
!
! /* Check and keep the lock timeout indicator for later checks */
! timeout_detected = get_timeout_indicator(LOCK_TIMEOUT);
! if (timeout_detected)
! break;
/*
* waitStatus could change from STATUS_WAITING to something else
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1185,1196 ****
} while (myWaitStatus == STATUS_WAITING);
/*
! * Disable the timer, if it's still running
*/
if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for process wakeup");
/*
* Re-acquire the lock table's partition lock. We have to do this to hold
* off cancel/die interrupts before we can mess with lockAwaited (else we
* might have a missed or duplicated locallock update).
--- 1204,1221 ----
} while (myWaitStatus == STATUS_WAITING);
/*
! * Disable the deadlock timer, if it's still running
*/
if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for process wakeup");
/*
+ * Disable the lock timeout timer, if it's still running
+ */
+ if (!disable_timeout(LOCK_TIMEOUT, false))
+ elog(FATAL, "could not disable timer for process wakeup");
+
+ /*
* Re-acquire the lock table's partition lock. We have to do this to hold
* off cancel/die interrupts before we can mess with lockAwaited (else we
* might have a missed or duplicated locallock update).
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1198,1203 ****
--- 1223,1237 ----
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;
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1211,1218 ****
/*
* We don't have to do anything else, because the awaker did all the
* necessary update of the lock table and MyProc.
*/
! return MyProc->waitStatus;
}
--- 1245,1254 ----
/*
* 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 (timeout_detected ? STATUS_WAITING : MyProc->waitStatus);
}
diff -dcrpN postgresql.1/src/backend/storage/lmgr/timeout.c postgresql.2/src/backend/storage/lmgr/timeout.c
*** postgresql.1/src/backend/storage/lmgr/timeout.c 2012-06-19 13:07:18.215857100 +0200
--- postgresql.2/src/backend/storage/lmgr/timeout.c 2012-06-19 13:07:34.928961485 +0200
***************
*** 22,32 ****
--- 22,36 ----
#include "storage/proctimeout.h"
#include "storage/standby.h"
#include "storage/timeout.h"
+ #include "utils/guc.h"
#include "utils/timestamp.h"
/* GUC variables */
int DeadlockTimeout = 1000;
int StatementTimeout = 0;
+ int LockTimeout = 0;
+
+ int LockTimeoutOption = LOCK_TIMEOUT_PER_LOCK;
/*
* Infrastructure for timeouts
*************** static void InitTimeout(TimeoutName tn,
*** 36,41 ****
--- 40,47 ----
static void DestroyTimeout(TimeoutName tn, bool keep_indicator);
/* CheckDeadLock() is declared in storage/proctimeout.h */
+ static void CheckLockTimeout(void);
+ static TimestampTz GetLockTimeoutStart(void);
static void CheckStatementTimeout(void);
static void CheckStandbyDeadLock(void);
static void CheckStandbyTimeout(void);
*************** static timeout_params base_timeouts[TIME
*** 71,76 ****
--- 77,88 ----
},
{
+ LOCK_TIMEOUT, false, false,
+ CheckLockTimeout, GetLockTimeoutStart,
+ 0
+ },
+
+ {
STATEMENT_TIMEOUT, false, false,
CheckStatementTimeout, GetCurrentStatementStartTimestamp,
0
*************** DestroyTimeout(TimeoutName tn, bool keep
*** 215,220 ****
--- 227,259 ----
}
/*
+ * CheckLockTimeout
+ * NOP - there is nothing to do for LockTimeout
+ */
+ static void
+ CheckLockTimeout(void)
+ {
+ }
+
+ static TimestampTz
+ GetLockTimeoutStart(void)
+ {
+ switch (LockTimeoutOption)
+ {
+ case LOCK_TIMEOUT_PER_LOCK:
+ return GetCurrentTimestamp();
+ case LOCK_TIMEOUT_PER_STMT:
+ return GetCurrentStatementStartTimestamp();
+ default:
+ elog(ERROR, "unhandled lock_timeout_option value: \"%s\"",
+ GetConfigOptionByName("lock_timeout_option", NULL));
+ break;
+ }
+ /* Silence possible warnings. */
+ return 0;
+ }
+
+ /*
* CheckStatementTimeout
*/
static void
diff -dcrpN postgresql.1/src/backend/utils/adt/lockfuncs.c postgresql.2/src/backend/utils/adt/lockfuncs.c
*** postgresql.1/src/backend/utils/adt/lockfuncs.c 2012-06-11 06:22:48.162921604 +0200
--- postgresql.2/src/backend/utils/adt/lockfuncs.c 2012-06-17 19:36:22.754266214 +0200
*************** pg_advisory_lock_int8(PG_FUNCTION_ARGS)
*** 421,427 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ExclusiveLock, true, false);
PG_RETURN_VOID();
}
--- 421,431 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ExclusiveLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_int8(PG_FUNCTION_A
*** 438,444 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
PG_RETURN_VOID();
}
--- 442,452 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_shared_int8(PG_FUNCTION
*** 454,460 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ShareLock, true, false);
PG_RETURN_VOID();
}
--- 462,472 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ShareLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_shared_int8(PG_FUN
*** 471,477 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ShareLock, false, false);
PG_RETURN_VOID();
}
--- 483,493 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_int4(PG_FUNCTION_ARGS)
*** 604,610 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ExclusiveLock, true, false);
PG_RETURN_VOID();
}
--- 620,630 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ExclusiveLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_int4(PG_FUNCTION_A
*** 622,628 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
PG_RETURN_VOID();
}
--- 642,652 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_shared_int4(PG_FUNCTION
*** 639,645 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ShareLock, true, false);
PG_RETURN_VOID();
}
--- 663,673 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ShareLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_shared_int4(PG_FUN
*** 657,663 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ShareLock, false, false);
PG_RETURN_VOID();
}
--- 685,695 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
diff -dcrpN 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-06-17 19:35:23.003926485 +0200
--- postgresql.2/src/backend/utils/misc/guc.c 2012-06-19 12:18:37.534939985 +0200
*************** static const struct config_enum_entry sy
*** 389,394 ****
--- 389,405 ----
};
/*
+ * 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[];
*************** static struct config_int ConfigureNamesI
*** 1861,1866 ****
--- 1872,1888 ----
},
{
+ {"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
*************** static struct config_enum ConfigureNames
*** 3141,3146 ****
--- 3163,3178 ----
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 -dcrpN 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-06-19 12:19:18.590189356 +0200
***************
*** 528,533 ****
--- 528,538 ----
#------------------------------------------------------------------------------
#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 -dcrpN postgresql.1/src/include/storage/pg_sema.h postgresql.2/src/include/storage/pg_sema.h
*** postgresql.1/src/include/storage/pg_sema.h 2012-04-16 19:57:22.672918205 +0200
--- postgresql.2/src/include/storage/pg_sema.h 2012-06-17 19:36:22.758266237 +0200
*************** extern void PGSemaphoreUnlock(PGSemaphor
*** 80,83 ****
--- 80,89 ----
/* 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 void PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK);
+
#endif /* PG_SEMA_H */
diff -dcrpN postgresql.1/src/include/storage/timeout.h postgresql.2/src/include/storage/timeout.h
*** postgresql.1/src/include/storage/timeout.h 2012-06-19 11:14:15.901547376 +0200
--- postgresql.2/src/include/storage/timeout.h 2012-06-19 12:19:01.454085266 +0200
***************
*** 19,33 ****
--- 19,42 ----
/* configurable options */
extern int DeadlockTimeout;
extern int StatementTimeout;
+ extern int LockTimeout;
typedef enum TimeoutName {
DEADLOCK_TIMEOUT,
+ LOCK_TIMEOUT,
STATEMENT_TIMEOUT,
STANDBY_DEADLOCK_TIMEOUT,
STANDBY_TIMEOUT,
TIMEOUT_MAX
} TimeoutName;
+ typedef enum LockTimeoutOptions {
+ LOCK_TIMEOUT_PER_LOCK,
+ LOCK_TIMEOUT_PER_STMT
+ } LockTimeoutOptions;
+
+ extern int LockTimeoutOption;
+
extern bool enable_timeout(TimeoutName tn, int delayms);
extern bool disable_timeout(TimeoutName tn, bool keep_indicator);
extern bool disable_all_timeouts(bool keep_indicators);
diff -dcrpN 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-06-19 13:06:06.374408235 +0200
*************** set statement_timeout to 2000;
*** 198,203 ****
--- 198,223 ----
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: could not obtain lock on relation "pxtest3"
+ LINE 1: 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;
+ ERROR: could not obtain lock on relation "pxtest3"
+ LINE 1: SELECT * FROM pxtest3;
+ ^
+ 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
*************** set statement_timeout to 2000;
*** 213,218 ****
--- 233,245 ----
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: could not obtain lock on relation "pxtest3"
+ LINE 1: SELECT * FROM pxtest3;
+ ^
+ reset lock_timeout;
-- Commit table creation
COMMIT PREPARED 'regress-one';
\d pxtest2
diff -dcrpN 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-06-19 13:03:34.550458762 +0200
*************** set statement_timeout to 2000;
*** 126,131 ****
--- 126,147 ----
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 -
*************** set statement_timeout to 2000;
*** 137,142 ****
--- 153,163 ----
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
Excerpts from Boszormenyi Zoltan's message of mar jun 19 04:44:35 -0400 2012:
2012-06-18 19:46 keltezéssel, Alvaro Herrera írta:
Excerpts from Boszormenyi Zoltan's message of vie may 11 03:54:13 -0400 2012:
Hi,
another rebasing and applied the GIT changes in
ada8fa08fc6cf5f199b6df935b4d0a730aaa4fec to the
Windows implementation of PGSemaphoreTimedLock.Hi,
I gave the framework patch a look. One thing I'm not sure about is the
way you've defined the API. It seems a bit strange to have a nice and
clean, generic interface in timeout.h; and then have the internal
implementation of the API cluttered with details such as what to do when
the deadlock timeout expires. Wouldn't it be much better if we could
keep the details of CheckDeadLock itself within proc.c, for example?Do you mean adding a callback function argument to for enable_timeout()
would be better?
Well, that's not exactly what I was thinking, but maybe your idea is
better than what I had in mind.
Same for the other specific Check*Timeout functions. It seems to me
that the only timeout.c specific requirement is to be able to poke
base_timeouts[].indicator and fin_time. Maybe that can get done in
timeout.c and then have the generic checker call the module-specific
checker.Or instead of static functions, Check* functions can be external
to timeout.c. It seemed to be a good idea to move all the timeout
related functions to timeout.c.
Yeah, except that they play with members of the timeout_params struct,
which hopefully does not need to be exported. So if you can just move
(that is, put back in their original places) the portions that do not
touch that strcut, it'd be great.
Also, I see you're calling GetCurrentTimestamp() in the checkers; maybe
more than once per signal if you have multiple timeouts enabled.Actually, GetCurrentTimestamp() is not called multiple times,
because only the first timeout source in the queue can get triggered.
I see.
BTW it doesn't seem that datatype/timestamp.h is really necessary in
timeout.h.You are wrong. get_timeout_start() returns TimestampTz and it's
defined in datatype/timestamp.h.
Oh, right.
--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support
2012-06-19 16:54 keltezéssel, Alvaro Herrera írta:
Excerpts from Boszormenyi Zoltan's message of mar jun 19 04:44:35 -0400 2012:
2012-06-18 19:46 keltezéssel, Alvaro Herrera írta:
Excerpts from Boszormenyi Zoltan's message of vie may 11 03:54:13 -0400 2012:
Hi,
another rebasing and applied the GIT changes in
ada8fa08fc6cf5f199b6df935b4d0a730aaa4fec to the
Windows implementation of PGSemaphoreTimedLock.Hi,
I gave the framework patch a look. One thing I'm not sure about is the
way you've defined the API. It seems a bit strange to have a nice and
clean, generic interface in timeout.h; and then have the internal
implementation of the API cluttered with details such as what to do when
the deadlock timeout expires. Wouldn't it be much better if we could
keep the details of CheckDeadLock itself within proc.c, for example?Do you mean adding a callback function argument to for enable_timeout()
would be better?Well, that's not exactly what I was thinking, but maybe your idea is
better than what I had in mind.Same for the other specific Check*Timeout functions. It seems to me
that the only timeout.c specific requirement is to be able to poke
base_timeouts[].indicator and fin_time. Maybe that can get done in
timeout.c and then have the generic checker call the module-specific
checker.Or instead of static functions, Check* functions can be external
to timeout.c. It seemed to be a good idea to move all the timeout
related functions to timeout.c.Yeah, except that they play with members of the timeout_params struct,
which hopefully does not need to be exported. So if you can just move
(that is, put back in their original places) the portions that do not
touch that strcut, it'd be great.
OK, all 4 Check* functions are now moved back into proc.c,
nothing outside of timeout.c touches anything in it. New patches
are attached.
--
----------------------------------
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/
Attachments:
1-timeout-framework-v7.patchtext/x-patch; name=1-timeout-framework-v7.patchDownload
diff -dcrpN postgresql/src/backend/postmaster/autovacuum.c postgresql.1/src/backend/postmaster/autovacuum.c
*** postgresql/src/backend/postmaster/autovacuum.c 2012-06-11 06:22:48.137921483 +0200
--- postgresql.1/src/backend/postmaster/autovacuum.c 2012-06-19 17:49:55.348479057 +0200
***************
*** 86,94 ****
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
- #include "storage/proc.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "tcop/tcopprot.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
--- 86,94 ----
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
+ #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
*************** AutoVacLauncherMain(int argc, char *argv
*** 484,490 ****
/* Forget any pending QueryCancel request */
QueryCancelPending = false;
! disable_sig_alarm(true);
QueryCancelPending = false; /* again in case timeout occurred */
/* Report the error to the server log */
--- 484,490 ----
/* Forget any pending QueryCancel request */
QueryCancelPending = false;
! disable_all_timeouts(false);
QueryCancelPending = false; /* again in case timeout occurred */
/* Report the error to the server log */
diff -dcrpN postgresql/src/backend/postmaster/postmaster.c postgresql.1/src/backend/postmaster/postmaster.c
*** postgresql/src/backend/postmaster/postmaster.c 2012-06-11 06:22:48.148921535 +0200
--- postgresql.1/src/backend/postmaster/postmaster.c 2012-06-19 17:49:55.351479077 +0200
***************
*** 112,118 ****
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
! #include "storage/proc.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/datetime.h"
--- 112,118 ----
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
! #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/datetime.h"
*************** BackendInitialize(Port *port)
*** 3486,3494 ****
* Ready to begin client interaction. We will give up and exit(1) after a
* time delay, so that a broken client can't hog a connection
* indefinitely. PreAuthDelay and any DNS interactions above don't count
! * against the time limit.
*/
! if (!enable_sig_alarm(AuthenticationTimeout * 1000, false))
elog(FATAL, "could not set timer for startup packet timeout");
/*
--- 3486,3494 ----
* Ready to begin client interaction. We will give up and exit(1) after a
* time delay, so that a broken client can't hog a connection
* indefinitely. PreAuthDelay and any DNS interactions above don't count
! * against the time limit. Use the deadlock timeout interface.
*/
! if (!enable_timeout(DEADLOCK_TIMEOUT, AuthenticationTimeout * 1000))
elog(FATAL, "could not set timer for startup packet timeout");
/*
*************** BackendInitialize(Port *port)
*** 3526,3532 ****
/*
* Disable the timeout, and prevent SIGTERM/SIGQUIT again.
*/
! if (!disable_sig_alarm(false))
elog(FATAL, "could not disable timer for startup packet timeout");
PG_SETMASK(&BlockSig);
}
--- 3526,3532 ----
/*
* Disable the timeout, and prevent SIGTERM/SIGQUIT again.
*/
! if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for startup packet timeout");
PG_SETMASK(&BlockSig);
}
diff -dcrpN postgresql/src/backend/postmaster/startup.c postgresql.1/src/backend/postmaster/startup.c
*** postgresql/src/backend/postmaster/startup.c 2012-04-16 19:57:22.442915536 +0200
--- postgresql.1/src/backend/postmaster/startup.c 2012-06-19 17:49:55.351479077 +0200
***************
*** 27,33 ****
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
! #include "storage/proc.h"
#include "utils/guc.h"
--- 27,33 ----
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
! #include "storage/timeout.h"
#include "utils/guc.h"
*************** StartupProcessMain(void)
*** 195,202 ****
pqsignal(SIGTERM, StartupProcShutdownHandler); /* request shutdown */
pqsignal(SIGQUIT, startupproc_quickdie); /* hard crash time */
if (EnableHotStandby)
! pqsignal(SIGALRM, handle_standby_sig_alarm); /* ignored unless
! * InHotStandby */
else
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
--- 195,202 ----
pqsignal(SIGTERM, StartupProcShutdownHandler); /* request shutdown */
pqsignal(SIGQUIT, startupproc_quickdie); /* hard crash time */
if (EnableHotStandby)
! pqsignal(SIGALRM, handle_sig_alarm); /* ignored unless
! * InHotStandby */
else
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
diff -dcrpN postgresql/src/backend/storage/ipc/standby.c postgresql.1/src/backend/storage/ipc/standby.c
*** postgresql/src/backend/storage/ipc/standby.c 2012-06-11 06:22:48.154921564 +0200
--- postgresql.1/src/backend/storage/ipc/standby.c 2012-06-19 17:49:55.352479081 +0200
***************
*** 23,32 ****
#include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
- #include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/sinvaladt.h"
#include "storage/standby.h"
#include "utils/ps_status.h"
#include "utils/timestamp.h"
--- 23,32 ----
#include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
#include "storage/procarray.h"
#include "storage/sinvaladt.h"
#include "storage/standby.h"
+ #include "storage/timeout.h"
#include "utils/ps_status.h"
#include "utils/timestamp.h"
*************** ResolveRecoveryConflictWithLock(Oid dbOi
*** 394,400 ****
void
ResolveRecoveryConflictWithBufferPin(void)
{
- bool sig_alarm_enabled = false;
TimestampTz ltime;
TimestampTz now;
--- 394,399 ----
*************** ResolveRecoveryConflictWithBufferPin(voi
*** 409,417 ****
* We're willing to wait forever for conflicts, so set timeout for
* deadlock check (only)
*/
! if (enable_standby_sig_alarm(now, now, true))
! sig_alarm_enabled = true;
! else
elog(FATAL, "could not set timer for process wakeup");
}
else if (now >= ltime)
--- 408,414 ----
* We're willing to wait forever for conflicts, so set timeout for
* deadlock check (only)
*/
! if (!enable_timeout(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout))
elog(FATAL, "could not set timer for process wakeup");
}
else if (now >= ltime)
*************** ResolveRecoveryConflictWithBufferPin(voi
*** 423,446 ****
}
else
{
/*
* Wake up at ltime, and check for deadlocks as well if we will be
* waiting longer than deadlock_timeout
*/
! if (enable_standby_sig_alarm(now, ltime, false))
! sig_alarm_enabled = true;
! else
elog(FATAL, "could not set timer for process wakeup");
}
/* Wait to be signaled by UnpinBuffer() */
ProcWaitForSignal();
! if (sig_alarm_enabled)
! {
! if (!disable_standby_sig_alarm())
! elog(FATAL, "could not disable timer for process wakeup");
! }
}
void
--- 420,447 ----
}
else
{
+ long secs, msecs;
+ int usecs;
+
/*
* Wake up at ltime, and check for deadlocks as well if we will be
* waiting longer than deadlock_timeout
*/
! if (!enable_timeout(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout))
! elog(FATAL, "could not set timer for process wakeup");
!
! TimestampDifference(now, ltime, &secs, &usecs);
! msecs = secs * 1000 + usecs / 1000;
!
! if (!enable_timeout(STANDBY_TIMEOUT, msecs))
elog(FATAL, "could not set timer for process wakeup");
}
/* Wait to be signaled by UnpinBuffer() */
ProcWaitForSignal();
! if (!disable_all_timeouts(false))
! elog(FATAL, "could not disable timer for process wakeup");
}
void
diff -dcrpN postgresql/src/backend/storage/lmgr/Makefile postgresql.1/src/backend/storage/lmgr/Makefile
*** postgresql/src/backend/storage/lmgr/Makefile 2012-04-16 19:57:22.458915722 +0200
--- postgresql.1/src/backend/storage/lmgr/Makefile 2012-06-19 17:49:55.353479085 +0200
*************** subdir = src/backend/storage/lmgr
*** 12,18 ****
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
! OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o predicate.o
include $(top_srcdir)/src/backend/common.mk
--- 12,18 ----
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
! OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o predicate.o timeout.o
include $(top_srcdir)/src/backend/common.mk
diff -dcrpN postgresql/src/backend/storage/lmgr/proc.c postgresql.1/src/backend/storage/lmgr/proc.c
*** postgresql/src/backend/storage/lmgr/proc.c 2012-06-11 06:22:48.156921573 +0200
--- postgresql.1/src/backend/storage/lmgr/proc.c 2012-06-19 17:59:28.649869905 +0200
***************
*** 47,59 ****
#include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/spin.h"
#include "utils/timestamp.h"
/* GUC variables */
- int DeadlockTimeout = 1000;
- int StatementTimeout = 0;
bool log_lock_waits = false;
/* Pointer to this process's PGPROC and PGXACT structs, if any */
--- 47,59 ----
#include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
+ #include "storage/proctimeout.h"
#include "storage/spin.h"
+ #include "storage/timeout.h"
#include "utils/timestamp.h"
/* GUC variables */
bool log_lock_waits = false;
/* Pointer to this process's PGPROC and PGXACT structs, if any */
*************** PGPROC *PreparedXactProcs = NULL;
*** 77,103 ****
/* If we are waiting for a lock, this points to the associated LOCALLOCK */
static LOCALLOCK *lockAwaited = NULL;
! /* Mark these volatile because they can be changed by signal handler */
! static volatile bool standby_timeout_active = false;
! static volatile bool statement_timeout_active = false;
! static volatile bool deadlock_timeout_active = false;
! static volatile DeadLockState deadlock_state = DS_NOT_YET_CHECKED;
! volatile bool cancel_from_timeout = false;
!
! /* timeout_start_time is set when log_lock_waits is true */
! static TimestampTz timeout_start_time;
!
! /* statement_fin_time is valid only if statement_timeout_active is true */
! static TimestampTz statement_fin_time;
! static TimestampTz statement_fin_time2; /* valid only in recovery */
!
static void RemoveProcFromArray(int code, Datum arg);
static void ProcKill(int code, Datum arg);
static void AuxiliaryProcKill(int code, Datum arg);
- static bool CheckStatementTimeout(void);
- static bool CheckStandbyTimeout(void);
-
/*
* Report shared-memory space needed by InitProcGlobal.
--- 77,88 ----
/* If we are waiting for a lock, this points to the associated LOCALLOCK */
static LOCALLOCK *lockAwaited = NULL;
! /* Mark this volatile because it can be changed by signal handler */
! static volatile DeadLockState deadlock_state;
static void RemoveProcFromArray(int code, Datum arg);
static void ProcKill(int code, Datum arg);
static void AuxiliaryProcKill(int code, Datum arg);
/*
* Report shared-memory space needed by InitProcGlobal.
*************** LockErrorCleanup(void)
*** 654,660 ****
return;
/* Turn off the deadlock timer, if it's still running (see ProcSleep) */
! disable_sig_alarm(false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
--- 639,645 ----
return;
/* Turn off the deadlock timer, if it's still running (see ProcSleep) */
! disable_timeout(DEADLOCK_TIMEOUT, false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1049,1055 ****
* 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 (!enable_sig_alarm(DeadlockTimeout, false))
elog(FATAL, "could not set timer for process wakeup");
/*
--- 1034,1040 ----
* 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 (!enable_timeout(DEADLOCK_TIMEOUT, DeadlockTimeout))
elog(FATAL, "could not set timer for process wakeup");
/*
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1139,1145 ****
DescribeLockTag(&buf, &locallock->tag.lock);
modename = GetLockmodeName(locallock->tag.lock.locktag_lockmethodid,
lockmode);
! TimestampDifference(timeout_start_time, GetCurrentTimestamp(),
&secs, &usecs);
msecs = secs * 1000 + usecs / 1000;
usecs = usecs % 1000;
--- 1124,1131 ----
DescribeLockTag(&buf, &locallock->tag.lock);
modename = GetLockmodeName(locallock->tag.lock.locktag_lockmethodid,
lockmode);
! TimestampDifference(get_timeout_start(DEADLOCK_TIMEOUT),
! GetCurrentTimestamp(),
&secs, &usecs);
msecs = secs * 1000 + usecs / 1000;
usecs = usecs % 1000;
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1201,1207 ****
/*
* Disable the timer, if it's still running
*/
! if (!disable_sig_alarm(false))
elog(FATAL, "could not disable timer for process wakeup");
/*
--- 1187,1193 ----
/*
* Disable the timer, if it's still running
*/
! if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for process wakeup");
/*
*************** ProcLockWakeup(LockMethod lockMethodTabl
*** 1345,1351 ****
* NB: this is run inside a signal handler, so be very wary about what is done
* here or in called routines.
*/
! static void
CheckDeadLock(void)
{
int i;
--- 1331,1337 ----
* NB: this is run inside a signal handler, so be very wary about what is done
* here or in called routines.
*/
! void
CheckDeadLock(void)
{
int i;
*************** ProcSendSignal(int pid)
*** 1500,1817 ****
PGSemaphoreUnlock(&proc->sem);
}
-
- /*****************************************************************************
- * SIGALRM interrupt support
- *
- * Maybe these should be in pqsignal.c?
- *****************************************************************************/
-
- /*
- * Enable the SIGALRM interrupt to fire after the specified delay
- *
- * Delay is given in milliseconds. Caller should be sure a SIGALRM
- * signal handler is installed before this is called.
- *
- * This code properly handles nesting of deadlock timeout alarms within
- * statement timeout alarms.
- *
- * Returns TRUE if okay, FALSE on failure.
- */
- bool
- enable_sig_alarm(int delayms, bool is_statement_timeout)
- {
- TimestampTz fin_time;
- struct itimerval timeval;
-
- if (is_statement_timeout)
- {
- /*
- * Begin statement-level timeout
- *
- * Note that we compute statement_fin_time with reference to the
- * statement_timestamp, but apply the specified delay without any
- * correction; that is, we ignore whatever time has elapsed since
- * statement_timestamp was set. In the normal case only a small
- * interval will have elapsed and so this doesn't matter, but there
- * are corner cases (involving multi-statement query strings with
- * embedded COMMIT or ROLLBACK) where we might re-initialize the
- * statement timeout long after initial receipt of the message. In
- * such cases the enforcement of the statement timeout will be a bit
- * inconsistent. This annoyance is judged not worth the cost of
- * performing an additional gettimeofday() here.
- */
- Assert(!deadlock_timeout_active);
- fin_time = GetCurrentStatementStartTimestamp();
- fin_time = TimestampTzPlusMilliseconds(fin_time, delayms);
- statement_fin_time = fin_time;
- cancel_from_timeout = false;
- statement_timeout_active = true;
- }
- else if (statement_timeout_active)
- {
- /*
- * Begin deadlock timeout with statement-level timeout active
- *
- * Here, we want to interrupt at the closer of the two timeout times.
- * If fin_time >= statement_fin_time then we need not touch the
- * existing timer setting; else set up to interrupt at the deadlock
- * timeout time.
- *
- * NOTE: in this case it is possible that this routine will be
- * interrupted by the previously-set timer alarm. This is okay
- * because the signal handler will do only what it should do according
- * to the state variables. The deadlock checker may get run earlier
- * than normal, but that does no harm.
- */
- timeout_start_time = GetCurrentTimestamp();
- fin_time = TimestampTzPlusMilliseconds(timeout_start_time, delayms);
- deadlock_timeout_active = true;
- if (fin_time >= statement_fin_time)
- return true;
- }
- else
- {
- /* Begin deadlock timeout with no statement-level timeout */
- deadlock_timeout_active = true;
- /* GetCurrentTimestamp can be expensive, so only do it if we must */
- if (log_lock_waits)
- timeout_start_time = GetCurrentTimestamp();
- }
-
- /* If we reach here, okay to set the timer interrupt */
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = delayms / 1000;
- timeval.it_value.tv_usec = (delayms % 1000) * 1000;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- return true;
- }
-
- /*
- * Cancel the SIGALRM timer, either for a deadlock timeout or a statement
- * timeout. If a deadlock timeout is canceled, any active statement timeout
- * remains in force.
- *
- * Returns TRUE if okay, FALSE on failure.
- */
- bool
- disable_sig_alarm(bool is_statement_timeout)
- {
- /*
- * Always disable the interrupt if it is active; this avoids being
- * interrupted by the signal handler and thereby possibly getting
- * confused.
- *
- * We will re-enable the interrupt if necessary in CheckStatementTimeout.
- */
- if (statement_timeout_active || deadlock_timeout_active)
- {
- struct itimerval timeval;
-
- MemSet(&timeval, 0, sizeof(struct itimerval));
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- {
- statement_timeout_active = false;
- cancel_from_timeout = false;
- deadlock_timeout_active = false;
- return false;
- }
- }
-
- /* Always cancel deadlock timeout, in case this is error cleanup */
- deadlock_timeout_active = false;
-
- /* Cancel or reschedule statement timeout */
- if (is_statement_timeout)
- {
- statement_timeout_active = false;
- cancel_from_timeout = false;
- }
- else if (statement_timeout_active)
- {
- if (!CheckStatementTimeout())
- return false;
- }
- return true;
- }
-
-
/*
* Check for statement timeout. If the timeout time has come,
! * trigger a query-cancel interrupt; if not, reschedule the SIGALRM
! * interrupt to occur at the right time.
! *
! * Returns true if okay, false if failed to set the interrupt.
*/
! static bool
CheckStatementTimeout(void)
{
- TimestampTz now;
-
- if (!statement_timeout_active)
- return true; /* do nothing if not active */
-
- now = GetCurrentTimestamp();
-
- if (now >= statement_fin_time)
- {
- /* Time to die */
- statement_timeout_active = false;
- cancel_from_timeout = true;
#ifdef HAVE_SETSID
! /* try to signal whole process group */
! kill(-MyProcPid, SIGINT);
#endif
! kill(MyProcPid, SIGINT);
! }
! else
! {
! /* Not time yet, so (re)schedule the interrupt */
! long secs;
! int usecs;
! struct itimerval timeval;
!
! TimestampDifference(now, statement_fin_time,
! &secs, &usecs);
!
! /*
! * It's possible that the difference is less than a microsecond;
! * ensure we don't cancel, rather than set, the interrupt.
! */
! if (secs == 0 && usecs == 0)
! usecs = 1;
! MemSet(&timeval, 0, sizeof(struct itimerval));
! timeval.it_value.tv_sec = secs;
! timeval.it_value.tv_usec = usecs;
! if (setitimer(ITIMER_REAL, &timeval, NULL))
! return false;
! }
!
! return true;
}
-
/*
! * Signal handler for SIGALRM for normal user backends
! *
! * Process deadlock check and/or statement timeout check, as needed.
! * To avoid various edge cases, we must be careful to do nothing
! * when there is nothing to be done. We also need to be able to
! * reschedule the timer interrupt if called before end of statement.
*/
void
! handle_sig_alarm(SIGNAL_ARGS)
! {
! int save_errno = errno;
!
! /* SIGALRM is cause for waking anything waiting on the process latch */
! if (MyProc)
! SetLatch(&MyProc->procLatch);
!
! if (deadlock_timeout_active)
! {
! deadlock_timeout_active = false;
! CheckDeadLock();
! }
!
! if (statement_timeout_active)
! (void) CheckStatementTimeout();
!
! errno = save_errno;
! }
!
! /*
! * Signal handler for SIGALRM in Startup process
! *
! * To avoid various edge cases, we must be careful to do nothing
! * when there is nothing to be done. We also need to be able to
! * reschedule the timer interrupt if called before end of statement.
! *
! * We set either deadlock_timeout_active or statement_timeout_active
! * or both. Interrupts are enabled if standby_timeout_active.
! */
! bool
! enable_standby_sig_alarm(TimestampTz now, TimestampTz fin_time, bool deadlock_only)
! {
! TimestampTz deadlock_time = TimestampTzPlusMilliseconds(now,
! DeadlockTimeout);
!
! if (deadlock_only)
! {
! /*
! * Wake up at deadlock_time only, then wait forever
! */
! statement_fin_time = deadlock_time;
! deadlock_timeout_active = true;
! statement_timeout_active = false;
! }
! else if (fin_time > deadlock_time)
! {
! /*
! * Wake up at deadlock_time, then again at fin_time
! */
! statement_fin_time = deadlock_time;
! statement_fin_time2 = fin_time;
! deadlock_timeout_active = true;
! statement_timeout_active = true;
! }
! else
! {
! /*
! * Wake only at fin_time because its fairly soon
! */
! statement_fin_time = fin_time;
! deadlock_timeout_active = false;
! statement_timeout_active = true;
! }
!
! if (deadlock_timeout_active || statement_timeout_active)
! {
! long secs;
! int usecs;
! struct itimerval timeval;
!
! TimestampDifference(now, statement_fin_time,
! &secs, &usecs);
! if (secs == 0 && usecs == 0)
! usecs = 1;
! MemSet(&timeval, 0, sizeof(struct itimerval));
! timeval.it_value.tv_sec = secs;
! timeval.it_value.tv_usec = usecs;
! if (setitimer(ITIMER_REAL, &timeval, NULL))
! return false;
! standby_timeout_active = true;
! }
!
! return true;
! }
!
! bool
! disable_standby_sig_alarm(void)
{
! /*
! * Always disable the interrupt if it is active; this avoids being
! * interrupted by the signal handler and thereby possibly getting
! * confused.
! *
! * We will re-enable the interrupt if necessary in CheckStandbyTimeout.
! */
! if (standby_timeout_active)
! {
! struct itimerval timeval;
!
! MemSet(&timeval, 0, sizeof(struct itimerval));
! if (setitimer(ITIMER_REAL, &timeval, NULL))
! {
! standby_timeout_active = false;
! return false;
! }
! }
!
! standby_timeout_active = false;
!
! return true;
}
/*
--- 1486,1514 ----
PGSemaphoreUnlock(&proc->sem);
}
/*
* Check for statement timeout. If the timeout time has come,
! * trigger a query-cancel interrupt.
*/
! void
CheckStatementTimeout(void)
{
#ifdef HAVE_SETSID
! /* try to signal whole process group */
! kill(-MyProcPid, SIGINT);
#endif
! kill(MyProcPid, SIGINT);
}
/*
! * CheckStandbyDeadLock() will trigger if the deadlock timeout
! * happens earlier than StandbyTimeout. If it triggers,
! * StandbyTimeout will still be rescheduled.
*/
void
! CheckStandbyDeadLock(void)
{
! SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
}
/*
*************** disable_standby_sig_alarm(void)
*** 1819,1899 ****
* SIGALRM handler. Timers will only be set when InHotStandby.
* We simply ignore any signals unless the timer has been set.
*/
- static bool
- CheckStandbyTimeout(void)
- {
- TimestampTz now;
- bool reschedule = false;
-
- standby_timeout_active = false;
-
- now = GetCurrentTimestamp();
-
- /*
- * Reschedule the timer if its not time to wake yet, or if we have both
- * timers set and the first one has just been reached.
- */
- if (now >= statement_fin_time)
- {
- if (deadlock_timeout_active)
- {
- /*
- * We're still waiting when we reach deadlock timeout, so send out
- * a request to have other backends check themselves for deadlock.
- * Then continue waiting until statement_fin_time, if that's set.
- */
- SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
- deadlock_timeout_active = false;
-
- /*
- * Begin second waiting period if required.
- */
- if (statement_timeout_active)
- {
- reschedule = true;
- statement_fin_time = statement_fin_time2;
- }
- }
- else
- {
- /*
- * We've now reached statement_fin_time, so ask all conflicts to
- * leave, so we can press ahead with applying changes in recovery.
- */
- SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
- }
- }
- else
- reschedule = true;
-
- if (reschedule)
- {
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- standby_timeout_active = true;
- }
-
- return true;
- }
-
void
! handle_standby_sig_alarm(SIGNAL_ARGS)
{
! int save_errno = errno;
!
! if (standby_timeout_active)
! (void) CheckStandbyTimeout();
!
! errno = save_errno;
}
--- 1516,1523 ----
* SIGALRM handler. Timers will only be set when InHotStandby.
* We simply ignore any signals unless the timer has been set.
*/
void
! CheckStandbyTimeout(void)
{
! SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
}
diff -dcrpN postgresql/src/backend/storage/lmgr/timeout.c postgresql.1/src/backend/storage/lmgr/timeout.c
*** postgresql/src/backend/storage/lmgr/timeout.c 1970-01-01 01:00:00.000000000 +0100
--- postgresql.1/src/backend/storage/lmgr/timeout.c 2012-06-19 18:00:18.922182361 +0200
***************
*** 0 ****
--- 1,423 ----
+ /*-------------------------------------------------------------------------
+ *
+ * timeout.c
+ * routines to manage timeout sources handled by SIGALRM
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/storage/lmgr/timeout.c
+ *
+ *-------------------------------------------------------------------------
+ */
+ #include "postgres.h"
+
+ #include <sys/time.h>
+
+ #include "access/xact.h"
+ #include "miscadmin.h"
+ #include "storage/proc.h"
+ #include "storage/proctimeout.h"
+ #include "storage/standby.h"
+ #include "storage/timeout.h"
+ #include "utils/timestamp.h"
+
+ /* GUC variables */
+ int DeadlockTimeout = 1000;
+ int StatementTimeout = 0;
+
+ /*
+ * Infrastructure for timeouts
+ */
+
+ static void InitTimeout(TimeoutName tn, TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyTimeout(TimeoutName tn, bool keep_indicator);
+
+ typedef void (*timeout_check)(void);
+ typedef TimestampTz (*timeout_start)(void);
+
+ typedef struct {
+ TimeoutName index;
+ bool resched_next;
+
+ /* volatile because it may be changed from the signal handler */
+ volatile bool indicator;
+
+ timeout_check timeout_check;
+ timeout_start timeout_start;
+
+ TimestampTz start_time;
+ TimestampTz fin_time;
+ } timeout_params;
+
+ /*
+ * List of possible timeout reasons in the order of enum TimeoutName.
+ * The priority of timeouts (in case two of them would trigger at the
+ * same time) is determined by this order: the earlier one in the list
+ * has higher priority.
+ */
+ static timeout_params base_timeouts[TIMEOUT_MAX] = {
+ {
+ DEADLOCK_TIMEOUT, true, false,
+ CheckDeadLock, GetCurrentTimestamp,
+ 0
+ },
+
+ {
+ STATEMENT_TIMEOUT, false, false,
+ CheckStatementTimeout, GetCurrentStatementStartTimestamp,
+ 0
+ },
+
+ {
+ STANDBY_DEADLOCK_TIMEOUT, true, false,
+ CheckStandbyDeadLock, GetCurrentTimestamp,
+ 0
+ },
+
+ {
+ STANDBY_TIMEOUT, false, false,
+ CheckStandbyTimeout, GetCurrentTimestamp,
+ 0
+ }
+ };
+
+ /*
+ * List of active timeouts ordered by their fin_time and priority.
+ */
+ static int n_timeouts = 0;
+ static timeout_params *timeouts[TIMEOUT_MAX];
+
+ /*****************************************************************************
+ * Internal helper functions
+ *****************************************************************************/
+
+ /*
+ * Find the index of a given timeout type in the active array
+ */
+ static int
+ find_active_timeout(TimeoutName tn)
+ {
+ int i;
+
+ for (i = 0; i < n_timeouts; i++)
+ {
+ if (timeouts[i]->index == tn)
+ return i;
+ }
+
+ return -1;
+ }
+
+ #define is_timeout_active(tn) (find_active_timeout(tn) >= 0)
+
+ /*
+ * Insert tn'th timeout into the list of timeouts at the given index
+ * if the previous timeout allows rescheduling the next one in the list.
+ */
+ static void
+ insert_timeout(TimeoutName tn, int index)
+ {
+ int i;
+
+ if (index > 0 && !timeouts[index-1]->resched_next)
+ return;
+
+ for (i = n_timeouts - 1; i >= index; i--)
+ timeouts[i+1] = timeouts[i];
+
+ timeouts[index] = &base_timeouts[tn];
+ n_timeouts++;
+ }
+
+ /*
+ * Remove the index'th element from the timeout list.
+ */
+ static void
+ remove_timeout_index(int index)
+ {
+ int i;
+
+ if (index < 0)
+ return;
+
+ for (i = index + 1; i < n_timeouts; i++)
+ timeouts[i-1] = timeouts[i];
+
+ if (n_timeouts > 0 && index < n_timeouts)
+ n_timeouts--;
+ }
+
+ /*
+ * (Re)schedule the next active timeout
+ */
+ static bool
+ schedule_timeout(TimestampTz now)
+ {
+ long secs;
+ int usecs;
+ struct itimerval timeval;
+
+ /* There is no active timeout, do nothing. */
+ if (n_timeouts == 0)
+ return true;
+
+ TimestampDifference(now, timeouts[0]->fin_time,
+ &secs, &usecs);
+
+ /*
+ * It's possible that the difference is less than a microsecond;
+ * ensure we don't cancel, rather than set, the interrupt.
+ */
+ if (secs == 0 && usecs == 0)
+ usecs = 1;
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ timeval.it_value.tv_sec = secs;
+ timeval.it_value.tv_usec = usecs;
+ if (setitimer(ITIMER_REAL, &timeval, NULL))
+ return false;
+ return true;
+ }
+
+ /*****************************************************************************
+ * Init, Destroy and Check functions for different timeouts.
+ *
+ * NB: all Check* functions are run inside a signal handler, so be very wary
+ * about what is done in them or in called routines.
+ *****************************************************************************/
+
+ /*
+ * Common Init and Destroy functions
+ */
+
+ static void
+ InitTimeout(TimeoutName tn, TimestampTz start_time, TimestampTz fin_time)
+ {
+ base_timeouts[tn].indicator = false;
+ base_timeouts[tn].start_time = start_time;
+ base_timeouts[tn].fin_time = fin_time;
+ }
+
+ static void
+ DestroyTimeout(TimeoutName tn, bool keep_indicator)
+ {
+ if (!keep_indicator)
+ base_timeouts[tn].indicator = false;
+ base_timeouts[tn].start_time = 0;
+ base_timeouts[tn].fin_time = 0;
+ }
+
+ /*****************************************************************************
+ * Public API
+ *****************************************************************************/
+
+ /*
+ * Enable the SIGALRM interrupt to fire after the specified delay
+ *
+ * Delay is given in milliseconds. Caller should be sure a SIGALRM
+ * signal handler is installed before this is called.
+ *
+ * This code properly handles nesting of different timeout alarms.
+ *
+ * Returns TRUE if okay, FALSE on failure to set the timer.
+ */
+ bool
+ enable_timeout(TimeoutName tn, int delayms)
+ {
+ TimestampTz start_time;
+ TimestampTz fin_time;
+ int i;
+
+ Assert(!is_timeout_active(tn));
+
+ if (delayms <= 0)
+ return true;
+
+ start_time = base_timeouts[tn].timeout_start();
+ fin_time = TimestampTzPlusMilliseconds(start_time, delayms);
+
+ /* Find out the index where to insert the new timeout. */
+ for (i = 0; i < n_timeouts; i++)
+ {
+ /*
+ * The new timeout triggers earlier than
+ * a previously added one: insert here.
+ */
+ if (fin_time < timeouts[i]->fin_time)
+ break;
+ /*
+ * The new timeout triggers at the same time
+ * as the previously added one but has greater priority.
+ */
+ if (fin_time == timeouts[i]->fin_time && tn < timeouts[i]->index)
+ break;
+ }
+
+ /*
+ * Initialize the timeout parameters
+ */
+ InitTimeout(tn, start_time, fin_time);
+
+ insert_timeout(tn, i);
+
+ if (i > 0)
+ return true;
+
+ /* If we reach here, okay to set the timer interrupt */
+ if (!schedule_timeout(start_time))
+ return false;
+ return true;
+ }
+
+ /*
+ * Cancel the SIGALRM timer for the specific timeout.
+ * If a timeout is canceled, any other active timeout remains in force.
+ *
+ * Returns TRUE if okay, FALSE on failure to set the timer for
+ * the next timeout source.
+ */
+ bool
+ disable_timeout(TimeoutName tn, bool keep_indicator)
+ {
+ int i;
+
+ /*
+ * Always disable the interrupt if it is active; this avoids being
+ * interrupted by the signal handler and thereby possibly getting
+ * confused.
+ *
+ * We will re-enable the interrupt if necessary in ->check_timeout().
+ */
+ if (n_timeouts > 0)
+ {
+ struct itimerval timeval;
+
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ if (setitimer(ITIMER_REAL, &timeval, NULL))
+ {
+ n_timeouts = 0;
+ for (i = 0; i < TIMEOUT_MAX; i++)
+ DestroyTimeout(i, false);
+
+ return false;
+ }
+ }
+
+ /* Find the timeout and remove from the list. */
+ i = find_active_timeout(tn);
+ remove_timeout_index(i);
+
+ /* Do cleanup. */
+ DestroyTimeout(tn, keep_indicator);
+
+ /*
+ * If the first timeout was removed from the list and there is
+ * at least one active left, reschedule it.
+ */
+ if (i == 0 && n_timeouts > 0)
+ if (!schedule_timeout(GetCurrentTimestamp()))
+ return false;
+
+ return true;
+ }
+
+ /*
+ * Disable SIGALRM and remove all timeouts from the list and
+ * reset the timeout indicators.
+ */
+ bool
+ disable_all_timeouts(bool keep_indicators)
+ {
+ struct itimerval timeval;
+ int i;
+ bool ret;
+
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ ret = (setitimer(ITIMER_REAL, &timeval, NULL) == 0);
+
+ n_timeouts = 0;
+ for (i = 0; i < TIMEOUT_MAX; i++)
+ DestroyTimeout(i, keep_indicators);
+
+ return ret;
+ }
+
+ /*
+ * Return the timeout indicator
+ */
+ bool
+ get_timeout_indicator(TimeoutName tn)
+ {
+ return base_timeouts[tn].indicator;
+ }
+
+ /*
+ * Return the start of the timer for this timeout
+ */
+ TimestampTz
+ get_timeout_start(TimeoutName tn)
+ {
+ return base_timeouts[tn].start_time;
+ }
+
+ /*
+ * Signal handler for SIGALRM
+ *
+ * Process the check for the currently active timeout source and
+ * reschedule the next as needed. To avoid various edge cases,
+ * we must be careful to do nothing when there is nothing to be done.
+ */
+ void
+ handle_sig_alarm(SIGNAL_ARGS)
+ {
+ int save_errno = errno;
+
+ /*
+ * SIGALRM is cause for waking anything waiting on the process latch.
+ * Recovery (the startup process) doesn't have MyProc set so
+ * it can also use this signal handler.
+ */
+ if (MyProc)
+ SetLatch(&MyProc->procLatch);
+
+ if (n_timeouts > 0)
+ {
+ TimestampTz now;
+ bool reschedule;
+
+ now = GetCurrentTimestamp();
+
+ /* If our time has not come yet, do nothing. */
+ if (now < timeouts[0]->fin_time)
+ goto out;
+
+ /*
+ * Set the trigger indicator before calling the checker function.
+ * Setting if after may have side effects that lead to detecting
+ * a different event, like "user pressed Ctrl-C" is detected instead
+ * of the statement timeout having triggered.
+ */
+ timeouts[0]->indicator = true;
+
+ /* Call the timeout checker. */
+ timeouts[0]->timeout_check();
+
+ /*
+ * Remove the current timeout source and reschedule
+ * the next if needed. Short circuit disable_timeout(..., true)
+ * for the timeout source that just triggered.
+ */
+ reschedule = timeouts[0]->resched_next;
+ remove_timeout_index(0);
+
+ if (reschedule)
+ schedule_timeout(now);
+ else
+ disable_all_timeouts(true);
+ }
+
+ out:
+ errno = save_errno;
+ }
diff -dcrpN postgresql/src/backend/tcop/postgres.c postgresql.1/src/backend/tcop/postgres.c
*** postgresql/src/backend/tcop/postgres.c 2012-06-11 06:22:48.157921579 +0200
--- postgresql.1/src/backend/tcop/postgres.c 2012-06-19 17:49:55.358479113 +0200
***************
*** 64,69 ****
--- 64,70 ----
#include "storage/proc.h"
#include "storage/procsignal.h"
#include "storage/sinval.h"
+ #include "storage/timeout.h"
#include "tcop/fastpath.h"
#include "tcop/pquery.h"
#include "tcop/tcopprot.h"
*************** start_xact_command(void)
*** 2396,2404 ****
/* Set statement timeout running, if any */
/* NB: this mustn't be enabled until we are within an xact */
if (StatementTimeout > 0)
! enable_sig_alarm(StatementTimeout, true);
else
! cancel_from_timeout = false;
xact_started = true;
}
--- 2397,2405 ----
/* Set statement timeout running, if any */
/* NB: this mustn't be enabled until we are within an xact */
if (StatementTimeout > 0)
! enable_timeout(STATEMENT_TIMEOUT, StatementTimeout);
else
! disable_timeout(STATEMENT_TIMEOUT, false);
xact_started = true;
}
*************** finish_xact_command(void)
*** 2410,2416 ****
if (xact_started)
{
/* Cancel any active statement timeout before committing */
! disable_sig_alarm(true);
/* Now commit the command */
ereport(DEBUG3,
--- 2411,2417 ----
if (xact_started)
{
/* Cancel any active statement timeout before committing */
! disable_all_timeouts(false);
/* Now commit the command */
ereport(DEBUG3,
*************** ProcessInterrupts(void)
*** 2891,2897 ****
(errcode(ERRCODE_QUERY_CANCELED),
errmsg("canceling authentication due to timeout")));
}
! if (cancel_from_timeout)
{
ImmediateInterruptOK = false; /* not idle anymore */
DisableNotifyInterrupt();
--- 2892,2898 ----
(errcode(ERRCODE_QUERY_CANCELED),
errmsg("canceling authentication due to timeout")));
}
! if (get_timeout_indicator(STATEMENT_TIMEOUT))
{
ImmediateInterruptOK = false; /* not idle anymore */
DisableNotifyInterrupt();
*************** PostgresMain(int argc, char *argv[], con
*** 3799,3808 ****
/*
* Forget any pending QueryCancel request, since we're returning to
! * the idle loop anyway, and cancel the statement timer if running.
*/
QueryCancelPending = false;
! disable_sig_alarm(true);
QueryCancelPending = false; /* again in case timeout occurred */
/*
--- 3800,3809 ----
/*
* Forget any pending QueryCancel request, since we're returning to
! * the idle loop anyway, and cancel the timer if running.
*/
QueryCancelPending = false;
! disable_all_timeouts(false);
QueryCancelPending = false; /* again in case timeout occurred */
/*
diff -dcrpN postgresql/src/backend/utils/init/postinit.c postgresql.1/src/backend/utils/init/postinit.c
*** postgresql/src/backend/utils/init/postinit.c 2012-04-16 19:57:22.490916093 +0200
--- postgresql.1/src/backend/utils/init/postinit.c 2012-06-19 17:49:55.360479125 +0200
***************
*** 41,51 ****
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lmgr.h"
- #include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "storage/smgr.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
#include "utils/fmgroids.h"
--- 41,51 ----
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lmgr.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "storage/smgr.h"
+ #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
#include "utils/fmgroids.h"
*************** PerformAuthentication(Port *port)
*** 204,210 ****
* during authentication. Since we're inside a transaction and might do
* database access, we have to use the statement_timeout infrastructure.
*/
! if (!enable_sig_alarm(AuthenticationTimeout * 1000, true))
elog(FATAL, "could not set timer for authorization timeout");
/*
--- 204,210 ----
* during authentication. Since we're inside a transaction and might do
* database access, we have to use the statement_timeout infrastructure.
*/
! if (!enable_timeout(STATEMENT_TIMEOUT, AuthenticationTimeout * 1000))
elog(FATAL, "could not set timer for authorization timeout");
/*
*************** PerformAuthentication(Port *port)
*** 215,221 ****
/*
* Done with authentication. Disable the timeout, and log if needed.
*/
! if (!disable_sig_alarm(true))
elog(FATAL, "could not disable timer for authorization timeout");
if (Log_connections)
--- 215,221 ----
/*
* Done with authentication. Disable the timeout, and log if needed.
*/
! if (!disable_timeout(STATEMENT_TIMEOUT, false))
elog(FATAL, "could not disable timer for authorization timeout");
if (Log_connections)
diff -dcrpN postgresql/src/backend/utils/misc/guc.c postgresql.1/src/backend/utils/misc/guc.c
*** postgresql/src/backend/utils/misc/guc.c 2012-06-11 06:22:48.193921753 +0200
--- postgresql.1/src/backend/utils/misc/guc.c 2012-06-19 17:49:55.364479146 +0200
***************
*** 63,68 ****
--- 63,69 ----
#include "storage/standby.h"
#include "storage/fd.h"
#include "storage/predicate.h"
+ #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "tsearch/ts_cache.h"
#include "utils/builtins.h"
diff -dcrpN postgresql/src/include/storage/proc.h postgresql.1/src/include/storage/proc.h
*** postgresql/src/include/storage/proc.h 2012-06-11 06:22:48.223921899 +0200
--- postgresql.1/src/include/storage/proc.h 2012-06-19 17:49:55.365479152 +0200
*************** extern PGPROC *PreparedXactProcs;
*** 218,230 ****
/* configurable options */
- extern int DeadlockTimeout;
- extern int StatementTimeout;
extern bool log_lock_waits;
- extern volatile bool cancel_from_timeout;
-
-
/*
* Function Prototypes
*/
--- 218,225 ----
*************** extern void LockErrorCleanup(void);
*** 252,264 ****
extern void ProcWaitForSignal(void);
extern void ProcSendSignal(int pid);
- extern bool enable_sig_alarm(int delayms, bool is_statement_timeout);
- extern bool disable_sig_alarm(bool is_statement_timeout);
- extern void handle_sig_alarm(SIGNAL_ARGS);
-
- extern bool enable_standby_sig_alarm(TimestampTz now,
- TimestampTz fin_time, bool deadlock_only);
- extern bool disable_standby_sig_alarm(void);
- extern void handle_standby_sig_alarm(SIGNAL_ARGS);
-
#endif /* PROC_H */
--- 247,250 ----
diff -dcrpN postgresql/src/include/storage/proctimeout.h postgresql.1/src/include/storage/proctimeout.h
*** postgresql/src/include/storage/proctimeout.h 1970-01-01 01:00:00.000000000 +0100
--- postgresql.1/src/include/storage/proctimeout.h 2012-06-19 18:32:03.951063303 +0200
***************
*** 0 ****
--- 1,22 ----
+ /*-------------------------------------------------------------------------
+ *
+ * proctimeout.h
+ * declaration of timeout checker functions
+ *
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/storage/proctimeout.h
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef _PROCTIMEOUT_H_
+ #define _PROCTIMEOUT_H_
+
+ extern void CheckDeadLock(void);
+ extern void CheckStatementTimeout(void);
+ extern void CheckStandbyDeadLock(void);
+ extern void CheckStandbyTimeout(void);
+
+ #endif /* _PROCTIMEOUT_H_ */
diff -dcrpN postgresql/src/include/storage/timeout.h postgresql.1/src/include/storage/timeout.h
*** postgresql/src/include/storage/timeout.h 1970-01-01 01:00:00.000000000 +0100
--- postgresql.1/src/include/storage/timeout.h 2012-06-19 17:49:55.365479152 +0200
***************
*** 0 ****
--- 1,38 ----
+ /*-------------------------------------------------------------------------
+ *
+ * timeout.h
+ * SIGALRM timeout API
+ *
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/storage/timeout.h
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef _TIMEOUT_H_
+ #define _TIMEOUT_H_
+
+ #include "datatype/timestamp.h"
+
+ /* configurable options */
+ extern int DeadlockTimeout;
+ extern int StatementTimeout;
+
+ typedef enum TimeoutName {
+ DEADLOCK_TIMEOUT,
+ STATEMENT_TIMEOUT,
+ STANDBY_DEADLOCK_TIMEOUT,
+ STANDBY_TIMEOUT,
+ TIMEOUT_MAX
+ } TimeoutName;
+
+ extern bool enable_timeout(TimeoutName tn, int delayms);
+ extern bool disable_timeout(TimeoutName tn, bool keep_indicator);
+ extern bool disable_all_timeouts(bool keep_indicators);
+ extern bool get_timeout_indicator(TimeoutName tn);
+ extern TimestampTz get_timeout_start(TimeoutName tn);
+ extern void handle_sig_alarm(SIGNAL_ARGS);
+
+ #endif /* _TIMEOUT_H_ */
2-lock_timeout-v7.patchtext/x-patch; name=2-lock_timeout-v7.patchDownload
diff -dcrpN postgresql.1/doc/src/sgml/config.sgml postgresql.2/doc/src/sgml/config.sgml
*** postgresql.1/doc/src/sgml/config.sgml 2012-05-11 09:23:17.030668613 +0200
--- postgresql.2/doc/src/sgml/config.sgml 2012-06-19 18:37:40.058979368 +0200
*************** COPY postgres_log FROM '/full/path/to/lo
*** 4980,4986 ****
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.
</para>
<para>
--- 4980,4989 ----
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. 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>
*************** COPY postgres_log FROM '/full/path/to/lo
*** 4991,4996 ****
--- 4994,5053 ----
</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 -dcrpN 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-06-19 18:17:11.658875780 +0200
*************** LOCK [ TABLE ] [ ONLY ] <replaceable cla
*** 39,46 ****
<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.)
</para>
--- 39,49 ----
<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. 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 -dcrpN 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-06-19 18:17:11.659875787 +0200
*************** FOR SHARE [ OF <replaceable class="param
*** 1199,1204 ****
--- 1199,1212 ----
</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 -dcrpN postgresql.1/src/backend/port/posix_sema.c postgresql.2/src/backend/port/posix_sema.c
*** postgresql.1/src/backend/port/posix_sema.c 2012-04-16 19:57:22.438915489 +0200
--- postgresql.2/src/backend/port/posix_sema.c 2012-06-19 18:17:11.659875787 +0200
***************
*** 24,29 ****
--- 24,30 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/timeout.h"
#ifdef USE_NAMED_POSIX_SEMAPHORES
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 313,315 ****
--- 314,341 ----
return true;
}
+
+ /*
+ * PGSemaphoreTimedLock
+ *
+ * Lock a semaphore (decrement count), blocking if count would be < 0
+ * Return if lock_timeout expired
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ int errStatus;
+
+ do
+ {
+ ImmediateInterruptOK = interruptOK;
+ CHECK_FOR_INTERRUPTS();
+ errStatus = sem_wait(PG_SEM_REF(sema));
+ ImmediateInterruptOK = false;
+ } while (errStatus < 0 && errno == EINTR && !get_timeout_indicator(LOCK_TIMEOUT));
+
+ if (get_timeout_indicator(LOCK_TIMEOUT))
+ return;
+ if (errStatus < 0)
+ elog(FATAL, "sem_wait failed: %m");
+ }
diff -dcrpN postgresql.1/src/backend/port/sysv_sema.c postgresql.2/src/backend/port/sysv_sema.c
*** postgresql.1/src/backend/port/sysv_sema.c 2012-05-14 08:20:56.284830580 +0200
--- postgresql.2/src/backend/port/sysv_sema.c 2012-06-19 18:17:11.659875788 +0200
***************
*** 27,32 ****
--- 27,33 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/timeout.h"
#ifndef HAVE_UNION_SEMUN
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 492,494 ****
--- 493,525 ----
return true;
}
+
+ /*
+ * PGSemaphoreTimedLock
+ *
+ * Lock a semaphore (decrement count), blocking if count would be < 0
+ * Return if lock_timeout expired
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ int errStatus;
+ 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;
+ } while (errStatus < 0 && errno == EINTR && !get_timeout_indicator(LOCK_TIMEOUT));
+
+ if (get_timeout_indicator(LOCK_TIMEOUT))
+ return;
+ if (errStatus < 0)
+ elog(FATAL, "semop(id=%d) failed: %m", sema->semId);
+ }
diff -dcrpN postgresql.1/src/backend/port/win32_sema.c postgresql.2/src/backend/port/win32_sema.c
*** postgresql.1/src/backend/port/win32_sema.c 2012-06-11 06:22:48.137921483 +0200
--- postgresql.2/src/backend/port/win32_sema.c 2012-06-19 18:17:11.660875795 +0200
***************
*** 16,21 ****
--- 16,22 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/timeout.h"
static HANDLE *mySemSet; /* IDs of sema sets acquired so far */
static int numSems; /* number of sema sets acquired so far */
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 209,211 ****
--- 210,271 ----
/* 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.
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ DWORD ret;
+ HANDLE wh[2];
+
+ /*
+ * 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;
+ } while (errno == EINTR && !get_timeout_indicator(LOCK_TIMEOUT));
+
+ if (get_timeout_indicator(LOCK_TIMEOUT))
+ return;
+ if (errno != 0)
+ ereport(FATAL,
+ (errmsg("could not lock semaphore: error code %d", (int) GetLastError())));
+ }
diff -dcrpN postgresql.1/src/backend/storage/lmgr/lmgr.c postgresql.2/src/backend/storage/lmgr/lmgr.c
*** postgresql.1/src/backend/storage/lmgr/lmgr.c 2012-04-16 19:57:22.459915733 +0200
--- postgresql.2/src/backend/storage/lmgr/lmgr.c 2012-06-19 18:17:11.661875801 +0200
***************
*** 19,26 ****
--- 19,29 ----
#include "access/transam.h"
#include "access/xact.h"
#include "catalog/catalog.h"
+ #include "catalog/pg_database.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
+ #include "utils/lsyscache.h"
+ #include "storage/proc.h"
#include "storage/procarray.h"
#include "utils/inval.h"
*************** LockRelationOid(Oid relid, LOCKMODE lock
*** 78,83 ****
--- 81,101 ----
res = LockAcquire(&tag, lockmode, false, false);
+ if (res == LOCKACQUIRE_NOT_AVAIL)
+ {
+ char *relname = get_rel_name(relid);
+ if (relname)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ relname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation with OID %u",
+ relid)));
+ }
+
/*
* Now that we have the lock, check for invalidation messages, so that we
* will update or flush any stale relcache entry before we try to use it.
*************** LockRelation(Relation relation, LOCKMODE
*** 174,179 ****
--- 192,203 ----
res = LockAcquire(&tag, lockmode, false, false);
+ if (res == LOCKACQUIRE_NOT_AVAIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ RelationGetRelationName(relation))));
+
/*
* Now that we have the lock, check for invalidation messages; see notes
* in LockRelationOid.
*************** LockRelationIdForSession(LockRelId *reli
*** 251,257 ****
SET_LOCKTAG_RELATION(tag, relid->dbId, relid->relId);
! (void) LockAcquire(&tag, lockmode, true, false);
}
/*
--- 275,294 ----
SET_LOCKTAG_RELATION(tag, relid->dbId, relid->relId);
! if (LockAcquire(&tag, lockmode, true, false) == LOCKACQUIRE_NOT_AVAIL)
! {
! char *relname = get_rel_name(relid->relId);
! if (relname)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on relation \"%s\"",
! relname)));
! else
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on relation with OID %u",
! relid->relId)));
! }
}
/*
*************** LockRelationForExtension(Relation relati
*** 286,292 ****
relation->rd_lockInfo.lockRelId.dbId,
relation->rd_lockInfo.lockRelId.relId);
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 323,333 ----
relation->rd_lockInfo.lockRelId.dbId,
relation->rd_lockInfo.lockRelId.relId);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on index \"%s\"",
! RelationGetRelationName(relation))));
}
/*
*************** LockPage(Relation relation, BlockNumber
*** 320,326 ****
relation->rd_lockInfo.lockRelId.relId,
blkno);
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 361,371 ----
relation->rd_lockInfo.lockRelId.relId,
blkno);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on page %u of relation \"%s\"",
! blkno, RelationGetRelationName(relation))));
}
/*
*************** LockTuple(Relation relation, ItemPointer
*** 376,382 ****
ItemPointerGetBlockNumber(tid),
ItemPointerGetOffsetNumber(tid));
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 421,431 ----
ItemPointerGetBlockNumber(tid),
ItemPointerGetOffsetNumber(tid));
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on row in relation \"%s\"",
! RelationGetRelationName(relation))));
}
/*
*************** XactLockTableInsert(TransactionId xid)
*** 430,436 ****
SET_LOCKTAG_TRANSACTION(tag, xid);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
}
/*
--- 479,488 ----
SET_LOCKTAG_TRANSACTION(tag, xid);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on transaction with ID %u", xid)));
}
/*
*************** XactLockTableWait(TransactionId xid)
*** 474,480 ****
SET_LOCKTAG_TRANSACTION(tag, xid);
! (void) LockAcquire(&tag, ShareLock, false, false);
LockRelease(&tag, ShareLock, false);
--- 526,535 ----
SET_LOCKTAG_TRANSACTION(tag, xid);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on transaction with ID %u", xid)));
LockRelease(&tag, ShareLock, false);
*************** LockDatabaseObject(Oid classid, Oid obji
*** 535,541 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, false, false);
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
--- 590,600 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
*************** LockSharedObject(Oid classid, Oid objid,
*** 576,582 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, false, false);
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
--- 635,645 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
*************** LockSharedObjectForSession(Oid classid,
*** 618,624 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, true, false);
}
/*
--- 681,702 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, true, false) == LOCKACQUIRE_NOT_AVAIL)
! switch(classid)
! {
! case DatabaseRelationId:
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on database with ID %u",
! objid)));
! break;
! default:
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
! break;
! }
}
/*
diff -dcrpN postgresql.1/src/backend/storage/lmgr/lock.c postgresql.2/src/backend/storage/lmgr/lock.c
*** postgresql.1/src/backend/storage/lmgr/lock.c 2012-06-11 06:22:48.154921564 +0200
--- postgresql.2/src/backend/storage/lmgr/lock.c 2012-06-19 18:17:11.663875813 +0200
*************** static PROCLOCK *SetupLockInTable(LockMe
*** 343,349 ****
static void GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner);
static void BeginStrongLockAcquire(LOCALLOCK *locallock, uint32 fasthashcode);
static void FinishStrongLockAcquire(void);
! static void WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner);
static void ReleaseLockIfHeld(LOCALLOCK *locallock, bool sessionLock);
static bool UnGrantLock(LOCK *lock, LOCKMODE lockmode,
PROCLOCK *proclock, LockMethod lockMethodTable);
--- 343,349 ----
static void GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner);
static void BeginStrongLockAcquire(LOCALLOCK *locallock, uint32 fasthashcode);
static void FinishStrongLockAcquire(void);
! static int WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner);
static void ReleaseLockIfHeld(LOCALLOCK *locallock, bool sessionLock);
static bool UnGrantLock(LOCK *lock, LOCKMODE lockmode,
PROCLOCK *proclock, LockMethod lockMethodTable);
*************** ProcLockHashCode(const PROCLOCKTAG *proc
*** 549,555 ****
* dontWait: if true, don't wait to acquire lock
*
* Returns one of:
! * LOCKACQUIRE_NOT_AVAIL lock not available, and dontWait=true
* LOCKACQUIRE_OK lock successfully acquired
* LOCKACQUIRE_ALREADY_HELD incremented count for lock already held
*
--- 549,555 ----
* dontWait: if true, don't wait to acquire lock
*
* Returns one of:
! * LOCKACQUIRE_NOT_AVAIL lock not available, either dontWait=true or timeout
* LOCKACQUIRE_OK lock successfully acquired
* LOCKACQUIRE_ALREADY_HELD incremented count for lock already held
*
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 863,869 ****
locktag->locktag_type,
lockmode);
! WaitOnLock(locallock, owner);
TRACE_POSTGRESQL_LOCK_WAIT_DONE(locktag->locktag_field1,
locktag->locktag_field2,
--- 863,869 ----
locktag->locktag_type,
lockmode);
! status = WaitOnLock(locallock, owner);
TRACE_POSTGRESQL_LOCK_WAIT_DONE(locktag->locktag_field1,
locktag->locktag_field2,
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 878,905 ****
* done when the lock was granted to us --- see notes in WaitOnLock.
*/
! /*
! * Check the proclock entry status, in case something in the ipc
! * communication doesn't work correctly.
! */
! if (!(proclock->holdMask & LOCKBIT_ON(lockmode)))
{
! AbortStrongLockAcquire();
! PROCLOCK_PRINT("LockAcquire: INCONSISTENT", proclock);
! LOCK_PRINT("LockAcquire: INCONSISTENT", lock, lockmode);
! /* Should we retry ? */
! LWLockRelease(partitionLock);
! elog(ERROR, "LockAcquire failed");
}
- PROCLOCK_PRINT("LockAcquire: granted", proclock);
- LOCK_PRINT("LockAcquire: granted", lock, lockmode);
}
! /*
! * Lock state is fully up-to-date now; if we error out after this, no
! * special error cleanup is required.
! */
! FinishStrongLockAcquire();
LWLockRelease(partitionLock);
--- 878,928 ----
* done when the lock was granted to us --- see notes in WaitOnLock.
*/
! switch (status)
{
! case STATUS_OK:
! /*
! * Check the proclock entry status, in case something in the ipc
! * communication doesn't work correctly.
! */
! if (!(proclock->holdMask & LOCKBIT_ON(lockmode)))
! {
! AbortStrongLockAcquire();
! PROCLOCK_PRINT("LockAcquire: INCONSISTENT", proclock);
! LOCK_PRINT("LockAcquire: INCONSISTENT", lock, lockmode);
! /* Should we retry ? */
! LWLockRelease(partitionLock);
! elog(ERROR, "LockAcquire failed");
! }
! PROCLOCK_PRINT("LockAcquire: granted", proclock);
! LOCK_PRINT("LockAcquire: granted", lock, lockmode);
! break;
! case STATUS_WAITING:
! PROCLOCK_PRINT("LockAcquire: timed out", proclock);
! LOCK_PRINT("LockAcquire: timed out", lock, lockmode);
! break;
! default:
! elog(ERROR, "LockAcquire invalid status");
! break;
}
}
! if (status == STATUS_WAITING)
! {
! /*
! * lock_timeout was set and WaitOnLock() indicated
! * we timed out. Clean up manually.
! */
! AbortStrongLockAcquire();
! }
! else
! {
! /*
! * Lock state is fully up-to-date now; if we error out after this, no
! * special error cleanup is required.
! */
! FinishStrongLockAcquire();
! }
LWLockRelease(partitionLock);
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 918,924 ****
locktag->locktag_field2);
}
! return LOCKACQUIRE_OK;
}
/*
--- 941,947 ----
locktag->locktag_field2);
}
! return (status == STATUS_OK ? LOCKACQUIRE_OK : LOCKACQUIRE_NOT_AVAIL);
}
/*
*************** GrantAwaitedLock(void)
*** 1437,1450 ****
* Caller must have set MyProc->heldLocks to reflect locks already held
* on the lockable object by this process.
*
* The appropriate partition lock must be held at entry.
*/
! static void
WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner)
{
LOCKMETHODID lockmethodid = LOCALLOCK_LOCKMETHOD(*locallock);
LockMethod lockMethodTable = LockMethods[lockmethodid];
char *volatile new_status = NULL;
LOCK_PRINT("WaitOnLock: sleeping on lock",
locallock->lock, locallock->tag.mode);
--- 1460,1479 ----
* Caller must have set MyProc->heldLocks to reflect locks already held
* on the lockable object by this process.
*
+ * Result: returns value of ProcSleep()
+ * STATUS_OK if we acquired the lock
+ * STATUS_ERROR if not (deadlock)
+ * STATUS_WAITING if not (timeout)
+ *
* The appropriate partition lock must be held at entry.
*/
! static int
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);
*************** WaitOnLock(LOCALLOCK *locallock, Resourc
*** 1486,1493 ****
*/
PG_TRY();
{
! if (ProcSleep(locallock, lockMethodTable) != STATUS_OK)
{
/*
* We failed as a result of a deadlock, see CheckDeadLock(). Quit
* now.
--- 1515,1527 ----
*/
PG_TRY();
{
! wait_status = ProcSleep(locallock, lockMethodTable);
! switch (wait_status)
{
+ case STATUS_OK:
+ case STATUS_WAITING:
+ break;
+ default:
/*
* We failed as a result of a deadlock, see CheckDeadLock(). Quit
* now.
*************** WaitOnLock(LOCALLOCK *locallock, Resourc
*** 1532,1539 ****
pfree(new_status);
}
! LOCK_PRINT("WaitOnLock: wakeup on lock",
locallock->lock, locallock->tag.mode);
}
/*
--- 1566,1579 ----
pfree(new_status);
}
! 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);
+
+ return wait_status;
}
/*
*************** VirtualXactLock(VirtualTransactionId vxi
*** 3853,3859 ****
LWLockRelease(proc->backendLock);
/* Time to wait. */
! (void) LockAcquire(&tag, ShareLock, false, false);
LockRelease(&tag, ShareLock, false);
return true;
--- 3893,3903 ----
LWLockRelease(proc->backendLock);
/* Time to wait. */
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on virtual transaction with ID %u",
! vxid.localTransactionId)));
LockRelease(&tag, ShareLock, false);
return true;
diff -dcrpN 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-06-19 17:59:28.649869905 +0200
--- postgresql.2/src/backend/storage/lmgr/proc.c 2012-06-19 18:17:11.665875828 +0200
*************** LockErrorCleanup(void)
*** 638,645 ****
if (lockAwaited == NULL)
return;
! /* Turn off the deadlock timer, if it's still running (see ProcSleep) */
disable_timeout(DEADLOCK_TIMEOUT, false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
--- 638,649 ----
if (lockAwaited == NULL)
return;
! /*
! * Turn off the deadlock and lock timeout timers,
! * if they are still running (see ProcSleep)
! */
disable_timeout(DEADLOCK_TIMEOUT, false);
+ disable_timeout(LOCK_TIMEOUT, false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
*************** ProcQueueInit(PROC_QUEUE *queue)
*** 875,881 ****
* 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).
*
* ASSUME: that no one will fiddle with the queue until after
* we release the partition lock.
--- 879,888 ----
* 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)
! * STATUS_WAITING if not (timeout)
*
* ASSUME: that no one will fiddle with the queue until after
* we release the partition lock.
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 897,902 ****
--- 904,910 ----
LOCKMASK myHeldLocks = MyProc->heldLocks;
bool early_deadlock = false;
bool allow_autovacuum_cancel = true;
+ bool timeout_detected = false;
int myWaitStatus;
PGPROC *proc;
int i;
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1038,1045 ****
elog(FATAL, "could not set timer for process wakeup");
/*
! * 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
* saved wakeup might be leftover from a previous operation (for example,
* we aborted ProcWaitForSignal just before someone did ProcSendSignal).
--- 1046,1059 ----
elog(FATAL, "could not set timer for process wakeup");
/*
! * Queue the timer for lock timeout, too.
! */
! if (!enable_timeout(LOCK_TIMEOUT, LockTimeout))
! elog(FATAL, "could not set timer for process wakeup");
!
! /*
! * 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).
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1056,1062 ****
*/
do
{
! PGSemaphoreLock(&MyProc->sem, true);
/*
* waitStatus could change from STATUS_WAITING to something else
--- 1070,1081 ----
*/
do
{
! PGSemaphoreTimedLock(&MyProc->sem, true);
!
! /* Check and keep the lock timeout indicator for later checks */
! timeout_detected = get_timeout_indicator(LOCK_TIMEOUT);
! if (timeout_detected)
! break;
/*
* waitStatus could change from STATUS_WAITING to something else
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1185,1196 ****
} while (myWaitStatus == STATUS_WAITING);
/*
! * Disable the timer, if it's still running
*/
if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for process wakeup");
/*
* Re-acquire the lock table's partition lock. We have to do this to hold
* off cancel/die interrupts before we can mess with lockAwaited (else we
* might have a missed or duplicated locallock update).
--- 1204,1221 ----
} while (myWaitStatus == STATUS_WAITING);
/*
! * Disable the deadlock timer, if it's still running
*/
if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for process wakeup");
/*
+ * Disable the lock timeout timer, if it's still running
+ */
+ if (!disable_timeout(LOCK_TIMEOUT, false))
+ elog(FATAL, "could not disable timer for process wakeup");
+
+ /*
* Re-acquire the lock table's partition lock. We have to do this to hold
* off cancel/die interrupts before we can mess with lockAwaited (else we
* might have a missed or duplicated locallock update).
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1198,1203 ****
--- 1223,1237 ----
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;
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1211,1218 ****
/*
* We don't have to do anything else, because the awaker did all the
* necessary update of the lock table and MyProc.
*/
! return MyProc->waitStatus;
}
--- 1245,1254 ----
/*
* 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 (timeout_detected ? STATUS_WAITING : MyProc->waitStatus);
}
diff -dcrpN postgresql.1/src/backend/storage/lmgr/timeout.c postgresql.2/src/backend/storage/lmgr/timeout.c
*** postgresql.1/src/backend/storage/lmgr/timeout.c 2012-06-19 18:00:18.922182361 +0200
--- postgresql.2/src/backend/storage/lmgr/timeout.c 2012-06-19 18:23:33.821236181 +0200
***************
*** 22,32 ****
--- 22,36 ----
#include "storage/proctimeout.h"
#include "storage/standby.h"
#include "storage/timeout.h"
+ #include "utils/guc.h"
#include "utils/timestamp.h"
/* GUC variables */
int DeadlockTimeout = 1000;
int StatementTimeout = 0;
+ int LockTimeout = 0;
+
+ int LockTimeoutOption = LOCK_TIMEOUT_PER_LOCK;
/*
* Infrastructure for timeouts
*************** int StatementTimeout = 0;
*** 35,40 ****
--- 39,47 ----
static void InitTimeout(TimeoutName tn, TimestampTz start_time, TimestampTz fin_time);
static void DestroyTimeout(TimeoutName tn, bool keep_indicator);
+ static void CheckDummyTimeout(void);
+ static TimestampTz GetLockTimeoutStart(void);
+
typedef void (*timeout_check)(void);
typedef TimestampTz (*timeout_start)(void);
*************** static timeout_params base_timeouts[TIME
*** 66,71 ****
--- 73,84 ----
},
{
+ LOCK_TIMEOUT, false, false,
+ CheckDummyTimeout, GetLockTimeoutStart,
+ 0
+ },
+
+ {
STATEMENT_TIMEOUT, false, false,
CheckStatementTimeout, GetCurrentStatementStartTimestamp,
0
*************** DestroyTimeout(TimeoutName tn, bool keep
*** 209,214 ****
--- 222,264 ----
base_timeouts[tn].fin_time = 0;
}
+ /*
+ * CheckDummyTimeout
+ *
+ * Do nothing. Usable for timeout sources that don't need
+ * extra processing, like lock_timeout. The timeout indicator
+ * is set by the signal handler.
+ */
+ static void
+ CheckDummyTimeout(void)
+ {
+ return;
+ }
+
+ /*
+ * GetLockTimeoutStart
+ *
+ * Depending on lock_timeout_option, return either the current time
+ * ('per_lock') or the statement start time ('per_statement').
+ */
+ static TimestampTz
+ GetLockTimeoutStart(void)
+ {
+ switch (LockTimeoutOption)
+ {
+ case LOCK_TIMEOUT_PER_LOCK:
+ return GetCurrentTimestamp();
+ case LOCK_TIMEOUT_PER_STMT:
+ return GetCurrentStatementStartTimestamp();
+ default:
+ elog(ERROR, "unhandled lock_timeout_option value: \"%s\"",
+ GetConfigOptionByName("lock_timeout_option", NULL));
+ break;
+ }
+ /* Silence possible warnings. */
+ return 0;
+ }
+
/*****************************************************************************
* Public API
*****************************************************************************/
diff -dcrpN postgresql.1/src/backend/utils/adt/lockfuncs.c postgresql.2/src/backend/utils/adt/lockfuncs.c
*** postgresql.1/src/backend/utils/adt/lockfuncs.c 2012-06-11 06:22:48.162921604 +0200
--- postgresql.2/src/backend/utils/adt/lockfuncs.c 2012-06-19 18:17:11.667875843 +0200
*************** pg_advisory_lock_int8(PG_FUNCTION_ARGS)
*** 421,427 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ExclusiveLock, true, false);
PG_RETURN_VOID();
}
--- 421,431 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ExclusiveLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_int8(PG_FUNCTION_A
*** 438,444 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
PG_RETURN_VOID();
}
--- 442,452 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_shared_int8(PG_FUNCTION
*** 454,460 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ShareLock, true, false);
PG_RETURN_VOID();
}
--- 462,472 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ShareLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_shared_int8(PG_FUN
*** 471,477 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ShareLock, false, false);
PG_RETURN_VOID();
}
--- 483,493 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_int4(PG_FUNCTION_ARGS)
*** 604,610 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ExclusiveLock, true, false);
PG_RETURN_VOID();
}
--- 620,630 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ExclusiveLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_int4(PG_FUNCTION_A
*** 622,628 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
PG_RETURN_VOID();
}
--- 642,652 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_shared_int4(PG_FUNCTION
*** 639,645 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ShareLock, true, false);
PG_RETURN_VOID();
}
--- 663,673 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ShareLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_shared_int4(PG_FUN
*** 657,663 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ShareLock, false, false);
PG_RETURN_VOID();
}
--- 685,695 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
diff -dcrpN 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-06-19 17:49:55.364479146 +0200
--- postgresql.2/src/backend/utils/misc/guc.c 2012-06-19 18:17:11.673875883 +0200
*************** static const struct config_enum_entry sy
*** 389,394 ****
--- 389,405 ----
};
/*
+ * 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[];
*************** static struct config_int ConfigureNamesI
*** 1861,1866 ****
--- 1872,1888 ----
},
{
+ {"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
*************** static struct config_enum ConfigureNames
*** 3141,3146 ****
--- 3163,3178 ----
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 -dcrpN 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-06-19 18:17:11.673875883 +0200
***************
*** 528,533 ****
--- 528,538 ----
#------------------------------------------------------------------------------
#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 -dcrpN postgresql.1/src/include/storage/pg_sema.h postgresql.2/src/include/storage/pg_sema.h
*** postgresql.1/src/include/storage/pg_sema.h 2012-04-16 19:57:22.672918205 +0200
--- postgresql.2/src/include/storage/pg_sema.h 2012-06-19 18:17:11.674875890 +0200
*************** extern void PGSemaphoreUnlock(PGSemaphor
*** 80,83 ****
--- 80,89 ----
/* 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 void PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK);
+
#endif /* PG_SEMA_H */
diff -dcrpN postgresql.1/src/include/storage/timeout.h postgresql.2/src/include/storage/timeout.h
*** postgresql.1/src/include/storage/timeout.h 2012-06-19 17:49:55.365479152 +0200
--- postgresql.2/src/include/storage/timeout.h 2012-06-19 18:17:11.674875890 +0200
***************
*** 19,33 ****
--- 19,42 ----
/* configurable options */
extern int DeadlockTimeout;
extern int StatementTimeout;
+ extern int LockTimeout;
typedef enum TimeoutName {
DEADLOCK_TIMEOUT,
+ LOCK_TIMEOUT,
STATEMENT_TIMEOUT,
STANDBY_DEADLOCK_TIMEOUT,
STANDBY_TIMEOUT,
TIMEOUT_MAX
} TimeoutName;
+ typedef enum LockTimeoutOptions {
+ LOCK_TIMEOUT_PER_LOCK,
+ LOCK_TIMEOUT_PER_STMT
+ } LockTimeoutOptions;
+
+ extern int LockTimeoutOption;
+
extern bool enable_timeout(TimeoutName tn, int delayms);
extern bool disable_timeout(TimeoutName tn, bool keep_indicator);
extern bool disable_all_timeouts(bool keep_indicators);
diff -dcrpN 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-06-19 18:17:11.674875891 +0200
*************** set statement_timeout to 2000;
*** 198,203 ****
--- 198,223 ----
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: could not obtain lock on relation "pxtest3"
+ LINE 1: 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;
+ ERROR: could not obtain lock on relation "pxtest3"
+ LINE 1: SELECT * FROM pxtest3;
+ ^
+ 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
*************** set statement_timeout to 2000;
*** 213,218 ****
--- 233,245 ----
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: could not obtain lock on relation "pxtest3"
+ LINE 1: SELECT * FROM pxtest3;
+ ^
+ reset lock_timeout;
-- Commit table creation
COMMIT PREPARED 'regress-one';
\d pxtest2
diff -dcrpN 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-06-19 18:17:11.675875897 +0200
*************** set statement_timeout to 2000;
*** 126,131 ****
--- 126,147 ----
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 -
*************** set statement_timeout to 2000;
*** 137,142 ****
--- 153,163 ----
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
2012-06-19 18:44 keltezéssel, Boszormenyi Zoltan írta:
2012-06-19 16:54 keltezéssel, Alvaro Herrera írta:
Excerpts from Boszormenyi Zoltan's message of mar jun 19 04:44:35 -0400 2012:
2012-06-18 19:46 keltezéssel, Alvaro Herrera írta:
Excerpts from Boszormenyi Zoltan's message of vie may 11 03:54:13 -0400 2012:
Hi,
another rebasing and applied the GIT changes in
ada8fa08fc6cf5f199b6df935b4d0a730aaa4fec to the
Windows implementation of PGSemaphoreTimedLock.Hi,
I gave the framework patch a look. One thing I'm not sure about is the
way you've defined the API. It seems a bit strange to have a nice and
clean, generic interface in timeout.h; and then have the internal
implementation of the API cluttered with details such as what to do when
the deadlock timeout expires. Wouldn't it be much better if we could
keep the details of CheckDeadLock itself within proc.c, for example?Do you mean adding a callback function argument to for enable_timeout()
would be better?Well, that's not exactly what I was thinking, but maybe your idea is
better than what I had in mind.Same for the other specific Check*Timeout functions. It seems to me
that the only timeout.c specific requirement is to be able to poke
base_timeouts[].indicator and fin_time. Maybe that can get done in
timeout.c and then have the generic checker call the module-specific
checker.Or instead of static functions, Check* functions can be external
to timeout.c. It seemed to be a good idea to move all the timeout
related functions to timeout.c.Yeah, except that they play with members of the timeout_params struct,
which hopefully does not need to be exported. So if you can just move
(that is, put back in their original places) the portions that do not
touch that strcut, it'd be great.OK, all 4 Check* functions are now moved back into proc.c,
nothing outside of timeout.c touches anything in it. New patches
are attached.
OK, one last post for today. The #included headers are cleaned up
in timeout.c, miscadmin.h and storage/standby.h are not needed.
--
----------------------------------
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/
Attachments:
1-timeout-framework-v7.1.patchtext/x-patch; name=1-timeout-framework-v7.1.patchDownload
diff -dcrpN postgresql/src/backend/postmaster/autovacuum.c postgresql.1/src/backend/postmaster/autovacuum.c
*** postgresql/src/backend/postmaster/autovacuum.c 2012-06-11 06:22:48.137921483 +0200
--- postgresql.1/src/backend/postmaster/autovacuum.c 2012-06-19 17:49:55.348479057 +0200
***************
*** 86,94 ****
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
- #include "storage/proc.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "tcop/tcopprot.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
--- 86,94 ----
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
+ #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
*************** AutoVacLauncherMain(int argc, char *argv
*** 484,490 ****
/* Forget any pending QueryCancel request */
QueryCancelPending = false;
! disable_sig_alarm(true);
QueryCancelPending = false; /* again in case timeout occurred */
/* Report the error to the server log */
--- 484,490 ----
/* Forget any pending QueryCancel request */
QueryCancelPending = false;
! disable_all_timeouts(false);
QueryCancelPending = false; /* again in case timeout occurred */
/* Report the error to the server log */
diff -dcrpN postgresql/src/backend/postmaster/postmaster.c postgresql.1/src/backend/postmaster/postmaster.c
*** postgresql/src/backend/postmaster/postmaster.c 2012-06-11 06:22:48.148921535 +0200
--- postgresql.1/src/backend/postmaster/postmaster.c 2012-06-19 17:49:55.351479077 +0200
***************
*** 112,118 ****
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
! #include "storage/proc.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/datetime.h"
--- 112,118 ----
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
! #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/datetime.h"
*************** BackendInitialize(Port *port)
*** 3486,3494 ****
* Ready to begin client interaction. We will give up and exit(1) after a
* time delay, so that a broken client can't hog a connection
* indefinitely. PreAuthDelay and any DNS interactions above don't count
! * against the time limit.
*/
! if (!enable_sig_alarm(AuthenticationTimeout * 1000, false))
elog(FATAL, "could not set timer for startup packet timeout");
/*
--- 3486,3494 ----
* Ready to begin client interaction. We will give up and exit(1) after a
* time delay, so that a broken client can't hog a connection
* indefinitely. PreAuthDelay and any DNS interactions above don't count
! * against the time limit. Use the deadlock timeout interface.
*/
! if (!enable_timeout(DEADLOCK_TIMEOUT, AuthenticationTimeout * 1000))
elog(FATAL, "could not set timer for startup packet timeout");
/*
*************** BackendInitialize(Port *port)
*** 3526,3532 ****
/*
* Disable the timeout, and prevent SIGTERM/SIGQUIT again.
*/
! if (!disable_sig_alarm(false))
elog(FATAL, "could not disable timer for startup packet timeout");
PG_SETMASK(&BlockSig);
}
--- 3526,3532 ----
/*
* Disable the timeout, and prevent SIGTERM/SIGQUIT again.
*/
! if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for startup packet timeout");
PG_SETMASK(&BlockSig);
}
diff -dcrpN postgresql/src/backend/postmaster/startup.c postgresql.1/src/backend/postmaster/startup.c
*** postgresql/src/backend/postmaster/startup.c 2012-04-16 19:57:22.442915536 +0200
--- postgresql.1/src/backend/postmaster/startup.c 2012-06-19 17:49:55.351479077 +0200
***************
*** 27,33 ****
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
! #include "storage/proc.h"
#include "utils/guc.h"
--- 27,33 ----
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
! #include "storage/timeout.h"
#include "utils/guc.h"
*************** StartupProcessMain(void)
*** 195,202 ****
pqsignal(SIGTERM, StartupProcShutdownHandler); /* request shutdown */
pqsignal(SIGQUIT, startupproc_quickdie); /* hard crash time */
if (EnableHotStandby)
! pqsignal(SIGALRM, handle_standby_sig_alarm); /* ignored unless
! * InHotStandby */
else
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
--- 195,202 ----
pqsignal(SIGTERM, StartupProcShutdownHandler); /* request shutdown */
pqsignal(SIGQUIT, startupproc_quickdie); /* hard crash time */
if (EnableHotStandby)
! pqsignal(SIGALRM, handle_sig_alarm); /* ignored unless
! * InHotStandby */
else
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
diff -dcrpN postgresql/src/backend/storage/ipc/standby.c postgresql.1/src/backend/storage/ipc/standby.c
*** postgresql/src/backend/storage/ipc/standby.c 2012-06-11 06:22:48.154921564 +0200
--- postgresql.1/src/backend/storage/ipc/standby.c 2012-06-19 17:49:55.352479081 +0200
***************
*** 23,32 ****
#include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
- #include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/sinvaladt.h"
#include "storage/standby.h"
#include "utils/ps_status.h"
#include "utils/timestamp.h"
--- 23,32 ----
#include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
#include "storage/procarray.h"
#include "storage/sinvaladt.h"
#include "storage/standby.h"
+ #include "storage/timeout.h"
#include "utils/ps_status.h"
#include "utils/timestamp.h"
*************** ResolveRecoveryConflictWithLock(Oid dbOi
*** 394,400 ****
void
ResolveRecoveryConflictWithBufferPin(void)
{
- bool sig_alarm_enabled = false;
TimestampTz ltime;
TimestampTz now;
--- 394,399 ----
*************** ResolveRecoveryConflictWithBufferPin(voi
*** 409,417 ****
* We're willing to wait forever for conflicts, so set timeout for
* deadlock check (only)
*/
! if (enable_standby_sig_alarm(now, now, true))
! sig_alarm_enabled = true;
! else
elog(FATAL, "could not set timer for process wakeup");
}
else if (now >= ltime)
--- 408,414 ----
* We're willing to wait forever for conflicts, so set timeout for
* deadlock check (only)
*/
! if (!enable_timeout(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout))
elog(FATAL, "could not set timer for process wakeup");
}
else if (now >= ltime)
*************** ResolveRecoveryConflictWithBufferPin(voi
*** 423,446 ****
}
else
{
/*
* Wake up at ltime, and check for deadlocks as well if we will be
* waiting longer than deadlock_timeout
*/
! if (enable_standby_sig_alarm(now, ltime, false))
! sig_alarm_enabled = true;
! else
elog(FATAL, "could not set timer for process wakeup");
}
/* Wait to be signaled by UnpinBuffer() */
ProcWaitForSignal();
! if (sig_alarm_enabled)
! {
! if (!disable_standby_sig_alarm())
! elog(FATAL, "could not disable timer for process wakeup");
! }
}
void
--- 420,447 ----
}
else
{
+ long secs, msecs;
+ int usecs;
+
/*
* Wake up at ltime, and check for deadlocks as well if we will be
* waiting longer than deadlock_timeout
*/
! if (!enable_timeout(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout))
! elog(FATAL, "could not set timer for process wakeup");
!
! TimestampDifference(now, ltime, &secs, &usecs);
! msecs = secs * 1000 + usecs / 1000;
!
! if (!enable_timeout(STANDBY_TIMEOUT, msecs))
elog(FATAL, "could not set timer for process wakeup");
}
/* Wait to be signaled by UnpinBuffer() */
ProcWaitForSignal();
! if (!disable_all_timeouts(false))
! elog(FATAL, "could not disable timer for process wakeup");
}
void
diff -dcrpN postgresql/src/backend/storage/lmgr/Makefile postgresql.1/src/backend/storage/lmgr/Makefile
*** postgresql/src/backend/storage/lmgr/Makefile 2012-04-16 19:57:22.458915722 +0200
--- postgresql.1/src/backend/storage/lmgr/Makefile 2012-06-19 17:49:55.353479085 +0200
*************** subdir = src/backend/storage/lmgr
*** 12,18 ****
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
! OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o predicate.o
include $(top_srcdir)/src/backend/common.mk
--- 12,18 ----
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
! OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o predicate.o timeout.o
include $(top_srcdir)/src/backend/common.mk
diff -dcrpN postgresql/src/backend/storage/lmgr/proc.c postgresql.1/src/backend/storage/lmgr/proc.c
*** postgresql/src/backend/storage/lmgr/proc.c 2012-06-11 06:22:48.156921573 +0200
--- postgresql.1/src/backend/storage/lmgr/proc.c 2012-06-19 17:59:28.649869905 +0200
***************
*** 47,59 ****
#include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/spin.h"
#include "utils/timestamp.h"
/* GUC variables */
- int DeadlockTimeout = 1000;
- int StatementTimeout = 0;
bool log_lock_waits = false;
/* Pointer to this process's PGPROC and PGXACT structs, if any */
--- 47,59 ----
#include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
+ #include "storage/proctimeout.h"
#include "storage/spin.h"
+ #include "storage/timeout.h"
#include "utils/timestamp.h"
/* GUC variables */
bool log_lock_waits = false;
/* Pointer to this process's PGPROC and PGXACT structs, if any */
*************** PGPROC *PreparedXactProcs = NULL;
*** 77,103 ****
/* If we are waiting for a lock, this points to the associated LOCALLOCK */
static LOCALLOCK *lockAwaited = NULL;
! /* Mark these volatile because they can be changed by signal handler */
! static volatile bool standby_timeout_active = false;
! static volatile bool statement_timeout_active = false;
! static volatile bool deadlock_timeout_active = false;
! static volatile DeadLockState deadlock_state = DS_NOT_YET_CHECKED;
! volatile bool cancel_from_timeout = false;
!
! /* timeout_start_time is set when log_lock_waits is true */
! static TimestampTz timeout_start_time;
!
! /* statement_fin_time is valid only if statement_timeout_active is true */
! static TimestampTz statement_fin_time;
! static TimestampTz statement_fin_time2; /* valid only in recovery */
!
static void RemoveProcFromArray(int code, Datum arg);
static void ProcKill(int code, Datum arg);
static void AuxiliaryProcKill(int code, Datum arg);
- static bool CheckStatementTimeout(void);
- static bool CheckStandbyTimeout(void);
-
/*
* Report shared-memory space needed by InitProcGlobal.
--- 77,88 ----
/* If we are waiting for a lock, this points to the associated LOCALLOCK */
static LOCALLOCK *lockAwaited = NULL;
! /* Mark this volatile because it can be changed by signal handler */
! static volatile DeadLockState deadlock_state;
static void RemoveProcFromArray(int code, Datum arg);
static void ProcKill(int code, Datum arg);
static void AuxiliaryProcKill(int code, Datum arg);
/*
* Report shared-memory space needed by InitProcGlobal.
*************** LockErrorCleanup(void)
*** 654,660 ****
return;
/* Turn off the deadlock timer, if it's still running (see ProcSleep) */
! disable_sig_alarm(false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
--- 639,645 ----
return;
/* Turn off the deadlock timer, if it's still running (see ProcSleep) */
! disable_timeout(DEADLOCK_TIMEOUT, false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1049,1055 ****
* 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 (!enable_sig_alarm(DeadlockTimeout, false))
elog(FATAL, "could not set timer for process wakeup");
/*
--- 1034,1040 ----
* 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 (!enable_timeout(DEADLOCK_TIMEOUT, DeadlockTimeout))
elog(FATAL, "could not set timer for process wakeup");
/*
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1139,1145 ****
DescribeLockTag(&buf, &locallock->tag.lock);
modename = GetLockmodeName(locallock->tag.lock.locktag_lockmethodid,
lockmode);
! TimestampDifference(timeout_start_time, GetCurrentTimestamp(),
&secs, &usecs);
msecs = secs * 1000 + usecs / 1000;
usecs = usecs % 1000;
--- 1124,1131 ----
DescribeLockTag(&buf, &locallock->tag.lock);
modename = GetLockmodeName(locallock->tag.lock.locktag_lockmethodid,
lockmode);
! TimestampDifference(get_timeout_start(DEADLOCK_TIMEOUT),
! GetCurrentTimestamp(),
&secs, &usecs);
msecs = secs * 1000 + usecs / 1000;
usecs = usecs % 1000;
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1201,1207 ****
/*
* Disable the timer, if it's still running
*/
! if (!disable_sig_alarm(false))
elog(FATAL, "could not disable timer for process wakeup");
/*
--- 1187,1193 ----
/*
* Disable the timer, if it's still running
*/
! if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for process wakeup");
/*
*************** ProcLockWakeup(LockMethod lockMethodTabl
*** 1345,1351 ****
* NB: this is run inside a signal handler, so be very wary about what is done
* here or in called routines.
*/
! static void
CheckDeadLock(void)
{
int i;
--- 1331,1337 ----
* NB: this is run inside a signal handler, so be very wary about what is done
* here or in called routines.
*/
! void
CheckDeadLock(void)
{
int i;
*************** ProcSendSignal(int pid)
*** 1500,1817 ****
PGSemaphoreUnlock(&proc->sem);
}
-
- /*****************************************************************************
- * SIGALRM interrupt support
- *
- * Maybe these should be in pqsignal.c?
- *****************************************************************************/
-
- /*
- * Enable the SIGALRM interrupt to fire after the specified delay
- *
- * Delay is given in milliseconds. Caller should be sure a SIGALRM
- * signal handler is installed before this is called.
- *
- * This code properly handles nesting of deadlock timeout alarms within
- * statement timeout alarms.
- *
- * Returns TRUE if okay, FALSE on failure.
- */
- bool
- enable_sig_alarm(int delayms, bool is_statement_timeout)
- {
- TimestampTz fin_time;
- struct itimerval timeval;
-
- if (is_statement_timeout)
- {
- /*
- * Begin statement-level timeout
- *
- * Note that we compute statement_fin_time with reference to the
- * statement_timestamp, but apply the specified delay without any
- * correction; that is, we ignore whatever time has elapsed since
- * statement_timestamp was set. In the normal case only a small
- * interval will have elapsed and so this doesn't matter, but there
- * are corner cases (involving multi-statement query strings with
- * embedded COMMIT or ROLLBACK) where we might re-initialize the
- * statement timeout long after initial receipt of the message. In
- * such cases the enforcement of the statement timeout will be a bit
- * inconsistent. This annoyance is judged not worth the cost of
- * performing an additional gettimeofday() here.
- */
- Assert(!deadlock_timeout_active);
- fin_time = GetCurrentStatementStartTimestamp();
- fin_time = TimestampTzPlusMilliseconds(fin_time, delayms);
- statement_fin_time = fin_time;
- cancel_from_timeout = false;
- statement_timeout_active = true;
- }
- else if (statement_timeout_active)
- {
- /*
- * Begin deadlock timeout with statement-level timeout active
- *
- * Here, we want to interrupt at the closer of the two timeout times.
- * If fin_time >= statement_fin_time then we need not touch the
- * existing timer setting; else set up to interrupt at the deadlock
- * timeout time.
- *
- * NOTE: in this case it is possible that this routine will be
- * interrupted by the previously-set timer alarm. This is okay
- * because the signal handler will do only what it should do according
- * to the state variables. The deadlock checker may get run earlier
- * than normal, but that does no harm.
- */
- timeout_start_time = GetCurrentTimestamp();
- fin_time = TimestampTzPlusMilliseconds(timeout_start_time, delayms);
- deadlock_timeout_active = true;
- if (fin_time >= statement_fin_time)
- return true;
- }
- else
- {
- /* Begin deadlock timeout with no statement-level timeout */
- deadlock_timeout_active = true;
- /* GetCurrentTimestamp can be expensive, so only do it if we must */
- if (log_lock_waits)
- timeout_start_time = GetCurrentTimestamp();
- }
-
- /* If we reach here, okay to set the timer interrupt */
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = delayms / 1000;
- timeval.it_value.tv_usec = (delayms % 1000) * 1000;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- return true;
- }
-
- /*
- * Cancel the SIGALRM timer, either for a deadlock timeout or a statement
- * timeout. If a deadlock timeout is canceled, any active statement timeout
- * remains in force.
- *
- * Returns TRUE if okay, FALSE on failure.
- */
- bool
- disable_sig_alarm(bool is_statement_timeout)
- {
- /*
- * Always disable the interrupt if it is active; this avoids being
- * interrupted by the signal handler and thereby possibly getting
- * confused.
- *
- * We will re-enable the interrupt if necessary in CheckStatementTimeout.
- */
- if (statement_timeout_active || deadlock_timeout_active)
- {
- struct itimerval timeval;
-
- MemSet(&timeval, 0, sizeof(struct itimerval));
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- {
- statement_timeout_active = false;
- cancel_from_timeout = false;
- deadlock_timeout_active = false;
- return false;
- }
- }
-
- /* Always cancel deadlock timeout, in case this is error cleanup */
- deadlock_timeout_active = false;
-
- /* Cancel or reschedule statement timeout */
- if (is_statement_timeout)
- {
- statement_timeout_active = false;
- cancel_from_timeout = false;
- }
- else if (statement_timeout_active)
- {
- if (!CheckStatementTimeout())
- return false;
- }
- return true;
- }
-
-
/*
* Check for statement timeout. If the timeout time has come,
! * trigger a query-cancel interrupt; if not, reschedule the SIGALRM
! * interrupt to occur at the right time.
! *
! * Returns true if okay, false if failed to set the interrupt.
*/
! static bool
CheckStatementTimeout(void)
{
- TimestampTz now;
-
- if (!statement_timeout_active)
- return true; /* do nothing if not active */
-
- now = GetCurrentTimestamp();
-
- if (now >= statement_fin_time)
- {
- /* Time to die */
- statement_timeout_active = false;
- cancel_from_timeout = true;
#ifdef HAVE_SETSID
! /* try to signal whole process group */
! kill(-MyProcPid, SIGINT);
#endif
! kill(MyProcPid, SIGINT);
! }
! else
! {
! /* Not time yet, so (re)schedule the interrupt */
! long secs;
! int usecs;
! struct itimerval timeval;
!
! TimestampDifference(now, statement_fin_time,
! &secs, &usecs);
!
! /*
! * It's possible that the difference is less than a microsecond;
! * ensure we don't cancel, rather than set, the interrupt.
! */
! if (secs == 0 && usecs == 0)
! usecs = 1;
! MemSet(&timeval, 0, sizeof(struct itimerval));
! timeval.it_value.tv_sec = secs;
! timeval.it_value.tv_usec = usecs;
! if (setitimer(ITIMER_REAL, &timeval, NULL))
! return false;
! }
!
! return true;
}
-
/*
! * Signal handler for SIGALRM for normal user backends
! *
! * Process deadlock check and/or statement timeout check, as needed.
! * To avoid various edge cases, we must be careful to do nothing
! * when there is nothing to be done. We also need to be able to
! * reschedule the timer interrupt if called before end of statement.
*/
void
! handle_sig_alarm(SIGNAL_ARGS)
! {
! int save_errno = errno;
!
! /* SIGALRM is cause for waking anything waiting on the process latch */
! if (MyProc)
! SetLatch(&MyProc->procLatch);
!
! if (deadlock_timeout_active)
! {
! deadlock_timeout_active = false;
! CheckDeadLock();
! }
!
! if (statement_timeout_active)
! (void) CheckStatementTimeout();
!
! errno = save_errno;
! }
!
! /*
! * Signal handler for SIGALRM in Startup process
! *
! * To avoid various edge cases, we must be careful to do nothing
! * when there is nothing to be done. We also need to be able to
! * reschedule the timer interrupt if called before end of statement.
! *
! * We set either deadlock_timeout_active or statement_timeout_active
! * or both. Interrupts are enabled if standby_timeout_active.
! */
! bool
! enable_standby_sig_alarm(TimestampTz now, TimestampTz fin_time, bool deadlock_only)
! {
! TimestampTz deadlock_time = TimestampTzPlusMilliseconds(now,
! DeadlockTimeout);
!
! if (deadlock_only)
! {
! /*
! * Wake up at deadlock_time only, then wait forever
! */
! statement_fin_time = deadlock_time;
! deadlock_timeout_active = true;
! statement_timeout_active = false;
! }
! else if (fin_time > deadlock_time)
! {
! /*
! * Wake up at deadlock_time, then again at fin_time
! */
! statement_fin_time = deadlock_time;
! statement_fin_time2 = fin_time;
! deadlock_timeout_active = true;
! statement_timeout_active = true;
! }
! else
! {
! /*
! * Wake only at fin_time because its fairly soon
! */
! statement_fin_time = fin_time;
! deadlock_timeout_active = false;
! statement_timeout_active = true;
! }
!
! if (deadlock_timeout_active || statement_timeout_active)
! {
! long secs;
! int usecs;
! struct itimerval timeval;
!
! TimestampDifference(now, statement_fin_time,
! &secs, &usecs);
! if (secs == 0 && usecs == 0)
! usecs = 1;
! MemSet(&timeval, 0, sizeof(struct itimerval));
! timeval.it_value.tv_sec = secs;
! timeval.it_value.tv_usec = usecs;
! if (setitimer(ITIMER_REAL, &timeval, NULL))
! return false;
! standby_timeout_active = true;
! }
!
! return true;
! }
!
! bool
! disable_standby_sig_alarm(void)
{
! /*
! * Always disable the interrupt if it is active; this avoids being
! * interrupted by the signal handler and thereby possibly getting
! * confused.
! *
! * We will re-enable the interrupt if necessary in CheckStandbyTimeout.
! */
! if (standby_timeout_active)
! {
! struct itimerval timeval;
!
! MemSet(&timeval, 0, sizeof(struct itimerval));
! if (setitimer(ITIMER_REAL, &timeval, NULL))
! {
! standby_timeout_active = false;
! return false;
! }
! }
!
! standby_timeout_active = false;
!
! return true;
}
/*
--- 1486,1514 ----
PGSemaphoreUnlock(&proc->sem);
}
/*
* Check for statement timeout. If the timeout time has come,
! * trigger a query-cancel interrupt.
*/
! void
CheckStatementTimeout(void)
{
#ifdef HAVE_SETSID
! /* try to signal whole process group */
! kill(-MyProcPid, SIGINT);
#endif
! kill(MyProcPid, SIGINT);
}
/*
! * CheckStandbyDeadLock() will trigger if the deadlock timeout
! * happens earlier than StandbyTimeout. If it triggers,
! * StandbyTimeout will still be rescheduled.
*/
void
! CheckStandbyDeadLock(void)
{
! SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
}
/*
*************** disable_standby_sig_alarm(void)
*** 1819,1899 ****
* SIGALRM handler. Timers will only be set when InHotStandby.
* We simply ignore any signals unless the timer has been set.
*/
- static bool
- CheckStandbyTimeout(void)
- {
- TimestampTz now;
- bool reschedule = false;
-
- standby_timeout_active = false;
-
- now = GetCurrentTimestamp();
-
- /*
- * Reschedule the timer if its not time to wake yet, or if we have both
- * timers set and the first one has just been reached.
- */
- if (now >= statement_fin_time)
- {
- if (deadlock_timeout_active)
- {
- /*
- * We're still waiting when we reach deadlock timeout, so send out
- * a request to have other backends check themselves for deadlock.
- * Then continue waiting until statement_fin_time, if that's set.
- */
- SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
- deadlock_timeout_active = false;
-
- /*
- * Begin second waiting period if required.
- */
- if (statement_timeout_active)
- {
- reschedule = true;
- statement_fin_time = statement_fin_time2;
- }
- }
- else
- {
- /*
- * We've now reached statement_fin_time, so ask all conflicts to
- * leave, so we can press ahead with applying changes in recovery.
- */
- SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
- }
- }
- else
- reschedule = true;
-
- if (reschedule)
- {
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- standby_timeout_active = true;
- }
-
- return true;
- }
-
void
! handle_standby_sig_alarm(SIGNAL_ARGS)
{
! int save_errno = errno;
!
! if (standby_timeout_active)
! (void) CheckStandbyTimeout();
!
! errno = save_errno;
}
--- 1516,1523 ----
* SIGALRM handler. Timers will only be set when InHotStandby.
* We simply ignore any signals unless the timer has been set.
*/
void
! CheckStandbyTimeout(void)
{
! SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
}
diff -dcrpN postgresql/src/backend/storage/lmgr/timeout.c postgresql.1/src/backend/storage/lmgr/timeout.c
*** postgresql/src/backend/storage/lmgr/timeout.c 1970-01-01 01:00:00.000000000 +0100
--- postgresql.1/src/backend/storage/lmgr/timeout.c 2012-06-19 18:57:10.735365663 +0200
***************
*** 0 ****
--- 1,421 ----
+ /*-------------------------------------------------------------------------
+ *
+ * timeout.c
+ * routines to manage timeout sources handled by SIGALRM
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/storage/lmgr/timeout.c
+ *
+ *-------------------------------------------------------------------------
+ */
+ #include "postgres.h"
+
+ #include <sys/time.h>
+
+ #include "access/xact.h"
+ #include "storage/proc.h"
+ #include "storage/proctimeout.h"
+ #include "storage/timeout.h"
+ #include "utils/timestamp.h"
+
+ /* GUC variables */
+ int DeadlockTimeout = 1000;
+ int StatementTimeout = 0;
+
+ /*
+ * Infrastructure for timeouts
+ */
+
+ static void InitTimeout(TimeoutName tn, TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyTimeout(TimeoutName tn, bool keep_indicator);
+
+ typedef void (*timeout_check)(void);
+ typedef TimestampTz (*timeout_start)(void);
+
+ typedef struct {
+ TimeoutName index;
+ bool resched_next;
+
+ /* volatile because it may be changed from the signal handler */
+ volatile bool indicator;
+
+ timeout_check timeout_check;
+ timeout_start timeout_start;
+
+ TimestampTz start_time;
+ TimestampTz fin_time;
+ } timeout_params;
+
+ /*
+ * List of possible timeout reasons in the order of enum TimeoutName.
+ * The priority of timeouts (in case two of them would trigger at the
+ * same time) is determined by this order: the earlier one in the list
+ * has higher priority.
+ */
+ static timeout_params base_timeouts[TIMEOUT_MAX] = {
+ {
+ DEADLOCK_TIMEOUT, true, false,
+ CheckDeadLock, GetCurrentTimestamp,
+ 0
+ },
+
+ {
+ STATEMENT_TIMEOUT, false, false,
+ CheckStatementTimeout, GetCurrentStatementStartTimestamp,
+ 0
+ },
+
+ {
+ STANDBY_DEADLOCK_TIMEOUT, true, false,
+ CheckStandbyDeadLock, GetCurrentTimestamp,
+ 0
+ },
+
+ {
+ STANDBY_TIMEOUT, false, false,
+ CheckStandbyTimeout, GetCurrentTimestamp,
+ 0
+ }
+ };
+
+ /*
+ * List of active timeouts ordered by their fin_time and priority.
+ */
+ static int n_timeouts = 0;
+ static timeout_params *timeouts[TIMEOUT_MAX];
+
+ /*****************************************************************************
+ * Internal helper functions
+ *****************************************************************************/
+
+ /*
+ * Find the index of a given timeout type in the active array
+ */
+ static int
+ find_active_timeout(TimeoutName tn)
+ {
+ int i;
+
+ for (i = 0; i < n_timeouts; i++)
+ {
+ if (timeouts[i]->index == tn)
+ return i;
+ }
+
+ return -1;
+ }
+
+ #define is_timeout_active(tn) (find_active_timeout(tn) >= 0)
+
+ /*
+ * Insert tn'th timeout into the list of timeouts at the given index
+ * if the previous timeout allows rescheduling the next one in the list.
+ */
+ static void
+ insert_timeout(TimeoutName tn, int index)
+ {
+ int i;
+
+ if (index > 0 && !timeouts[index-1]->resched_next)
+ return;
+
+ for (i = n_timeouts - 1; i >= index; i--)
+ timeouts[i+1] = timeouts[i];
+
+ timeouts[index] = &base_timeouts[tn];
+ n_timeouts++;
+ }
+
+ /*
+ * Remove the index'th element from the timeout list.
+ */
+ static void
+ remove_timeout_index(int index)
+ {
+ int i;
+
+ if (index < 0)
+ return;
+
+ for (i = index + 1; i < n_timeouts; i++)
+ timeouts[i-1] = timeouts[i];
+
+ if (n_timeouts > 0 && index < n_timeouts)
+ n_timeouts--;
+ }
+
+ /*
+ * (Re)schedule the next active timeout
+ */
+ static bool
+ schedule_timeout(TimestampTz now)
+ {
+ long secs;
+ int usecs;
+ struct itimerval timeval;
+
+ /* There is no active timeout, do nothing. */
+ if (n_timeouts == 0)
+ return true;
+
+ TimestampDifference(now, timeouts[0]->fin_time,
+ &secs, &usecs);
+
+ /*
+ * It's possible that the difference is less than a microsecond;
+ * ensure we don't cancel, rather than set, the interrupt.
+ */
+ if (secs == 0 && usecs == 0)
+ usecs = 1;
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ timeval.it_value.tv_sec = secs;
+ timeval.it_value.tv_usec = usecs;
+ if (setitimer(ITIMER_REAL, &timeval, NULL))
+ return false;
+ return true;
+ }
+
+ /*****************************************************************************
+ * Init, Destroy and Check functions for different timeouts.
+ *
+ * NB: all Check* functions are run inside a signal handler, so be very wary
+ * about what is done in them or in called routines.
+ *****************************************************************************/
+
+ /*
+ * Common Init and Destroy functions
+ */
+
+ static void
+ InitTimeout(TimeoutName tn, TimestampTz start_time, TimestampTz fin_time)
+ {
+ base_timeouts[tn].indicator = false;
+ base_timeouts[tn].start_time = start_time;
+ base_timeouts[tn].fin_time = fin_time;
+ }
+
+ static void
+ DestroyTimeout(TimeoutName tn, bool keep_indicator)
+ {
+ if (!keep_indicator)
+ base_timeouts[tn].indicator = false;
+ base_timeouts[tn].start_time = 0;
+ base_timeouts[tn].fin_time = 0;
+ }
+
+ /*****************************************************************************
+ * Public API
+ *****************************************************************************/
+
+ /*
+ * Enable the SIGALRM interrupt to fire after the specified delay
+ *
+ * Delay is given in milliseconds. Caller should be sure a SIGALRM
+ * signal handler is installed before this is called.
+ *
+ * This code properly handles nesting of different timeout alarms.
+ *
+ * Returns TRUE if okay, FALSE on failure to set the timer.
+ */
+ bool
+ enable_timeout(TimeoutName tn, int delayms)
+ {
+ TimestampTz start_time;
+ TimestampTz fin_time;
+ int i;
+
+ Assert(!is_timeout_active(tn));
+
+ if (delayms <= 0)
+ return true;
+
+ start_time = base_timeouts[tn].timeout_start();
+ fin_time = TimestampTzPlusMilliseconds(start_time, delayms);
+
+ /* Find out the index where to insert the new timeout. */
+ for (i = 0; i < n_timeouts; i++)
+ {
+ /*
+ * The new timeout triggers earlier than
+ * a previously added one: insert here.
+ */
+ if (fin_time < timeouts[i]->fin_time)
+ break;
+ /*
+ * The new timeout triggers at the same time
+ * as the previously added one but has greater priority.
+ */
+ if (fin_time == timeouts[i]->fin_time && tn < timeouts[i]->index)
+ break;
+ }
+
+ /*
+ * Initialize the timeout parameters
+ */
+ InitTimeout(tn, start_time, fin_time);
+
+ insert_timeout(tn, i);
+
+ if (i > 0)
+ return true;
+
+ /* If we reach here, okay to set the timer interrupt */
+ if (!schedule_timeout(start_time))
+ return false;
+ return true;
+ }
+
+ /*
+ * Cancel the SIGALRM timer for the specific timeout.
+ * If a timeout is canceled, any other active timeout remains in force.
+ *
+ * Returns TRUE if okay, FALSE on failure to set the timer for
+ * the next timeout source.
+ */
+ bool
+ disable_timeout(TimeoutName tn, bool keep_indicator)
+ {
+ int i;
+
+ /*
+ * Always disable the interrupt if it is active; this avoids being
+ * interrupted by the signal handler and thereby possibly getting
+ * confused.
+ *
+ * We will re-enable the interrupt if necessary in ->check_timeout().
+ */
+ if (n_timeouts > 0)
+ {
+ struct itimerval timeval;
+
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ if (setitimer(ITIMER_REAL, &timeval, NULL))
+ {
+ n_timeouts = 0;
+ for (i = 0; i < TIMEOUT_MAX; i++)
+ DestroyTimeout(i, false);
+
+ return false;
+ }
+ }
+
+ /* Find the timeout and remove from the list. */
+ i = find_active_timeout(tn);
+ remove_timeout_index(i);
+
+ /* Do cleanup. */
+ DestroyTimeout(tn, keep_indicator);
+
+ /*
+ * If the first timeout was removed from the list and there is
+ * at least one active left, reschedule it.
+ */
+ if (i == 0 && n_timeouts > 0)
+ if (!schedule_timeout(GetCurrentTimestamp()))
+ return false;
+
+ return true;
+ }
+
+ /*
+ * Disable SIGALRM and remove all timeouts from the list and
+ * reset the timeout indicators.
+ */
+ bool
+ disable_all_timeouts(bool keep_indicators)
+ {
+ struct itimerval timeval;
+ int i;
+ bool ret;
+
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ ret = (setitimer(ITIMER_REAL, &timeval, NULL) == 0);
+
+ n_timeouts = 0;
+ for (i = 0; i < TIMEOUT_MAX; i++)
+ DestroyTimeout(i, keep_indicators);
+
+ return ret;
+ }
+
+ /*
+ * Return the timeout indicator
+ */
+ bool
+ get_timeout_indicator(TimeoutName tn)
+ {
+ return base_timeouts[tn].indicator;
+ }
+
+ /*
+ * Return the start of the timer for this timeout
+ */
+ TimestampTz
+ get_timeout_start(TimeoutName tn)
+ {
+ return base_timeouts[tn].start_time;
+ }
+
+ /*
+ * Signal handler for SIGALRM
+ *
+ * Process the check for the currently active timeout source and
+ * reschedule the next as needed. To avoid various edge cases,
+ * we must be careful to do nothing when there is nothing to be done.
+ */
+ void
+ handle_sig_alarm(SIGNAL_ARGS)
+ {
+ int save_errno = errno;
+
+ /*
+ * SIGALRM is cause for waking anything waiting on the process latch.
+ * Recovery (the startup process) doesn't have MyProc set so
+ * it can also use this signal handler.
+ */
+ if (MyProc)
+ SetLatch(&MyProc->procLatch);
+
+ if (n_timeouts > 0)
+ {
+ TimestampTz now;
+ bool reschedule;
+
+ now = GetCurrentTimestamp();
+
+ /* If our time has not come yet, do nothing. */
+ if (now < timeouts[0]->fin_time)
+ goto out;
+
+ /*
+ * Set the trigger indicator before calling the checker function.
+ * Setting if after may have side effects that lead to detecting
+ * a different event, like "user pressed Ctrl-C" is detected instead
+ * of the statement timeout having triggered.
+ */
+ timeouts[0]->indicator = true;
+
+ /* Call the timeout checker. */
+ timeouts[0]->timeout_check();
+
+ /*
+ * Remove the current timeout source and reschedule
+ * the next if needed. Short circuit disable_timeout(..., true)
+ * for the timeout source that just triggered.
+ */
+ reschedule = timeouts[0]->resched_next;
+ remove_timeout_index(0);
+
+ if (reschedule)
+ schedule_timeout(now);
+ else
+ disable_all_timeouts(true);
+ }
+
+ out:
+ errno = save_errno;
+ }
diff -dcrpN postgresql/src/backend/tcop/postgres.c postgresql.1/src/backend/tcop/postgres.c
*** postgresql/src/backend/tcop/postgres.c 2012-06-11 06:22:48.157921579 +0200
--- postgresql.1/src/backend/tcop/postgres.c 2012-06-19 17:49:55.358479113 +0200
***************
*** 64,69 ****
--- 64,70 ----
#include "storage/proc.h"
#include "storage/procsignal.h"
#include "storage/sinval.h"
+ #include "storage/timeout.h"
#include "tcop/fastpath.h"
#include "tcop/pquery.h"
#include "tcop/tcopprot.h"
*************** start_xact_command(void)
*** 2396,2404 ****
/* Set statement timeout running, if any */
/* NB: this mustn't be enabled until we are within an xact */
if (StatementTimeout > 0)
! enable_sig_alarm(StatementTimeout, true);
else
! cancel_from_timeout = false;
xact_started = true;
}
--- 2397,2405 ----
/* Set statement timeout running, if any */
/* NB: this mustn't be enabled until we are within an xact */
if (StatementTimeout > 0)
! enable_timeout(STATEMENT_TIMEOUT, StatementTimeout);
else
! disable_timeout(STATEMENT_TIMEOUT, false);
xact_started = true;
}
*************** finish_xact_command(void)
*** 2410,2416 ****
if (xact_started)
{
/* Cancel any active statement timeout before committing */
! disable_sig_alarm(true);
/* Now commit the command */
ereport(DEBUG3,
--- 2411,2417 ----
if (xact_started)
{
/* Cancel any active statement timeout before committing */
! disable_all_timeouts(false);
/* Now commit the command */
ereport(DEBUG3,
*************** ProcessInterrupts(void)
*** 2891,2897 ****
(errcode(ERRCODE_QUERY_CANCELED),
errmsg("canceling authentication due to timeout")));
}
! if (cancel_from_timeout)
{
ImmediateInterruptOK = false; /* not idle anymore */
DisableNotifyInterrupt();
--- 2892,2898 ----
(errcode(ERRCODE_QUERY_CANCELED),
errmsg("canceling authentication due to timeout")));
}
! if (get_timeout_indicator(STATEMENT_TIMEOUT))
{
ImmediateInterruptOK = false; /* not idle anymore */
DisableNotifyInterrupt();
*************** PostgresMain(int argc, char *argv[], con
*** 3799,3808 ****
/*
* Forget any pending QueryCancel request, since we're returning to
! * the idle loop anyway, and cancel the statement timer if running.
*/
QueryCancelPending = false;
! disable_sig_alarm(true);
QueryCancelPending = false; /* again in case timeout occurred */
/*
--- 3800,3809 ----
/*
* Forget any pending QueryCancel request, since we're returning to
! * the idle loop anyway, and cancel the timer if running.
*/
QueryCancelPending = false;
! disable_all_timeouts(false);
QueryCancelPending = false; /* again in case timeout occurred */
/*
diff -dcrpN postgresql/src/backend/utils/init/postinit.c postgresql.1/src/backend/utils/init/postinit.c
*** postgresql/src/backend/utils/init/postinit.c 2012-04-16 19:57:22.490916093 +0200
--- postgresql.1/src/backend/utils/init/postinit.c 2012-06-19 17:49:55.360479125 +0200
***************
*** 41,51 ****
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lmgr.h"
- #include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "storage/smgr.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
#include "utils/fmgroids.h"
--- 41,51 ----
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lmgr.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "storage/smgr.h"
+ #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
#include "utils/fmgroids.h"
*************** PerformAuthentication(Port *port)
*** 204,210 ****
* during authentication. Since we're inside a transaction and might do
* database access, we have to use the statement_timeout infrastructure.
*/
! if (!enable_sig_alarm(AuthenticationTimeout * 1000, true))
elog(FATAL, "could not set timer for authorization timeout");
/*
--- 204,210 ----
* during authentication. Since we're inside a transaction and might do
* database access, we have to use the statement_timeout infrastructure.
*/
! if (!enable_timeout(STATEMENT_TIMEOUT, AuthenticationTimeout * 1000))
elog(FATAL, "could not set timer for authorization timeout");
/*
*************** PerformAuthentication(Port *port)
*** 215,221 ****
/*
* Done with authentication. Disable the timeout, and log if needed.
*/
! if (!disable_sig_alarm(true))
elog(FATAL, "could not disable timer for authorization timeout");
if (Log_connections)
--- 215,221 ----
/*
* Done with authentication. Disable the timeout, and log if needed.
*/
! if (!disable_timeout(STATEMENT_TIMEOUT, false))
elog(FATAL, "could not disable timer for authorization timeout");
if (Log_connections)
diff -dcrpN postgresql/src/backend/utils/misc/guc.c postgresql.1/src/backend/utils/misc/guc.c
*** postgresql/src/backend/utils/misc/guc.c 2012-06-11 06:22:48.193921753 +0200
--- postgresql.1/src/backend/utils/misc/guc.c 2012-06-19 17:49:55.364479146 +0200
***************
*** 63,68 ****
--- 63,69 ----
#include "storage/standby.h"
#include "storage/fd.h"
#include "storage/predicate.h"
+ #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "tsearch/ts_cache.h"
#include "utils/builtins.h"
diff -dcrpN postgresql/src/include/storage/proc.h postgresql.1/src/include/storage/proc.h
*** postgresql/src/include/storage/proc.h 2012-06-11 06:22:48.223921899 +0200
--- postgresql.1/src/include/storage/proc.h 2012-06-19 17:49:55.365479152 +0200
*************** extern PGPROC *PreparedXactProcs;
*** 218,230 ****
/* configurable options */
- extern int DeadlockTimeout;
- extern int StatementTimeout;
extern bool log_lock_waits;
- extern volatile bool cancel_from_timeout;
-
-
/*
* Function Prototypes
*/
--- 218,225 ----
*************** extern void LockErrorCleanup(void);
*** 252,264 ****
extern void ProcWaitForSignal(void);
extern void ProcSendSignal(int pid);
- extern bool enable_sig_alarm(int delayms, bool is_statement_timeout);
- extern bool disable_sig_alarm(bool is_statement_timeout);
- extern void handle_sig_alarm(SIGNAL_ARGS);
-
- extern bool enable_standby_sig_alarm(TimestampTz now,
- TimestampTz fin_time, bool deadlock_only);
- extern bool disable_standby_sig_alarm(void);
- extern void handle_standby_sig_alarm(SIGNAL_ARGS);
-
#endif /* PROC_H */
--- 247,250 ----
diff -dcrpN postgresql/src/include/storage/proctimeout.h postgresql.1/src/include/storage/proctimeout.h
*** postgresql/src/include/storage/proctimeout.h 1970-01-01 01:00:00.000000000 +0100
--- postgresql.1/src/include/storage/proctimeout.h 2012-06-19 18:32:03.951063303 +0200
***************
*** 0 ****
--- 1,22 ----
+ /*-------------------------------------------------------------------------
+ *
+ * proctimeout.h
+ * declaration of timeout checker functions
+ *
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/storage/proctimeout.h
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef _PROCTIMEOUT_H_
+ #define _PROCTIMEOUT_H_
+
+ extern void CheckDeadLock(void);
+ extern void CheckStatementTimeout(void);
+ extern void CheckStandbyDeadLock(void);
+ extern void CheckStandbyTimeout(void);
+
+ #endif /* _PROCTIMEOUT_H_ */
diff -dcrpN postgresql/src/include/storage/timeout.h postgresql.1/src/include/storage/timeout.h
*** postgresql/src/include/storage/timeout.h 1970-01-01 01:00:00.000000000 +0100
--- postgresql.1/src/include/storage/timeout.h 2012-06-19 17:49:55.365479152 +0200
***************
*** 0 ****
--- 1,38 ----
+ /*-------------------------------------------------------------------------
+ *
+ * timeout.h
+ * SIGALRM timeout API
+ *
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/storage/timeout.h
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef _TIMEOUT_H_
+ #define _TIMEOUT_H_
+
+ #include "datatype/timestamp.h"
+
+ /* configurable options */
+ extern int DeadlockTimeout;
+ extern int StatementTimeout;
+
+ typedef enum TimeoutName {
+ DEADLOCK_TIMEOUT,
+ STATEMENT_TIMEOUT,
+ STANDBY_DEADLOCK_TIMEOUT,
+ STANDBY_TIMEOUT,
+ TIMEOUT_MAX
+ } TimeoutName;
+
+ extern bool enable_timeout(TimeoutName tn, int delayms);
+ extern bool disable_timeout(TimeoutName tn, bool keep_indicator);
+ extern bool disable_all_timeouts(bool keep_indicators);
+ extern bool get_timeout_indicator(TimeoutName tn);
+ extern TimestampTz get_timeout_start(TimeoutName tn);
+ extern void handle_sig_alarm(SIGNAL_ARGS);
+
+ #endif /* _TIMEOUT_H_ */
2-lock_timeout-v7.1.patchtext/x-patch; name=2-lock_timeout-v7.1.patchDownload
diff -dcrpN postgresql.1/doc/src/sgml/config.sgml postgresql.2/doc/src/sgml/config.sgml
*** postgresql.1/doc/src/sgml/config.sgml 2012-05-11 09:23:17.030668613 +0200
--- postgresql.2/doc/src/sgml/config.sgml 2012-06-19 18:37:40.058979368 +0200
*************** COPY postgres_log FROM '/full/path/to/lo
*** 4980,4986 ****
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.
</para>
<para>
--- 4980,4989 ----
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. 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>
*************** COPY postgres_log FROM '/full/path/to/lo
*** 4991,4996 ****
--- 4994,5053 ----
</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 -dcrpN 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-06-19 18:17:11.658875780 +0200
*************** LOCK [ TABLE ] [ ONLY ] <replaceable cla
*** 39,46 ****
<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.)
</para>
--- 39,49 ----
<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. 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 -dcrpN 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-06-19 18:17:11.659875787 +0200
*************** FOR SHARE [ OF <replaceable class="param
*** 1199,1204 ****
--- 1199,1212 ----
</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 -dcrpN postgresql.1/src/backend/port/posix_sema.c postgresql.2/src/backend/port/posix_sema.c
*** postgresql.1/src/backend/port/posix_sema.c 2012-04-16 19:57:22.438915489 +0200
--- postgresql.2/src/backend/port/posix_sema.c 2012-06-19 18:17:11.659875787 +0200
***************
*** 24,29 ****
--- 24,30 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/timeout.h"
#ifdef USE_NAMED_POSIX_SEMAPHORES
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 313,315 ****
--- 314,341 ----
return true;
}
+
+ /*
+ * PGSemaphoreTimedLock
+ *
+ * Lock a semaphore (decrement count), blocking if count would be < 0
+ * Return if lock_timeout expired
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ int errStatus;
+
+ do
+ {
+ ImmediateInterruptOK = interruptOK;
+ CHECK_FOR_INTERRUPTS();
+ errStatus = sem_wait(PG_SEM_REF(sema));
+ ImmediateInterruptOK = false;
+ } while (errStatus < 0 && errno == EINTR && !get_timeout_indicator(LOCK_TIMEOUT));
+
+ if (get_timeout_indicator(LOCK_TIMEOUT))
+ return;
+ if (errStatus < 0)
+ elog(FATAL, "sem_wait failed: %m");
+ }
diff -dcrpN postgresql.1/src/backend/port/sysv_sema.c postgresql.2/src/backend/port/sysv_sema.c
*** postgresql.1/src/backend/port/sysv_sema.c 2012-05-14 08:20:56.284830580 +0200
--- postgresql.2/src/backend/port/sysv_sema.c 2012-06-19 18:17:11.659875788 +0200
***************
*** 27,32 ****
--- 27,33 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/timeout.h"
#ifndef HAVE_UNION_SEMUN
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 492,494 ****
--- 493,525 ----
return true;
}
+
+ /*
+ * PGSemaphoreTimedLock
+ *
+ * Lock a semaphore (decrement count), blocking if count would be < 0
+ * Return if lock_timeout expired
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ int errStatus;
+ 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;
+ } while (errStatus < 0 && errno == EINTR && !get_timeout_indicator(LOCK_TIMEOUT));
+
+ if (get_timeout_indicator(LOCK_TIMEOUT))
+ return;
+ if (errStatus < 0)
+ elog(FATAL, "semop(id=%d) failed: %m", sema->semId);
+ }
diff -dcrpN postgresql.1/src/backend/port/win32_sema.c postgresql.2/src/backend/port/win32_sema.c
*** postgresql.1/src/backend/port/win32_sema.c 2012-06-11 06:22:48.137921483 +0200
--- postgresql.2/src/backend/port/win32_sema.c 2012-06-19 18:17:11.660875795 +0200
***************
*** 16,21 ****
--- 16,22 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/timeout.h"
static HANDLE *mySemSet; /* IDs of sema sets acquired so far */
static int numSems; /* number of sema sets acquired so far */
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 209,211 ****
--- 210,271 ----
/* 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.
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ DWORD ret;
+ HANDLE wh[2];
+
+ /*
+ * 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;
+ } while (errno == EINTR && !get_timeout_indicator(LOCK_TIMEOUT));
+
+ if (get_timeout_indicator(LOCK_TIMEOUT))
+ return;
+ if (errno != 0)
+ ereport(FATAL,
+ (errmsg("could not lock semaphore: error code %d", (int) GetLastError())));
+ }
diff -dcrpN postgresql.1/src/backend/storage/lmgr/lmgr.c postgresql.2/src/backend/storage/lmgr/lmgr.c
*** postgresql.1/src/backend/storage/lmgr/lmgr.c 2012-04-16 19:57:22.459915733 +0200
--- postgresql.2/src/backend/storage/lmgr/lmgr.c 2012-06-19 18:17:11.661875801 +0200
***************
*** 19,26 ****
--- 19,29 ----
#include "access/transam.h"
#include "access/xact.h"
#include "catalog/catalog.h"
+ #include "catalog/pg_database.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
+ #include "utils/lsyscache.h"
+ #include "storage/proc.h"
#include "storage/procarray.h"
#include "utils/inval.h"
*************** LockRelationOid(Oid relid, LOCKMODE lock
*** 78,83 ****
--- 81,101 ----
res = LockAcquire(&tag, lockmode, false, false);
+ if (res == LOCKACQUIRE_NOT_AVAIL)
+ {
+ char *relname = get_rel_name(relid);
+ if (relname)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ relname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation with OID %u",
+ relid)));
+ }
+
/*
* Now that we have the lock, check for invalidation messages, so that we
* will update or flush any stale relcache entry before we try to use it.
*************** LockRelation(Relation relation, LOCKMODE
*** 174,179 ****
--- 192,203 ----
res = LockAcquire(&tag, lockmode, false, false);
+ if (res == LOCKACQUIRE_NOT_AVAIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ RelationGetRelationName(relation))));
+
/*
* Now that we have the lock, check for invalidation messages; see notes
* in LockRelationOid.
*************** LockRelationIdForSession(LockRelId *reli
*** 251,257 ****
SET_LOCKTAG_RELATION(tag, relid->dbId, relid->relId);
! (void) LockAcquire(&tag, lockmode, true, false);
}
/*
--- 275,294 ----
SET_LOCKTAG_RELATION(tag, relid->dbId, relid->relId);
! if (LockAcquire(&tag, lockmode, true, false) == LOCKACQUIRE_NOT_AVAIL)
! {
! char *relname = get_rel_name(relid->relId);
! if (relname)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on relation \"%s\"",
! relname)));
! else
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on relation with OID %u",
! relid->relId)));
! }
}
/*
*************** LockRelationForExtension(Relation relati
*** 286,292 ****
relation->rd_lockInfo.lockRelId.dbId,
relation->rd_lockInfo.lockRelId.relId);
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 323,333 ----
relation->rd_lockInfo.lockRelId.dbId,
relation->rd_lockInfo.lockRelId.relId);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on index \"%s\"",
! RelationGetRelationName(relation))));
}
/*
*************** LockPage(Relation relation, BlockNumber
*** 320,326 ****
relation->rd_lockInfo.lockRelId.relId,
blkno);
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 361,371 ----
relation->rd_lockInfo.lockRelId.relId,
blkno);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on page %u of relation \"%s\"",
! blkno, RelationGetRelationName(relation))));
}
/*
*************** LockTuple(Relation relation, ItemPointer
*** 376,382 ****
ItemPointerGetBlockNumber(tid),
ItemPointerGetOffsetNumber(tid));
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 421,431 ----
ItemPointerGetBlockNumber(tid),
ItemPointerGetOffsetNumber(tid));
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on row in relation \"%s\"",
! RelationGetRelationName(relation))));
}
/*
*************** XactLockTableInsert(TransactionId xid)
*** 430,436 ****
SET_LOCKTAG_TRANSACTION(tag, xid);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
}
/*
--- 479,488 ----
SET_LOCKTAG_TRANSACTION(tag, xid);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on transaction with ID %u", xid)));
}
/*
*************** XactLockTableWait(TransactionId xid)
*** 474,480 ****
SET_LOCKTAG_TRANSACTION(tag, xid);
! (void) LockAcquire(&tag, ShareLock, false, false);
LockRelease(&tag, ShareLock, false);
--- 526,535 ----
SET_LOCKTAG_TRANSACTION(tag, xid);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on transaction with ID %u", xid)));
LockRelease(&tag, ShareLock, false);
*************** LockDatabaseObject(Oid classid, Oid obji
*** 535,541 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, false, false);
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
--- 590,600 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
*************** LockSharedObject(Oid classid, Oid objid,
*** 576,582 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, false, false);
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
--- 635,645 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
*************** LockSharedObjectForSession(Oid classid,
*** 618,624 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, true, false);
}
/*
--- 681,702 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, true, false) == LOCKACQUIRE_NOT_AVAIL)
! switch(classid)
! {
! case DatabaseRelationId:
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on database with ID %u",
! objid)));
! break;
! default:
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
! break;
! }
}
/*
diff -dcrpN postgresql.1/src/backend/storage/lmgr/lock.c postgresql.2/src/backend/storage/lmgr/lock.c
*** postgresql.1/src/backend/storage/lmgr/lock.c 2012-06-11 06:22:48.154921564 +0200
--- postgresql.2/src/backend/storage/lmgr/lock.c 2012-06-19 18:17:11.663875813 +0200
*************** static PROCLOCK *SetupLockInTable(LockMe
*** 343,349 ****
static void GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner);
static void BeginStrongLockAcquire(LOCALLOCK *locallock, uint32 fasthashcode);
static void FinishStrongLockAcquire(void);
! static void WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner);
static void ReleaseLockIfHeld(LOCALLOCK *locallock, bool sessionLock);
static bool UnGrantLock(LOCK *lock, LOCKMODE lockmode,
PROCLOCK *proclock, LockMethod lockMethodTable);
--- 343,349 ----
static void GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner);
static void BeginStrongLockAcquire(LOCALLOCK *locallock, uint32 fasthashcode);
static void FinishStrongLockAcquire(void);
! static int WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner);
static void ReleaseLockIfHeld(LOCALLOCK *locallock, bool sessionLock);
static bool UnGrantLock(LOCK *lock, LOCKMODE lockmode,
PROCLOCK *proclock, LockMethod lockMethodTable);
*************** ProcLockHashCode(const PROCLOCKTAG *proc
*** 549,555 ****
* dontWait: if true, don't wait to acquire lock
*
* Returns one of:
! * LOCKACQUIRE_NOT_AVAIL lock not available, and dontWait=true
* LOCKACQUIRE_OK lock successfully acquired
* LOCKACQUIRE_ALREADY_HELD incremented count for lock already held
*
--- 549,555 ----
* dontWait: if true, don't wait to acquire lock
*
* Returns one of:
! * LOCKACQUIRE_NOT_AVAIL lock not available, either dontWait=true or timeout
* LOCKACQUIRE_OK lock successfully acquired
* LOCKACQUIRE_ALREADY_HELD incremented count for lock already held
*
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 863,869 ****
locktag->locktag_type,
lockmode);
! WaitOnLock(locallock, owner);
TRACE_POSTGRESQL_LOCK_WAIT_DONE(locktag->locktag_field1,
locktag->locktag_field2,
--- 863,869 ----
locktag->locktag_type,
lockmode);
! status = WaitOnLock(locallock, owner);
TRACE_POSTGRESQL_LOCK_WAIT_DONE(locktag->locktag_field1,
locktag->locktag_field2,
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 878,905 ****
* done when the lock was granted to us --- see notes in WaitOnLock.
*/
! /*
! * Check the proclock entry status, in case something in the ipc
! * communication doesn't work correctly.
! */
! if (!(proclock->holdMask & LOCKBIT_ON(lockmode)))
{
! AbortStrongLockAcquire();
! PROCLOCK_PRINT("LockAcquire: INCONSISTENT", proclock);
! LOCK_PRINT("LockAcquire: INCONSISTENT", lock, lockmode);
! /* Should we retry ? */
! LWLockRelease(partitionLock);
! elog(ERROR, "LockAcquire failed");
}
- PROCLOCK_PRINT("LockAcquire: granted", proclock);
- LOCK_PRINT("LockAcquire: granted", lock, lockmode);
}
! /*
! * Lock state is fully up-to-date now; if we error out after this, no
! * special error cleanup is required.
! */
! FinishStrongLockAcquire();
LWLockRelease(partitionLock);
--- 878,928 ----
* done when the lock was granted to us --- see notes in WaitOnLock.
*/
! switch (status)
{
! case STATUS_OK:
! /*
! * Check the proclock entry status, in case something in the ipc
! * communication doesn't work correctly.
! */
! if (!(proclock->holdMask & LOCKBIT_ON(lockmode)))
! {
! AbortStrongLockAcquire();
! PROCLOCK_PRINT("LockAcquire: INCONSISTENT", proclock);
! LOCK_PRINT("LockAcquire: INCONSISTENT", lock, lockmode);
! /* Should we retry ? */
! LWLockRelease(partitionLock);
! elog(ERROR, "LockAcquire failed");
! }
! PROCLOCK_PRINT("LockAcquire: granted", proclock);
! LOCK_PRINT("LockAcquire: granted", lock, lockmode);
! break;
! case STATUS_WAITING:
! PROCLOCK_PRINT("LockAcquire: timed out", proclock);
! LOCK_PRINT("LockAcquire: timed out", lock, lockmode);
! break;
! default:
! elog(ERROR, "LockAcquire invalid status");
! break;
}
}
! if (status == STATUS_WAITING)
! {
! /*
! * lock_timeout was set and WaitOnLock() indicated
! * we timed out. Clean up manually.
! */
! AbortStrongLockAcquire();
! }
! else
! {
! /*
! * Lock state is fully up-to-date now; if we error out after this, no
! * special error cleanup is required.
! */
! FinishStrongLockAcquire();
! }
LWLockRelease(partitionLock);
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 918,924 ****
locktag->locktag_field2);
}
! return LOCKACQUIRE_OK;
}
/*
--- 941,947 ----
locktag->locktag_field2);
}
! return (status == STATUS_OK ? LOCKACQUIRE_OK : LOCKACQUIRE_NOT_AVAIL);
}
/*
*************** GrantAwaitedLock(void)
*** 1437,1450 ****
* Caller must have set MyProc->heldLocks to reflect locks already held
* on the lockable object by this process.
*
* The appropriate partition lock must be held at entry.
*/
! static void
WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner)
{
LOCKMETHODID lockmethodid = LOCALLOCK_LOCKMETHOD(*locallock);
LockMethod lockMethodTable = LockMethods[lockmethodid];
char *volatile new_status = NULL;
LOCK_PRINT("WaitOnLock: sleeping on lock",
locallock->lock, locallock->tag.mode);
--- 1460,1479 ----
* Caller must have set MyProc->heldLocks to reflect locks already held
* on the lockable object by this process.
*
+ * Result: returns value of ProcSleep()
+ * STATUS_OK if we acquired the lock
+ * STATUS_ERROR if not (deadlock)
+ * STATUS_WAITING if not (timeout)
+ *
* The appropriate partition lock must be held at entry.
*/
! static int
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);
*************** WaitOnLock(LOCALLOCK *locallock, Resourc
*** 1486,1493 ****
*/
PG_TRY();
{
! if (ProcSleep(locallock, lockMethodTable) != STATUS_OK)
{
/*
* We failed as a result of a deadlock, see CheckDeadLock(). Quit
* now.
--- 1515,1527 ----
*/
PG_TRY();
{
! wait_status = ProcSleep(locallock, lockMethodTable);
! switch (wait_status)
{
+ case STATUS_OK:
+ case STATUS_WAITING:
+ break;
+ default:
/*
* We failed as a result of a deadlock, see CheckDeadLock(). Quit
* now.
*************** WaitOnLock(LOCALLOCK *locallock, Resourc
*** 1532,1539 ****
pfree(new_status);
}
! LOCK_PRINT("WaitOnLock: wakeup on lock",
locallock->lock, locallock->tag.mode);
}
/*
--- 1566,1579 ----
pfree(new_status);
}
! 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);
+
+ return wait_status;
}
/*
*************** VirtualXactLock(VirtualTransactionId vxi
*** 3853,3859 ****
LWLockRelease(proc->backendLock);
/* Time to wait. */
! (void) LockAcquire(&tag, ShareLock, false, false);
LockRelease(&tag, ShareLock, false);
return true;
--- 3893,3903 ----
LWLockRelease(proc->backendLock);
/* Time to wait. */
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on virtual transaction with ID %u",
! vxid.localTransactionId)));
LockRelease(&tag, ShareLock, false);
return true;
diff -dcrpN 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-06-19 17:59:28.649869905 +0200
--- postgresql.2/src/backend/storage/lmgr/proc.c 2012-06-19 18:17:11.665875828 +0200
*************** LockErrorCleanup(void)
*** 638,645 ****
if (lockAwaited == NULL)
return;
! /* Turn off the deadlock timer, if it's still running (see ProcSleep) */
disable_timeout(DEADLOCK_TIMEOUT, false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
--- 638,649 ----
if (lockAwaited == NULL)
return;
! /*
! * Turn off the deadlock and lock timeout timers,
! * if they are still running (see ProcSleep)
! */
disable_timeout(DEADLOCK_TIMEOUT, false);
+ disable_timeout(LOCK_TIMEOUT, false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
*************** ProcQueueInit(PROC_QUEUE *queue)
*** 875,881 ****
* 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).
*
* ASSUME: that no one will fiddle with the queue until after
* we release the partition lock.
--- 879,888 ----
* 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)
! * STATUS_WAITING if not (timeout)
*
* ASSUME: that no one will fiddle with the queue until after
* we release the partition lock.
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 897,902 ****
--- 904,910 ----
LOCKMASK myHeldLocks = MyProc->heldLocks;
bool early_deadlock = false;
bool allow_autovacuum_cancel = true;
+ bool timeout_detected = false;
int myWaitStatus;
PGPROC *proc;
int i;
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1038,1045 ****
elog(FATAL, "could not set timer for process wakeup");
/*
! * 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
* saved wakeup might be leftover from a previous operation (for example,
* we aborted ProcWaitForSignal just before someone did ProcSendSignal).
--- 1046,1059 ----
elog(FATAL, "could not set timer for process wakeup");
/*
! * Queue the timer for lock timeout, too.
! */
! if (!enable_timeout(LOCK_TIMEOUT, LockTimeout))
! elog(FATAL, "could not set timer for process wakeup");
!
! /*
! * 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).
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1056,1062 ****
*/
do
{
! PGSemaphoreLock(&MyProc->sem, true);
/*
* waitStatus could change from STATUS_WAITING to something else
--- 1070,1081 ----
*/
do
{
! PGSemaphoreTimedLock(&MyProc->sem, true);
!
! /* Check and keep the lock timeout indicator for later checks */
! timeout_detected = get_timeout_indicator(LOCK_TIMEOUT);
! if (timeout_detected)
! break;
/*
* waitStatus could change from STATUS_WAITING to something else
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1185,1196 ****
} while (myWaitStatus == STATUS_WAITING);
/*
! * Disable the timer, if it's still running
*/
if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for process wakeup");
/*
* Re-acquire the lock table's partition lock. We have to do this to hold
* off cancel/die interrupts before we can mess with lockAwaited (else we
* might have a missed or duplicated locallock update).
--- 1204,1221 ----
} while (myWaitStatus == STATUS_WAITING);
/*
! * Disable the deadlock timer, if it's still running
*/
if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for process wakeup");
/*
+ * Disable the lock timeout timer, if it's still running
+ */
+ if (!disable_timeout(LOCK_TIMEOUT, false))
+ elog(FATAL, "could not disable timer for process wakeup");
+
+ /*
* Re-acquire the lock table's partition lock. We have to do this to hold
* off cancel/die interrupts before we can mess with lockAwaited (else we
* might have a missed or duplicated locallock update).
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1198,1203 ****
--- 1223,1237 ----
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;
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1211,1218 ****
/*
* We don't have to do anything else, because the awaker did all the
* necessary update of the lock table and MyProc.
*/
! return MyProc->waitStatus;
}
--- 1245,1254 ----
/*
* 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 (timeout_detected ? STATUS_WAITING : MyProc->waitStatus);
}
diff -dcrpN postgresql.1/src/backend/storage/lmgr/timeout.c postgresql.2/src/backend/storage/lmgr/timeout.c
*** postgresql.1/src/backend/storage/lmgr/timeout.c 2012-06-19 18:57:10.735365663 +0200
--- postgresql.2/src/backend/storage/lmgr/timeout.c 2012-06-19 18:54:40.270578186 +0200
***************
*** 20,30 ****
--- 20,34 ----
#include "storage/proc.h"
#include "storage/proctimeout.h"
#include "storage/timeout.h"
+ #include "utils/guc.h"
#include "utils/timestamp.h"
/* GUC variables */
int DeadlockTimeout = 1000;
int StatementTimeout = 0;
+ int LockTimeout = 0;
+
+ int LockTimeoutOption = LOCK_TIMEOUT_PER_LOCK;
/*
* Infrastructure for timeouts
*************** int StatementTimeout = 0;
*** 33,38 ****
--- 37,45 ----
static void InitTimeout(TimeoutName tn, TimestampTz start_time, TimestampTz fin_time);
static void DestroyTimeout(TimeoutName tn, bool keep_indicator);
+ static void CheckDummyTimeout(void);
+ static TimestampTz GetLockTimeoutStart(void);
+
typedef void (*timeout_check)(void);
typedef TimestampTz (*timeout_start)(void);
*************** static timeout_params base_timeouts[TIME
*** 64,69 ****
--- 71,82 ----
},
{
+ LOCK_TIMEOUT, false, false,
+ CheckDummyTimeout, GetLockTimeoutStart,
+ 0
+ },
+
+ {
STATEMENT_TIMEOUT, false, false,
CheckStatementTimeout, GetCurrentStatementStartTimestamp,
0
*************** DestroyTimeout(TimeoutName tn, bool keep
*** 207,212 ****
--- 220,262 ----
base_timeouts[tn].fin_time = 0;
}
+ /*
+ * CheckDummyTimeout
+ *
+ * Do nothing. Usable for timeout sources that don't need
+ * extra processing, like lock_timeout. The timeout indicator
+ * is set by the signal handler.
+ */
+ static void
+ CheckDummyTimeout(void)
+ {
+ return;
+ }
+
+ /*
+ * GetLockTimeoutStart
+ *
+ * Depending on lock_timeout_option, return either the current time
+ * ('per_lock') or the statement start time ('per_statement').
+ */
+ static TimestampTz
+ GetLockTimeoutStart(void)
+ {
+ switch (LockTimeoutOption)
+ {
+ case LOCK_TIMEOUT_PER_LOCK:
+ return GetCurrentTimestamp();
+ case LOCK_TIMEOUT_PER_STMT:
+ return GetCurrentStatementStartTimestamp();
+ default:
+ elog(ERROR, "unhandled lock_timeout_option value: \"%s\"",
+ GetConfigOptionByName("lock_timeout_option", NULL));
+ break;
+ }
+ /* Silence possible warnings. */
+ return 0;
+ }
+
/*****************************************************************************
* Public API
*****************************************************************************/
diff -dcrpN postgresql.1/src/backend/utils/adt/lockfuncs.c postgresql.2/src/backend/utils/adt/lockfuncs.c
*** postgresql.1/src/backend/utils/adt/lockfuncs.c 2012-06-11 06:22:48.162921604 +0200
--- postgresql.2/src/backend/utils/adt/lockfuncs.c 2012-06-19 18:17:11.667875843 +0200
*************** pg_advisory_lock_int8(PG_FUNCTION_ARGS)
*** 421,427 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ExclusiveLock, true, false);
PG_RETURN_VOID();
}
--- 421,431 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ExclusiveLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_int8(PG_FUNCTION_A
*** 438,444 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
PG_RETURN_VOID();
}
--- 442,452 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_shared_int8(PG_FUNCTION
*** 454,460 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ShareLock, true, false);
PG_RETURN_VOID();
}
--- 462,472 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ShareLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_shared_int8(PG_FUN
*** 471,477 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ShareLock, false, false);
PG_RETURN_VOID();
}
--- 483,493 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_int4(PG_FUNCTION_ARGS)
*** 604,610 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ExclusiveLock, true, false);
PG_RETURN_VOID();
}
--- 620,630 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ExclusiveLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_int4(PG_FUNCTION_A
*** 622,628 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
PG_RETURN_VOID();
}
--- 642,652 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_shared_int4(PG_FUNCTION
*** 639,645 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ShareLock, true, false);
PG_RETURN_VOID();
}
--- 663,673 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ShareLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_shared_int4(PG_FUN
*** 657,663 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ShareLock, false, false);
PG_RETURN_VOID();
}
--- 685,695 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
diff -dcrpN 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-06-19 17:49:55.364479146 +0200
--- postgresql.2/src/backend/utils/misc/guc.c 2012-06-19 18:17:11.673875883 +0200
*************** static const struct config_enum_entry sy
*** 389,394 ****
--- 389,405 ----
};
/*
+ * 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[];
*************** static struct config_int ConfigureNamesI
*** 1861,1866 ****
--- 1872,1888 ----
},
{
+ {"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
*************** static struct config_enum ConfigureNames
*** 3141,3146 ****
--- 3163,3178 ----
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 -dcrpN 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-06-19 18:17:11.673875883 +0200
***************
*** 528,533 ****
--- 528,538 ----
#------------------------------------------------------------------------------
#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 -dcrpN postgresql.1/src/include/storage/pg_sema.h postgresql.2/src/include/storage/pg_sema.h
*** postgresql.1/src/include/storage/pg_sema.h 2012-04-16 19:57:22.672918205 +0200
--- postgresql.2/src/include/storage/pg_sema.h 2012-06-19 18:17:11.674875890 +0200
*************** extern void PGSemaphoreUnlock(PGSemaphor
*** 80,83 ****
--- 80,89 ----
/* 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 void PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK);
+
#endif /* PG_SEMA_H */
diff -dcrpN postgresql.1/src/include/storage/timeout.h postgresql.2/src/include/storage/timeout.h
*** postgresql.1/src/include/storage/timeout.h 2012-06-19 17:49:55.365479152 +0200
--- postgresql.2/src/include/storage/timeout.h 2012-06-19 18:17:11.674875890 +0200
***************
*** 19,33 ****
--- 19,42 ----
/* configurable options */
extern int DeadlockTimeout;
extern int StatementTimeout;
+ extern int LockTimeout;
typedef enum TimeoutName {
DEADLOCK_TIMEOUT,
+ LOCK_TIMEOUT,
STATEMENT_TIMEOUT,
STANDBY_DEADLOCK_TIMEOUT,
STANDBY_TIMEOUT,
TIMEOUT_MAX
} TimeoutName;
+ typedef enum LockTimeoutOptions {
+ LOCK_TIMEOUT_PER_LOCK,
+ LOCK_TIMEOUT_PER_STMT
+ } LockTimeoutOptions;
+
+ extern int LockTimeoutOption;
+
extern bool enable_timeout(TimeoutName tn, int delayms);
extern bool disable_timeout(TimeoutName tn, bool keep_indicator);
extern bool disable_all_timeouts(bool keep_indicators);
diff -dcrpN 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-06-19 18:17:11.674875891 +0200
*************** set statement_timeout to 2000;
*** 198,203 ****
--- 198,223 ----
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: could not obtain lock on relation "pxtest3"
+ LINE 1: 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;
+ ERROR: could not obtain lock on relation "pxtest3"
+ LINE 1: SELECT * FROM pxtest3;
+ ^
+ 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
*************** set statement_timeout to 2000;
*** 213,218 ****
--- 233,245 ----
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: could not obtain lock on relation "pxtest3"
+ LINE 1: SELECT * FROM pxtest3;
+ ^
+ reset lock_timeout;
-- Commit table creation
COMMIT PREPARED 'regress-one';
\d pxtest2
diff -dcrpN 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-06-19 18:17:11.675875897 +0200
*************** set statement_timeout to 2000;
*** 126,131 ****
--- 126,147 ----
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 -
*************** set statement_timeout to 2000;
*** 137,142 ****
--- 153,163 ----
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
Excerpts from Boszormenyi Zoltan's message of mar jun 19 12:44:04 -0400 2012:
OK, all 4 Check* functions are now moved back into proc.c,
nothing outside of timeout.c touches anything in it. New patches
are attached.
Yeah, I like this one better, thanks.
It seems to me that the "check" functions are no longer "check" anymore,
right? I mean, they don't check whether the time has expired. It can
be argued that CheckDeadLock is well named, because it does check
whether there is a deadlock before doing anything else; but
CheckStandbyTimeout is no longer a check -- it just sends a signal.
Do we need to rename these functions?
Why are you using the deadlock timeout for authentication? Wouldn't it
make more sense to have a separate TimeoutName, just to keep things
clean?
The "NB:" comment here doesn't seem to be useful anymore:
+ /*****************************************************************************
+ * Init, Destroy and Check functions for different timeouts.
+ *
+ * NB: all Check* functions are run inside a signal handler, so be very wary
+ * about what is done in them or in called routines.
+ *****************************************************************************/
In base_timeouts you don't initialize fin_time for any of the members.
This is probably unimportant but then why initialize start_time?
--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support
2012-06-19 19:19 keltezéssel, Alvaro Herrera írta:
Excerpts from Boszormenyi Zoltan's message of mar jun 19 12:44:04 -0400 2012:
OK, all 4 Check* functions are now moved back into proc.c,
nothing outside of timeout.c touches anything in it. New patches
are attached.Yeah, I like this one better, thanks.
Thanks. It seems I am on the right track then. :-)
It seems to me that the "check" functions are no longer "check" anymore,
right? I mean, they don't check whether the time has expired. It can
be argued that CheckDeadLock is well named, because it does check
whether there is a deadlock before doing anything else; but
CheckStandbyTimeout is no longer a check -- it just sends a signal.
Do we need to rename these functions?
Well, maybe. How about *Function() instead of Check*()?
Why are you using the deadlock timeout for authentication?
Because it originally also used the deadlock timeout.
I agree that it was abusing code that's written for something else.
Wouldn't it
make more sense to have a separate TimeoutName, just to keep things
clean?
Yes, sure. Can you tell me a way to test authentication timeout?
"pre_auth_delay" is not good for testing it.
Changing authentication_timeout to 1sec both in the GUC list and the initial value of it in
postmaster.c plus adding this code below after enabling the auth timeout didn't help.
---8<------8<------8<------8<---
if (AuthenticationTimeout > 0)
pg_usleep((AuthenticationTimeout + 2) * 1000000L);
---8<------8<------8<------8<---
I got this despite authentication_timeout being 1 second:
---8<------8<------8<------8<---
============== removing existing temp installation ==============
============== creating temporary installation ==============
============== initializing database system ==============
============== starting postmaster ==============
pg_regress: postmaster did not respond within 60 seconds
Examine .../postgresql.1/src/test/regress/log/postmaster.log for the reason
---8<------8<------8<------8<---
Anyway, it doesn't seem to me that the code after enabling auth timeout
actually uses anything from the timeout code but the side effect of
a signal interrupting read() on the client connection socket and logs
"incomplete startup packet" or "invalid length of startup packet".
If that's true, then it can use its own (dummy) timeout and I modified
postmaster.c and timeout.c accordingly. It survives "make check".
The "NB:" comment here doesn't seem to be useful anymore: + /***************************************************************************** + * Init, Destroy and Check functions for different timeouts. + * + * NB: all Check* functions are run inside a signal handler, so be very wary + * about what is done in them or in called routines. + *****************************************************************************/
I removed it.
In base_timeouts you don't initialize fin_time for any of the members.
This is probably unimportant but then why initialize start_time?
The answer is "out of habit" and "leftover".
But now the checks are only done for active timeouts,
I think neither of these are needed to be initialized now.
Thanks for spotting it. It survives "make check".
Attached are the new patches with these changes.
--
----------------------------------
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/
Attachments:
1-timeout-framework-v8.patchtext/x-patch; name=1-timeout-framework-v8.patchDownload
diff -dcrpN postgresql/src/backend/postmaster/autovacuum.c postgresql.1/src/backend/postmaster/autovacuum.c
*** postgresql/src/backend/postmaster/autovacuum.c 2012-06-11 06:22:48.137921483 +0200
--- postgresql.1/src/backend/postmaster/autovacuum.c 2012-06-19 17:49:55.348479057 +0200
***************
*** 86,94 ****
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
- #include "storage/proc.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "tcop/tcopprot.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
--- 86,94 ----
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
+ #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
*************** AutoVacLauncherMain(int argc, char *argv
*** 484,490 ****
/* Forget any pending QueryCancel request */
QueryCancelPending = false;
! disable_sig_alarm(true);
QueryCancelPending = false; /* again in case timeout occurred */
/* Report the error to the server log */
--- 484,490 ----
/* Forget any pending QueryCancel request */
QueryCancelPending = false;
! disable_all_timeouts(false);
QueryCancelPending = false; /* again in case timeout occurred */
/* Report the error to the server log */
diff -dcrpN postgresql/src/backend/postmaster/postmaster.c postgresql.1/src/backend/postmaster/postmaster.c
*** postgresql/src/backend/postmaster/postmaster.c 2012-06-11 06:22:48.148921535 +0200
--- postgresql.1/src/backend/postmaster/postmaster.c 2012-06-19 20:39:15.561668974 +0200
***************
*** 112,118 ****
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
! #include "storage/proc.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/datetime.h"
--- 112,118 ----
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
! #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/datetime.h"
*************** BackendInitialize(Port *port)
*** 3486,3494 ****
* Ready to begin client interaction. We will give up and exit(1) after a
* time delay, so that a broken client can't hog a connection
* indefinitely. PreAuthDelay and any DNS interactions above don't count
! * against the time limit.
*/
! if (!enable_sig_alarm(AuthenticationTimeout * 1000, false))
elog(FATAL, "could not set timer for startup packet timeout");
/*
--- 3486,3494 ----
* Ready to begin client interaction. We will give up and exit(1) after a
* time delay, so that a broken client can't hog a connection
* indefinitely. PreAuthDelay and any DNS interactions above don't count
! * against the time limit. Use the deadlock timeout interface.
*/
! if (!enable_timeout(AUTHENTICATION_TIMEOUT, AuthenticationTimeout * 1000))
elog(FATAL, "could not set timer for startup packet timeout");
/*
*************** BackendInitialize(Port *port)
*** 3526,3532 ****
/*
* Disable the timeout, and prevent SIGTERM/SIGQUIT again.
*/
! if (!disable_sig_alarm(false))
elog(FATAL, "could not disable timer for startup packet timeout");
PG_SETMASK(&BlockSig);
}
--- 3526,3532 ----
/*
* Disable the timeout, and prevent SIGTERM/SIGQUIT again.
*/
! if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for startup packet timeout");
PG_SETMASK(&BlockSig);
}
diff -dcrpN postgresql/src/backend/postmaster/startup.c postgresql.1/src/backend/postmaster/startup.c
*** postgresql/src/backend/postmaster/startup.c 2012-04-16 19:57:22.442915536 +0200
--- postgresql.1/src/backend/postmaster/startup.c 2012-06-19 17:49:55.351479077 +0200
***************
*** 27,33 ****
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
! #include "storage/proc.h"
#include "utils/guc.h"
--- 27,33 ----
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
! #include "storage/timeout.h"
#include "utils/guc.h"
*************** StartupProcessMain(void)
*** 195,202 ****
pqsignal(SIGTERM, StartupProcShutdownHandler); /* request shutdown */
pqsignal(SIGQUIT, startupproc_quickdie); /* hard crash time */
if (EnableHotStandby)
! pqsignal(SIGALRM, handle_standby_sig_alarm); /* ignored unless
! * InHotStandby */
else
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
--- 195,202 ----
pqsignal(SIGTERM, StartupProcShutdownHandler); /* request shutdown */
pqsignal(SIGQUIT, startupproc_quickdie); /* hard crash time */
if (EnableHotStandby)
! pqsignal(SIGALRM, handle_sig_alarm); /* ignored unless
! * InHotStandby */
else
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
diff -dcrpN postgresql/src/backend/storage/ipc/standby.c postgresql.1/src/backend/storage/ipc/standby.c
*** postgresql/src/backend/storage/ipc/standby.c 2012-06-11 06:22:48.154921564 +0200
--- postgresql.1/src/backend/storage/ipc/standby.c 2012-06-19 17:49:55.352479081 +0200
***************
*** 23,32 ****
#include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
- #include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/sinvaladt.h"
#include "storage/standby.h"
#include "utils/ps_status.h"
#include "utils/timestamp.h"
--- 23,32 ----
#include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
#include "storage/procarray.h"
#include "storage/sinvaladt.h"
#include "storage/standby.h"
+ #include "storage/timeout.h"
#include "utils/ps_status.h"
#include "utils/timestamp.h"
*************** ResolveRecoveryConflictWithLock(Oid dbOi
*** 394,400 ****
void
ResolveRecoveryConflictWithBufferPin(void)
{
- bool sig_alarm_enabled = false;
TimestampTz ltime;
TimestampTz now;
--- 394,399 ----
*************** ResolveRecoveryConflictWithBufferPin(voi
*** 409,417 ****
* We're willing to wait forever for conflicts, so set timeout for
* deadlock check (only)
*/
! if (enable_standby_sig_alarm(now, now, true))
! sig_alarm_enabled = true;
! else
elog(FATAL, "could not set timer for process wakeup");
}
else if (now >= ltime)
--- 408,414 ----
* We're willing to wait forever for conflicts, so set timeout for
* deadlock check (only)
*/
! if (!enable_timeout(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout))
elog(FATAL, "could not set timer for process wakeup");
}
else if (now >= ltime)
*************** ResolveRecoveryConflictWithBufferPin(voi
*** 423,446 ****
}
else
{
/*
* Wake up at ltime, and check for deadlocks as well if we will be
* waiting longer than deadlock_timeout
*/
! if (enable_standby_sig_alarm(now, ltime, false))
! sig_alarm_enabled = true;
! else
elog(FATAL, "could not set timer for process wakeup");
}
/* Wait to be signaled by UnpinBuffer() */
ProcWaitForSignal();
! if (sig_alarm_enabled)
! {
! if (!disable_standby_sig_alarm())
! elog(FATAL, "could not disable timer for process wakeup");
! }
}
void
--- 420,447 ----
}
else
{
+ long secs, msecs;
+ int usecs;
+
/*
* Wake up at ltime, and check for deadlocks as well if we will be
* waiting longer than deadlock_timeout
*/
! if (!enable_timeout(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout))
! elog(FATAL, "could not set timer for process wakeup");
!
! TimestampDifference(now, ltime, &secs, &usecs);
! msecs = secs * 1000 + usecs / 1000;
!
! if (!enable_timeout(STANDBY_TIMEOUT, msecs))
elog(FATAL, "could not set timer for process wakeup");
}
/* Wait to be signaled by UnpinBuffer() */
ProcWaitForSignal();
! if (!disable_all_timeouts(false))
! elog(FATAL, "could not disable timer for process wakeup");
}
void
diff -dcrpN postgresql/src/backend/storage/lmgr/Makefile postgresql.1/src/backend/storage/lmgr/Makefile
*** postgresql/src/backend/storage/lmgr/Makefile 2012-04-16 19:57:22.458915722 +0200
--- postgresql.1/src/backend/storage/lmgr/Makefile 2012-06-19 17:49:55.353479085 +0200
*************** subdir = src/backend/storage/lmgr
*** 12,18 ****
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
! OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o predicate.o
include $(top_srcdir)/src/backend/common.mk
--- 12,18 ----
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
! OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o predicate.o timeout.o
include $(top_srcdir)/src/backend/common.mk
diff -dcrpN postgresql/src/backend/storage/lmgr/proc.c postgresql.1/src/backend/storage/lmgr/proc.c
*** postgresql/src/backend/storage/lmgr/proc.c 2012-06-11 06:22:48.156921573 +0200
--- postgresql.1/src/backend/storage/lmgr/proc.c 2012-06-19 20:37:53.136249927 +0200
***************
*** 47,59 ****
#include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/spin.h"
#include "utils/timestamp.h"
/* GUC variables */
- int DeadlockTimeout = 1000;
- int StatementTimeout = 0;
bool log_lock_waits = false;
/* Pointer to this process's PGPROC and PGXACT structs, if any */
--- 47,59 ----
#include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
+ #include "storage/proctimeout.h"
#include "storage/spin.h"
+ #include "storage/timeout.h"
#include "utils/timestamp.h"
/* GUC variables */
bool log_lock_waits = false;
/* Pointer to this process's PGPROC and PGXACT structs, if any */
*************** PGPROC *PreparedXactProcs = NULL;
*** 77,103 ****
/* If we are waiting for a lock, this points to the associated LOCALLOCK */
static LOCALLOCK *lockAwaited = NULL;
! /* Mark these volatile because they can be changed by signal handler */
! static volatile bool standby_timeout_active = false;
! static volatile bool statement_timeout_active = false;
! static volatile bool deadlock_timeout_active = false;
! static volatile DeadLockState deadlock_state = DS_NOT_YET_CHECKED;
! volatile bool cancel_from_timeout = false;
!
! /* timeout_start_time is set when log_lock_waits is true */
! static TimestampTz timeout_start_time;
!
! /* statement_fin_time is valid only if statement_timeout_active is true */
! static TimestampTz statement_fin_time;
! static TimestampTz statement_fin_time2; /* valid only in recovery */
!
static void RemoveProcFromArray(int code, Datum arg);
static void ProcKill(int code, Datum arg);
static void AuxiliaryProcKill(int code, Datum arg);
- static bool CheckStatementTimeout(void);
- static bool CheckStandbyTimeout(void);
-
/*
* Report shared-memory space needed by InitProcGlobal.
--- 77,88 ----
/* If we are waiting for a lock, this points to the associated LOCALLOCK */
static LOCALLOCK *lockAwaited = NULL;
! /* Mark this volatile because it can be changed by signal handler */
! static volatile DeadLockState deadlock_state;
static void RemoveProcFromArray(int code, Datum arg);
static void ProcKill(int code, Datum arg);
static void AuxiliaryProcKill(int code, Datum arg);
/*
* Report shared-memory space needed by InitProcGlobal.
*************** LockErrorCleanup(void)
*** 654,660 ****
return;
/* Turn off the deadlock timer, if it's still running (see ProcSleep) */
! disable_sig_alarm(false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
--- 639,645 ----
return;
/* Turn off the deadlock timer, if it's still running (see ProcSleep) */
! disable_timeout(DEADLOCK_TIMEOUT, false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1049,1055 ****
* 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 (!enable_sig_alarm(DeadlockTimeout, false))
elog(FATAL, "could not set timer for process wakeup");
/*
--- 1034,1040 ----
* 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 (!enable_timeout(DEADLOCK_TIMEOUT, DeadlockTimeout))
elog(FATAL, "could not set timer for process wakeup");
/*
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1139,1145 ****
DescribeLockTag(&buf, &locallock->tag.lock);
modename = GetLockmodeName(locallock->tag.lock.locktag_lockmethodid,
lockmode);
! TimestampDifference(timeout_start_time, GetCurrentTimestamp(),
&secs, &usecs);
msecs = secs * 1000 + usecs / 1000;
usecs = usecs % 1000;
--- 1124,1131 ----
DescribeLockTag(&buf, &locallock->tag.lock);
modename = GetLockmodeName(locallock->tag.lock.locktag_lockmethodid,
lockmode);
! TimestampDifference(get_timeout_start(DEADLOCK_TIMEOUT),
! GetCurrentTimestamp(),
&secs, &usecs);
msecs = secs * 1000 + usecs / 1000;
usecs = usecs % 1000;
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1201,1207 ****
/*
* Disable the timer, if it's still running
*/
! if (!disable_sig_alarm(false))
elog(FATAL, "could not disable timer for process wakeup");
/*
--- 1187,1193 ----
/*
* Disable the timer, if it's still running
*/
! if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for process wakeup");
/*
*************** ProcLockWakeup(LockMethod lockMethodTabl
*** 1345,1351 ****
* NB: this is run inside a signal handler, so be very wary about what is done
* here or in called routines.
*/
! static void
CheckDeadLock(void)
{
int i;
--- 1331,1337 ----
* NB: this is run inside a signal handler, so be very wary about what is done
* here or in called routines.
*/
! void
CheckDeadLock(void)
{
int i;
*************** ProcSendSignal(int pid)
*** 1500,1817 ****
PGSemaphoreUnlock(&proc->sem);
}
-
- /*****************************************************************************
- * SIGALRM interrupt support
- *
- * Maybe these should be in pqsignal.c?
- *****************************************************************************/
-
- /*
- * Enable the SIGALRM interrupt to fire after the specified delay
- *
- * Delay is given in milliseconds. Caller should be sure a SIGALRM
- * signal handler is installed before this is called.
- *
- * This code properly handles nesting of deadlock timeout alarms within
- * statement timeout alarms.
- *
- * Returns TRUE if okay, FALSE on failure.
- */
- bool
- enable_sig_alarm(int delayms, bool is_statement_timeout)
- {
- TimestampTz fin_time;
- struct itimerval timeval;
-
- if (is_statement_timeout)
- {
- /*
- * Begin statement-level timeout
- *
- * Note that we compute statement_fin_time with reference to the
- * statement_timestamp, but apply the specified delay without any
- * correction; that is, we ignore whatever time has elapsed since
- * statement_timestamp was set. In the normal case only a small
- * interval will have elapsed and so this doesn't matter, but there
- * are corner cases (involving multi-statement query strings with
- * embedded COMMIT or ROLLBACK) where we might re-initialize the
- * statement timeout long after initial receipt of the message. In
- * such cases the enforcement of the statement timeout will be a bit
- * inconsistent. This annoyance is judged not worth the cost of
- * performing an additional gettimeofday() here.
- */
- Assert(!deadlock_timeout_active);
- fin_time = GetCurrentStatementStartTimestamp();
- fin_time = TimestampTzPlusMilliseconds(fin_time, delayms);
- statement_fin_time = fin_time;
- cancel_from_timeout = false;
- statement_timeout_active = true;
- }
- else if (statement_timeout_active)
- {
- /*
- * Begin deadlock timeout with statement-level timeout active
- *
- * Here, we want to interrupt at the closer of the two timeout times.
- * If fin_time >= statement_fin_time then we need not touch the
- * existing timer setting; else set up to interrupt at the deadlock
- * timeout time.
- *
- * NOTE: in this case it is possible that this routine will be
- * interrupted by the previously-set timer alarm. This is okay
- * because the signal handler will do only what it should do according
- * to the state variables. The deadlock checker may get run earlier
- * than normal, but that does no harm.
- */
- timeout_start_time = GetCurrentTimestamp();
- fin_time = TimestampTzPlusMilliseconds(timeout_start_time, delayms);
- deadlock_timeout_active = true;
- if (fin_time >= statement_fin_time)
- return true;
- }
- else
- {
- /* Begin deadlock timeout with no statement-level timeout */
- deadlock_timeout_active = true;
- /* GetCurrentTimestamp can be expensive, so only do it if we must */
- if (log_lock_waits)
- timeout_start_time = GetCurrentTimestamp();
- }
-
- /* If we reach here, okay to set the timer interrupt */
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = delayms / 1000;
- timeval.it_value.tv_usec = (delayms % 1000) * 1000;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- return true;
- }
-
- /*
- * Cancel the SIGALRM timer, either for a deadlock timeout or a statement
- * timeout. If a deadlock timeout is canceled, any active statement timeout
- * remains in force.
- *
- * Returns TRUE if okay, FALSE on failure.
- */
- bool
- disable_sig_alarm(bool is_statement_timeout)
- {
- /*
- * Always disable the interrupt if it is active; this avoids being
- * interrupted by the signal handler and thereby possibly getting
- * confused.
- *
- * We will re-enable the interrupt if necessary in CheckStatementTimeout.
- */
- if (statement_timeout_active || deadlock_timeout_active)
- {
- struct itimerval timeval;
-
- MemSet(&timeval, 0, sizeof(struct itimerval));
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- {
- statement_timeout_active = false;
- cancel_from_timeout = false;
- deadlock_timeout_active = false;
- return false;
- }
- }
-
- /* Always cancel deadlock timeout, in case this is error cleanup */
- deadlock_timeout_active = false;
-
- /* Cancel or reschedule statement timeout */
- if (is_statement_timeout)
- {
- statement_timeout_active = false;
- cancel_from_timeout = false;
- }
- else if (statement_timeout_active)
- {
- if (!CheckStatementTimeout())
- return false;
- }
- return true;
- }
-
-
/*
* Check for statement timeout. If the timeout time has come,
! * trigger a query-cancel interrupt; if not, reschedule the SIGALRM
! * interrupt to occur at the right time.
! *
! * Returns true if okay, false if failed to set the interrupt.
*/
! static bool
! CheckStatementTimeout(void)
{
- TimestampTz now;
-
- if (!statement_timeout_active)
- return true; /* do nothing if not active */
-
- now = GetCurrentTimestamp();
-
- if (now >= statement_fin_time)
- {
- /* Time to die */
- statement_timeout_active = false;
- cancel_from_timeout = true;
#ifdef HAVE_SETSID
! /* try to signal whole process group */
! kill(-MyProcPid, SIGINT);
#endif
! kill(MyProcPid, SIGINT);
! }
! else
! {
! /* Not time yet, so (re)schedule the interrupt */
! long secs;
! int usecs;
! struct itimerval timeval;
!
! TimestampDifference(now, statement_fin_time,
! &secs, &usecs);
!
! /*
! * It's possible that the difference is less than a microsecond;
! * ensure we don't cancel, rather than set, the interrupt.
! */
! if (secs == 0 && usecs == 0)
! usecs = 1;
! MemSet(&timeval, 0, sizeof(struct itimerval));
! timeval.it_value.tv_sec = secs;
! timeval.it_value.tv_usec = usecs;
! if (setitimer(ITIMER_REAL, &timeval, NULL))
! return false;
! }
!
! return true;
}
-
/*
! * Signal handler for SIGALRM for normal user backends
! *
! * Process deadlock check and/or statement timeout check, as needed.
! * To avoid various edge cases, we must be careful to do nothing
! * when there is nothing to be done. We also need to be able to
! * reschedule the timer interrupt if called before end of statement.
*/
void
! handle_sig_alarm(SIGNAL_ARGS)
! {
! int save_errno = errno;
!
! /* SIGALRM is cause for waking anything waiting on the process latch */
! if (MyProc)
! SetLatch(&MyProc->procLatch);
!
! if (deadlock_timeout_active)
! {
! deadlock_timeout_active = false;
! CheckDeadLock();
! }
!
! if (statement_timeout_active)
! (void) CheckStatementTimeout();
!
! errno = save_errno;
! }
!
! /*
! * Signal handler for SIGALRM in Startup process
! *
! * To avoid various edge cases, we must be careful to do nothing
! * when there is nothing to be done. We also need to be able to
! * reschedule the timer interrupt if called before end of statement.
! *
! * We set either deadlock_timeout_active or statement_timeout_active
! * or both. Interrupts are enabled if standby_timeout_active.
! */
! bool
! enable_standby_sig_alarm(TimestampTz now, TimestampTz fin_time, bool deadlock_only)
! {
! TimestampTz deadlock_time = TimestampTzPlusMilliseconds(now,
! DeadlockTimeout);
!
! if (deadlock_only)
! {
! /*
! * Wake up at deadlock_time only, then wait forever
! */
! statement_fin_time = deadlock_time;
! deadlock_timeout_active = true;
! statement_timeout_active = false;
! }
! else if (fin_time > deadlock_time)
! {
! /*
! * Wake up at deadlock_time, then again at fin_time
! */
! statement_fin_time = deadlock_time;
! statement_fin_time2 = fin_time;
! deadlock_timeout_active = true;
! statement_timeout_active = true;
! }
! else
! {
! /*
! * Wake only at fin_time because its fairly soon
! */
! statement_fin_time = fin_time;
! deadlock_timeout_active = false;
! statement_timeout_active = true;
! }
!
! if (deadlock_timeout_active || statement_timeout_active)
! {
! long secs;
! int usecs;
! struct itimerval timeval;
!
! TimestampDifference(now, statement_fin_time,
! &secs, &usecs);
! if (secs == 0 && usecs == 0)
! usecs = 1;
! MemSet(&timeval, 0, sizeof(struct itimerval));
! timeval.it_value.tv_sec = secs;
! timeval.it_value.tv_usec = usecs;
! if (setitimer(ITIMER_REAL, &timeval, NULL))
! return false;
! standby_timeout_active = true;
! }
!
! return true;
! }
!
! bool
! disable_standby_sig_alarm(void)
{
! /*
! * Always disable the interrupt if it is active; this avoids being
! * interrupted by the signal handler and thereby possibly getting
! * confused.
! *
! * We will re-enable the interrupt if necessary in CheckStandbyTimeout.
! */
! if (standby_timeout_active)
! {
! struct itimerval timeval;
!
! MemSet(&timeval, 0, sizeof(struct itimerval));
! if (setitimer(ITIMER_REAL, &timeval, NULL))
! {
! standby_timeout_active = false;
! return false;
! }
! }
!
! standby_timeout_active = false;
!
! return true;
}
/*
--- 1486,1514 ----
PGSemaphoreUnlock(&proc->sem);
}
/*
* Check for statement timeout. If the timeout time has come,
! * trigger a query-cancel interrupt.
*/
! void
! StatementTimeoutFunction(void)
{
#ifdef HAVE_SETSID
! /* try to signal whole process group */
! kill(-MyProcPid, SIGINT);
#endif
! kill(MyProcPid, SIGINT);
}
/*
! * CheckStandbyDeadLock() will trigger if the deadlock timeout
! * happens earlier than StandbyTimeout. If it triggers,
! * StandbyTimeout will still be rescheduled.
*/
void
! StandbyDeadLockFunction(void)
{
! SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
}
/*
*************** disable_standby_sig_alarm(void)
*** 1819,1899 ****
* SIGALRM handler. Timers will only be set when InHotStandby.
* We simply ignore any signals unless the timer has been set.
*/
- static bool
- CheckStandbyTimeout(void)
- {
- TimestampTz now;
- bool reschedule = false;
-
- standby_timeout_active = false;
-
- now = GetCurrentTimestamp();
-
- /*
- * Reschedule the timer if its not time to wake yet, or if we have both
- * timers set and the first one has just been reached.
- */
- if (now >= statement_fin_time)
- {
- if (deadlock_timeout_active)
- {
- /*
- * We're still waiting when we reach deadlock timeout, so send out
- * a request to have other backends check themselves for deadlock.
- * Then continue waiting until statement_fin_time, if that's set.
- */
- SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
- deadlock_timeout_active = false;
-
- /*
- * Begin second waiting period if required.
- */
- if (statement_timeout_active)
- {
- reschedule = true;
- statement_fin_time = statement_fin_time2;
- }
- }
- else
- {
- /*
- * We've now reached statement_fin_time, so ask all conflicts to
- * leave, so we can press ahead with applying changes in recovery.
- */
- SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
- }
- }
- else
- reschedule = true;
-
- if (reschedule)
- {
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- standby_timeout_active = true;
- }
-
- return true;
- }
-
void
! handle_standby_sig_alarm(SIGNAL_ARGS)
{
! int save_errno = errno;
!
! if (standby_timeout_active)
! (void) CheckStandbyTimeout();
!
! errno = save_errno;
}
--- 1516,1523 ----
* SIGALRM handler. Timers will only be set when InHotStandby.
* We simply ignore any signals unless the timer has been set.
*/
void
! StandbyTimeoutFunction(void)
{
! SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
}
diff -dcrpN postgresql/src/backend/storage/lmgr/timeout.c postgresql.1/src/backend/storage/lmgr/timeout.c
*** postgresql/src/backend/storage/lmgr/timeout.c 1970-01-01 01:00:00.000000000 +0100
--- postgresql.1/src/backend/storage/lmgr/timeout.c 2012-06-19 21:00:05.183188860 +0200
***************
*** 0 ****
--- 1,434 ----
+ /*-------------------------------------------------------------------------
+ *
+ * timeout.c
+ * routines to manage timeout sources handled by SIGALRM
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/storage/lmgr/timeout.c
+ *
+ *-------------------------------------------------------------------------
+ */
+ #include "postgres.h"
+
+ #include <sys/time.h>
+
+ #include "access/xact.h"
+ #include "storage/proc.h"
+ #include "storage/proctimeout.h"
+ #include "storage/timeout.h"
+ #include "utils/timestamp.h"
+
+ /* GUC variables */
+ int DeadlockTimeout = 1000;
+ int StatementTimeout = 0;
+
+ /*
+ * Infrastructure for timeouts
+ */
+
+ static void InitTimeout(TimeoutName tn, TimestampTz start_time, TimestampTz fin_time);
+ static void DestroyTimeout(TimeoutName tn, bool keep_indicator);
+
+ static void DummyTimeoutFunction(void);
+
+ typedef void (*timeout_func)(void);
+ typedef TimestampTz (*timeout_start)(void);
+
+ typedef struct {
+ TimeoutName index;
+ bool resched_next;
+
+ /* volatile because it may be changed from the signal handler */
+ volatile bool indicator;
+
+ timeout_func timeout_func;
+ timeout_start timeout_start;
+
+ TimestampTz start_time;
+ TimestampTz fin_time;
+ } timeout_params;
+
+ /*
+ * List of possible timeout reasons in the order of enum TimeoutName.
+ * The priority of timeouts (in case two of them would trigger at the
+ * same time) is determined by this order: the earlier one in the list
+ * has higher priority.
+ */
+ static timeout_params base_timeouts[TIMEOUT_MAX] = {
+ {
+ AUTHENTICATION_TIMEOUT, false, false,
+ DummyTimeoutFunction, GetCurrentTimestamp
+ },
+
+ {
+ DEADLOCK_TIMEOUT, true, false,
+ CheckDeadLock, GetCurrentTimestamp
+ },
+
+ {
+ STATEMENT_TIMEOUT, false, false,
+ StatementTimeoutFunction, GetCurrentStatementStartTimestamp
+ },
+
+ {
+ STANDBY_DEADLOCK_TIMEOUT, true, false,
+ StandbyDeadLockFunction, GetCurrentTimestamp
+ },
+
+ {
+ STANDBY_TIMEOUT, false, false,
+ StandbyTimeoutFunction, GetCurrentTimestamp
+ }
+ };
+
+ /*
+ * List of active timeouts ordered by their fin_time and priority.
+ */
+ static int n_timeouts = 0;
+ static timeout_params *timeouts[TIMEOUT_MAX];
+
+ /*****************************************************************************
+ * Internal helper functions
+ *****************************************************************************/
+
+ /*
+ * Find the index of a given timeout type in the active array
+ */
+ static int
+ find_active_timeout(TimeoutName tn)
+ {
+ int i;
+
+ for (i = 0; i < n_timeouts; i++)
+ {
+ if (timeouts[i]->index == tn)
+ return i;
+ }
+
+ return -1;
+ }
+
+ #define is_timeout_active(tn) (find_active_timeout(tn) >= 0)
+
+ /*
+ * Insert tn'th timeout into the list of timeouts at the given index
+ * if the previous timeout allows rescheduling the next one in the list.
+ */
+ static void
+ insert_timeout(TimeoutName tn, int index)
+ {
+ int i;
+
+ if (index > 0 && !timeouts[index-1]->resched_next)
+ return;
+
+ for (i = n_timeouts - 1; i >= index; i--)
+ timeouts[i+1] = timeouts[i];
+
+ timeouts[index] = &base_timeouts[tn];
+ n_timeouts++;
+ }
+
+ /*
+ * Remove the index'th element from the timeout list.
+ */
+ static void
+ remove_timeout_index(int index)
+ {
+ int i;
+
+ if (index < 0)
+ return;
+
+ for (i = index + 1; i < n_timeouts; i++)
+ timeouts[i-1] = timeouts[i];
+
+ if (n_timeouts > 0 && index < n_timeouts)
+ n_timeouts--;
+ }
+
+ /*
+ * (Re)schedule the next active timeout
+ */
+ static bool
+ schedule_timeout(TimestampTz now)
+ {
+ long secs;
+ int usecs;
+ struct itimerval timeval;
+
+ /* There is no active timeout, do nothing. */
+ if (n_timeouts == 0)
+ return true;
+
+ TimestampDifference(now, timeouts[0]->fin_time,
+ &secs, &usecs);
+
+ /*
+ * It's possible that the difference is less than a microsecond;
+ * ensure we don't cancel, rather than set, the interrupt.
+ */
+ if (secs == 0 && usecs == 0)
+ usecs = 1;
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ timeval.it_value.tv_sec = secs;
+ timeval.it_value.tv_usec = usecs;
+ if (setitimer(ITIMER_REAL, &timeval, NULL))
+ return false;
+ return true;
+ }
+
+ /*****************************************************************************
+ * Init, Destroy and callback functions for different timeouts.
+ *****************************************************************************/
+
+ /*
+ * Common Init and Destroy functions
+ */
+
+ static void
+ InitTimeout(TimeoutName tn, TimestampTz start_time, TimestampTz fin_time)
+ {
+ base_timeouts[tn].indicator = false;
+ base_timeouts[tn].start_time = start_time;
+ base_timeouts[tn].fin_time = fin_time;
+ }
+
+ static void
+ DestroyTimeout(TimeoutName tn, bool keep_indicator)
+ {
+ if (!keep_indicator)
+ base_timeouts[tn].indicator = false;
+ base_timeouts[tn].start_time = 0;
+ base_timeouts[tn].fin_time = 0;
+ }
+
+ /*
+ * CheckDummyTimeout
+ *
+ * Do nothing. Usable for timeout sources that don't need
+ * extra processing, like authentication_timeout.
+ * The timeout indicator is set by the signal handler.
+ */
+ static void
+ DummyTimeoutFunction(void)
+ {
+ return;
+ }
+
+ /*****************************************************************************
+ * Public API
+ *****************************************************************************/
+
+ /*
+ * Enable the SIGALRM interrupt to fire after the specified delay
+ *
+ * Delay is given in milliseconds. Caller should be sure a SIGALRM
+ * signal handler is installed before this is called.
+ *
+ * This code properly handles nesting of different timeout alarms.
+ *
+ * Returns TRUE if okay, FALSE on failure to set the timer.
+ */
+ bool
+ enable_timeout(TimeoutName tn, int delayms)
+ {
+ TimestampTz start_time;
+ TimestampTz fin_time;
+ int i;
+
+ Assert(!is_timeout_active(tn));
+
+ if (delayms <= 0)
+ return true;
+
+ start_time = base_timeouts[tn].timeout_start();
+ fin_time = TimestampTzPlusMilliseconds(start_time, delayms);
+
+ /* Find out the index where to insert the new timeout. */
+ for (i = 0; i < n_timeouts; i++)
+ {
+ /*
+ * The new timeout triggers earlier than
+ * a previously added one: insert here.
+ */
+ if (fin_time < timeouts[i]->fin_time)
+ break;
+ /*
+ * The new timeout triggers at the same time
+ * as the previously added one but has greater priority.
+ */
+ if (fin_time == timeouts[i]->fin_time && tn < timeouts[i]->index)
+ break;
+ }
+
+ /*
+ * Initialize the timeout parameters
+ */
+ InitTimeout(tn, start_time, fin_time);
+
+ insert_timeout(tn, i);
+
+ if (i > 0)
+ return true;
+
+ /* If we reach here, okay to set the timer interrupt */
+ if (!schedule_timeout(start_time))
+ return false;
+ return true;
+ }
+
+ /*
+ * Cancel the SIGALRM timer for the specific timeout.
+ * If a timeout is canceled, any other active timeout remains in force.
+ *
+ * Returns TRUE if okay, FALSE on failure to set the timer for
+ * the next timeout source.
+ */
+ bool
+ disable_timeout(TimeoutName tn, bool keep_indicator)
+ {
+ int i;
+
+ /*
+ * Always disable the interrupt if it is active; this avoids being
+ * interrupted by the signal handler and thereby possibly getting
+ * confused.
+ *
+ * We will re-enable the interrupt if necessary in ->check_timeout().
+ */
+ if (n_timeouts > 0)
+ {
+ struct itimerval timeval;
+
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ if (setitimer(ITIMER_REAL, &timeval, NULL))
+ {
+ n_timeouts = 0;
+ for (i = 0; i < TIMEOUT_MAX; i++)
+ DestroyTimeout(i, false);
+
+ return false;
+ }
+ }
+
+ /* Find the timeout and remove from the list. */
+ i = find_active_timeout(tn);
+ remove_timeout_index(i);
+
+ /* Do cleanup. */
+ DestroyTimeout(tn, keep_indicator);
+
+ /*
+ * If the first timeout was removed from the list and there is
+ * at least one active left, reschedule it.
+ */
+ if (i == 0 && n_timeouts > 0)
+ if (!schedule_timeout(GetCurrentTimestamp()))
+ return false;
+
+ return true;
+ }
+
+ /*
+ * Disable SIGALRM and remove all timeouts from the list and
+ * reset the timeout indicators.
+ */
+ bool
+ disable_all_timeouts(bool keep_indicators)
+ {
+ struct itimerval timeval;
+ int i;
+ bool ret;
+
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ ret = (setitimer(ITIMER_REAL, &timeval, NULL) == 0);
+
+ n_timeouts = 0;
+ for (i = 0; i < TIMEOUT_MAX; i++)
+ DestroyTimeout(i, keep_indicators);
+
+ return ret;
+ }
+
+ /*
+ * Return the timeout indicator
+ */
+ bool
+ get_timeout_indicator(TimeoutName tn)
+ {
+ return base_timeouts[tn].indicator;
+ }
+
+ /*
+ * Return the start of the timer for this timeout
+ */
+ TimestampTz
+ get_timeout_start(TimeoutName tn)
+ {
+ return base_timeouts[tn].start_time;
+ }
+
+ /*
+ * Signal handler for SIGALRM
+ *
+ * Process the check for the currently active timeout source and
+ * reschedule the next as needed. To avoid various edge cases,
+ * we must be careful to do nothing when there is nothing to be done.
+ */
+ void
+ handle_sig_alarm(SIGNAL_ARGS)
+ {
+ int save_errno = errno;
+
+ /*
+ * SIGALRM is cause for waking anything waiting on the process latch.
+ * Recovery (the startup process) doesn't have MyProc set so
+ * it can also use this signal handler.
+ */
+ if (MyProc)
+ SetLatch(&MyProc->procLatch);
+
+ if (n_timeouts > 0)
+ {
+ TimestampTz now;
+ bool reschedule;
+
+ now = GetCurrentTimestamp();
+
+ /* If our time has not come yet, do nothing. */
+ if (now < timeouts[0]->fin_time)
+ goto out;
+
+ /*
+ * Set the trigger indicator before calling the checker function.
+ * Setting if after may have side effects that lead to detecting
+ * a different event, like "user pressed Ctrl-C" is detected instead
+ * of the statement timeout having triggered.
+ */
+ timeouts[0]->indicator = true;
+
+ /* Call the timeout checker. */
+ timeouts[0]->timeout_func();
+
+ /*
+ * Remove the current timeout source and reschedule
+ * the next if needed. Short circuit disable_timeout(..., true)
+ * for the timeout source that just triggered.
+ */
+ reschedule = timeouts[0]->resched_next;
+ remove_timeout_index(0);
+
+ if (reschedule)
+ schedule_timeout(now);
+ else
+ disable_all_timeouts(true);
+ }
+
+ out:
+ errno = save_errno;
+ }
diff -dcrpN postgresql/src/backend/tcop/postgres.c postgresql.1/src/backend/tcop/postgres.c
*** postgresql/src/backend/tcop/postgres.c 2012-06-11 06:22:48.157921579 +0200
--- postgresql.1/src/backend/tcop/postgres.c 2012-06-19 17:49:55.358479113 +0200
***************
*** 64,69 ****
--- 64,70 ----
#include "storage/proc.h"
#include "storage/procsignal.h"
#include "storage/sinval.h"
+ #include "storage/timeout.h"
#include "tcop/fastpath.h"
#include "tcop/pquery.h"
#include "tcop/tcopprot.h"
*************** start_xact_command(void)
*** 2396,2404 ****
/* Set statement timeout running, if any */
/* NB: this mustn't be enabled until we are within an xact */
if (StatementTimeout > 0)
! enable_sig_alarm(StatementTimeout, true);
else
! cancel_from_timeout = false;
xact_started = true;
}
--- 2397,2405 ----
/* Set statement timeout running, if any */
/* NB: this mustn't be enabled until we are within an xact */
if (StatementTimeout > 0)
! enable_timeout(STATEMENT_TIMEOUT, StatementTimeout);
else
! disable_timeout(STATEMENT_TIMEOUT, false);
xact_started = true;
}
*************** finish_xact_command(void)
*** 2410,2416 ****
if (xact_started)
{
/* Cancel any active statement timeout before committing */
! disable_sig_alarm(true);
/* Now commit the command */
ereport(DEBUG3,
--- 2411,2417 ----
if (xact_started)
{
/* Cancel any active statement timeout before committing */
! disable_all_timeouts(false);
/* Now commit the command */
ereport(DEBUG3,
*************** ProcessInterrupts(void)
*** 2891,2897 ****
(errcode(ERRCODE_QUERY_CANCELED),
errmsg("canceling authentication due to timeout")));
}
! if (cancel_from_timeout)
{
ImmediateInterruptOK = false; /* not idle anymore */
DisableNotifyInterrupt();
--- 2892,2898 ----
(errcode(ERRCODE_QUERY_CANCELED),
errmsg("canceling authentication due to timeout")));
}
! if (get_timeout_indicator(STATEMENT_TIMEOUT))
{
ImmediateInterruptOK = false; /* not idle anymore */
DisableNotifyInterrupt();
*************** PostgresMain(int argc, char *argv[], con
*** 3799,3808 ****
/*
* Forget any pending QueryCancel request, since we're returning to
! * the idle loop anyway, and cancel the statement timer if running.
*/
QueryCancelPending = false;
! disable_sig_alarm(true);
QueryCancelPending = false; /* again in case timeout occurred */
/*
--- 3800,3809 ----
/*
* Forget any pending QueryCancel request, since we're returning to
! * the idle loop anyway, and cancel the timer if running.
*/
QueryCancelPending = false;
! disable_all_timeouts(false);
QueryCancelPending = false; /* again in case timeout occurred */
/*
diff -dcrpN postgresql/src/backend/utils/init/postinit.c postgresql.1/src/backend/utils/init/postinit.c
*** postgresql/src/backend/utils/init/postinit.c 2012-04-16 19:57:22.490916093 +0200
--- postgresql.1/src/backend/utils/init/postinit.c 2012-06-19 17:49:55.360479125 +0200
***************
*** 41,51 ****
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lmgr.h"
- #include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "storage/smgr.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
#include "utils/fmgroids.h"
--- 41,51 ----
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lmgr.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "storage/smgr.h"
+ #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
#include "utils/fmgroids.h"
*************** PerformAuthentication(Port *port)
*** 204,210 ****
* during authentication. Since we're inside a transaction and might do
* database access, we have to use the statement_timeout infrastructure.
*/
! if (!enable_sig_alarm(AuthenticationTimeout * 1000, true))
elog(FATAL, "could not set timer for authorization timeout");
/*
--- 204,210 ----
* during authentication. Since we're inside a transaction and might do
* database access, we have to use the statement_timeout infrastructure.
*/
! if (!enable_timeout(STATEMENT_TIMEOUT, AuthenticationTimeout * 1000))
elog(FATAL, "could not set timer for authorization timeout");
/*
*************** PerformAuthentication(Port *port)
*** 215,221 ****
/*
* Done with authentication. Disable the timeout, and log if needed.
*/
! if (!disable_sig_alarm(true))
elog(FATAL, "could not disable timer for authorization timeout");
if (Log_connections)
--- 215,221 ----
/*
* Done with authentication. Disable the timeout, and log if needed.
*/
! if (!disable_timeout(STATEMENT_TIMEOUT, false))
elog(FATAL, "could not disable timer for authorization timeout");
if (Log_connections)
diff -dcrpN postgresql/src/backend/utils/misc/guc.c postgresql.1/src/backend/utils/misc/guc.c
*** postgresql/src/backend/utils/misc/guc.c 2012-06-11 06:22:48.193921753 +0200
--- postgresql.1/src/backend/utils/misc/guc.c 2012-06-19 20:23:28.364954147 +0200
***************
*** 63,68 ****
--- 63,69 ----
#include "storage/standby.h"
#include "storage/fd.h"
#include "storage/predicate.h"
+ #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "tsearch/ts_cache.h"
#include "utils/builtins.h"
diff -dcrpN postgresql/src/include/storage/proc.h postgresql.1/src/include/storage/proc.h
*** postgresql/src/include/storage/proc.h 2012-06-11 06:22:48.223921899 +0200
--- postgresql.1/src/include/storage/proc.h 2012-06-19 17:49:55.365479152 +0200
*************** extern PGPROC *PreparedXactProcs;
*** 218,230 ****
/* configurable options */
- extern int DeadlockTimeout;
- extern int StatementTimeout;
extern bool log_lock_waits;
- extern volatile bool cancel_from_timeout;
-
-
/*
* Function Prototypes
*/
--- 218,225 ----
*************** extern void LockErrorCleanup(void);
*** 252,264 ****
extern void ProcWaitForSignal(void);
extern void ProcSendSignal(int pid);
- extern bool enable_sig_alarm(int delayms, bool is_statement_timeout);
- extern bool disable_sig_alarm(bool is_statement_timeout);
- extern void handle_sig_alarm(SIGNAL_ARGS);
-
- extern bool enable_standby_sig_alarm(TimestampTz now,
- TimestampTz fin_time, bool deadlock_only);
- extern bool disable_standby_sig_alarm(void);
- extern void handle_standby_sig_alarm(SIGNAL_ARGS);
-
#endif /* PROC_H */
--- 247,250 ----
diff -dcrpN postgresql/src/include/storage/proctimeout.h postgresql.1/src/include/storage/proctimeout.h
*** postgresql/src/include/storage/proctimeout.h 1970-01-01 01:00:00.000000000 +0100
--- postgresql.1/src/include/storage/proctimeout.h 2012-06-19 20:37:02.399992841 +0200
***************
*** 0 ****
--- 1,22 ----
+ /*-------------------------------------------------------------------------
+ *
+ * proctimeout.h
+ * declaration of timeout checker functions
+ *
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/storage/proctimeout.h
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef _PROCTIMEOUT_H_
+ #define _PROCTIMEOUT_H_
+
+ extern void CheckDeadLock(void);
+ extern void StatementTimeoutFunction(void);
+ extern void StandbyDeadLockFunction(void);
+ extern void StandbyTimeoutFunction(void);
+
+ #endif /* _PROCTIMEOUT_H_ */
diff -dcrpN postgresql/src/include/storage/timeout.h postgresql.1/src/include/storage/timeout.h
*** postgresql/src/include/storage/timeout.h 1970-01-01 01:00:00.000000000 +0100
--- postgresql.1/src/include/storage/timeout.h 2012-06-19 20:33:16.942858484 +0200
***************
*** 0 ****
--- 1,39 ----
+ /*-------------------------------------------------------------------------
+ *
+ * timeout.h
+ * SIGALRM timeout API
+ *
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/storage/timeout.h
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef _TIMEOUT_H_
+ #define _TIMEOUT_H_
+
+ #include "datatype/timestamp.h"
+
+ /* configurable options */
+ extern int DeadlockTimeout;
+ extern int StatementTimeout;
+
+ typedef enum TimeoutName {
+ AUTHENTICATION_TIMEOUT,
+ DEADLOCK_TIMEOUT,
+ STATEMENT_TIMEOUT,
+ STANDBY_DEADLOCK_TIMEOUT,
+ STANDBY_TIMEOUT,
+ TIMEOUT_MAX
+ } TimeoutName;
+
+ extern bool enable_timeout(TimeoutName tn, int delayms);
+ extern bool disable_timeout(TimeoutName tn, bool keep_indicator);
+ extern bool disable_all_timeouts(bool keep_indicators);
+ extern bool get_timeout_indicator(TimeoutName tn);
+ extern TimestampTz get_timeout_start(TimeoutName tn);
+ extern void handle_sig_alarm(SIGNAL_ARGS);
+
+ #endif /* _TIMEOUT_H_ */
2-lock_timeout-v8.patchtext/x-patch; name=2-lock_timeout-v8.patchDownload
diff -dcrpN postgresql.1/doc/src/sgml/config.sgml postgresql.2/doc/src/sgml/config.sgml
*** postgresql.1/doc/src/sgml/config.sgml 2012-05-11 09:23:17.030668613 +0200
--- postgresql.2/doc/src/sgml/config.sgml 2012-06-19 20:44:12.545192522 +0200
*************** COPY postgres_log FROM '/full/path/to/lo
*** 4980,4986 ****
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.
</para>
<para>
--- 4980,4989 ----
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. 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>
*************** COPY postgres_log FROM '/full/path/to/lo
*** 4991,4996 ****
--- 4994,5053 ----
</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 -dcrpN 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-06-19 20:44:12.546192527 +0200
*************** LOCK [ TABLE ] [ ONLY ] <replaceable cla
*** 39,46 ****
<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.)
</para>
--- 39,49 ----
<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. 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 -dcrpN 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-06-19 20:44:12.548192537 +0200
*************** FOR SHARE [ OF <replaceable class="param
*** 1199,1204 ****
--- 1199,1212 ----
</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 -dcrpN postgresql.1/src/backend/port/posix_sema.c postgresql.2/src/backend/port/posix_sema.c
*** postgresql.1/src/backend/port/posix_sema.c 2012-04-16 19:57:22.438915489 +0200
--- postgresql.2/src/backend/port/posix_sema.c 2012-06-19 20:44:12.548192537 +0200
***************
*** 24,29 ****
--- 24,30 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/timeout.h"
#ifdef USE_NAMED_POSIX_SEMAPHORES
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 313,315 ****
--- 314,341 ----
return true;
}
+
+ /*
+ * PGSemaphoreTimedLock
+ *
+ * Lock a semaphore (decrement count), blocking if count would be < 0
+ * Return if lock_timeout expired
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ int errStatus;
+
+ do
+ {
+ ImmediateInterruptOK = interruptOK;
+ CHECK_FOR_INTERRUPTS();
+ errStatus = sem_wait(PG_SEM_REF(sema));
+ ImmediateInterruptOK = false;
+ } while (errStatus < 0 && errno == EINTR && !get_timeout_indicator(LOCK_TIMEOUT));
+
+ if (get_timeout_indicator(LOCK_TIMEOUT))
+ return;
+ if (errStatus < 0)
+ elog(FATAL, "sem_wait failed: %m");
+ }
diff -dcrpN postgresql.1/src/backend/port/sysv_sema.c postgresql.2/src/backend/port/sysv_sema.c
*** postgresql.1/src/backend/port/sysv_sema.c 2012-05-14 08:20:56.284830580 +0200
--- postgresql.2/src/backend/port/sysv_sema.c 2012-06-19 20:44:12.549192542 +0200
***************
*** 27,32 ****
--- 27,33 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/timeout.h"
#ifndef HAVE_UNION_SEMUN
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 492,494 ****
--- 493,525 ----
return true;
}
+
+ /*
+ * PGSemaphoreTimedLock
+ *
+ * Lock a semaphore (decrement count), blocking if count would be < 0
+ * Return if lock_timeout expired
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ int errStatus;
+ 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;
+ } while (errStatus < 0 && errno == EINTR && !get_timeout_indicator(LOCK_TIMEOUT));
+
+ if (get_timeout_indicator(LOCK_TIMEOUT))
+ return;
+ if (errStatus < 0)
+ elog(FATAL, "semop(id=%d) failed: %m", sema->semId);
+ }
diff -dcrpN postgresql.1/src/backend/port/win32_sema.c postgresql.2/src/backend/port/win32_sema.c
*** postgresql.1/src/backend/port/win32_sema.c 2012-06-11 06:22:48.137921483 +0200
--- postgresql.2/src/backend/port/win32_sema.c 2012-06-19 20:44:12.549192542 +0200
***************
*** 16,21 ****
--- 16,22 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/timeout.h"
static HANDLE *mySemSet; /* IDs of sema sets acquired so far */
static int numSems; /* number of sema sets acquired so far */
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 209,211 ****
--- 210,271 ----
/* 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.
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ DWORD ret;
+ HANDLE wh[2];
+
+ /*
+ * 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;
+ } while (errno == EINTR && !get_timeout_indicator(LOCK_TIMEOUT));
+
+ if (get_timeout_indicator(LOCK_TIMEOUT))
+ return;
+ if (errno != 0)
+ ereport(FATAL,
+ (errmsg("could not lock semaphore: error code %d", (int) GetLastError())));
+ }
diff -dcrpN postgresql.1/src/backend/storage/lmgr/lmgr.c postgresql.2/src/backend/storage/lmgr/lmgr.c
*** postgresql.1/src/backend/storage/lmgr/lmgr.c 2012-04-16 19:57:22.459915733 +0200
--- postgresql.2/src/backend/storage/lmgr/lmgr.c 2012-06-19 20:44:12.549192542 +0200
***************
*** 19,26 ****
--- 19,29 ----
#include "access/transam.h"
#include "access/xact.h"
#include "catalog/catalog.h"
+ #include "catalog/pg_database.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
+ #include "utils/lsyscache.h"
+ #include "storage/proc.h"
#include "storage/procarray.h"
#include "utils/inval.h"
*************** LockRelationOid(Oid relid, LOCKMODE lock
*** 78,83 ****
--- 81,101 ----
res = LockAcquire(&tag, lockmode, false, false);
+ if (res == LOCKACQUIRE_NOT_AVAIL)
+ {
+ char *relname = get_rel_name(relid);
+ if (relname)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ relname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation with OID %u",
+ relid)));
+ }
+
/*
* Now that we have the lock, check for invalidation messages, so that we
* will update or flush any stale relcache entry before we try to use it.
*************** LockRelation(Relation relation, LOCKMODE
*** 174,179 ****
--- 192,203 ----
res = LockAcquire(&tag, lockmode, false, false);
+ if (res == LOCKACQUIRE_NOT_AVAIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ RelationGetRelationName(relation))));
+
/*
* Now that we have the lock, check for invalidation messages; see notes
* in LockRelationOid.
*************** LockRelationIdForSession(LockRelId *reli
*** 251,257 ****
SET_LOCKTAG_RELATION(tag, relid->dbId, relid->relId);
! (void) LockAcquire(&tag, lockmode, true, false);
}
/*
--- 275,294 ----
SET_LOCKTAG_RELATION(tag, relid->dbId, relid->relId);
! if (LockAcquire(&tag, lockmode, true, false) == LOCKACQUIRE_NOT_AVAIL)
! {
! char *relname = get_rel_name(relid->relId);
! if (relname)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on relation \"%s\"",
! relname)));
! else
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on relation with OID %u",
! relid->relId)));
! }
}
/*
*************** LockRelationForExtension(Relation relati
*** 286,292 ****
relation->rd_lockInfo.lockRelId.dbId,
relation->rd_lockInfo.lockRelId.relId);
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 323,333 ----
relation->rd_lockInfo.lockRelId.dbId,
relation->rd_lockInfo.lockRelId.relId);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on index \"%s\"",
! RelationGetRelationName(relation))));
}
/*
*************** LockPage(Relation relation, BlockNumber
*** 320,326 ****
relation->rd_lockInfo.lockRelId.relId,
blkno);
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 361,371 ----
relation->rd_lockInfo.lockRelId.relId,
blkno);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on page %u of relation \"%s\"",
! blkno, RelationGetRelationName(relation))));
}
/*
*************** LockTuple(Relation relation, ItemPointer
*** 376,382 ****
ItemPointerGetBlockNumber(tid),
ItemPointerGetOffsetNumber(tid));
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 421,431 ----
ItemPointerGetBlockNumber(tid),
ItemPointerGetOffsetNumber(tid));
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on row in relation \"%s\"",
! RelationGetRelationName(relation))));
}
/*
*************** XactLockTableInsert(TransactionId xid)
*** 430,436 ****
SET_LOCKTAG_TRANSACTION(tag, xid);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
}
/*
--- 479,488 ----
SET_LOCKTAG_TRANSACTION(tag, xid);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on transaction with ID %u", xid)));
}
/*
*************** XactLockTableWait(TransactionId xid)
*** 474,480 ****
SET_LOCKTAG_TRANSACTION(tag, xid);
! (void) LockAcquire(&tag, ShareLock, false, false);
LockRelease(&tag, ShareLock, false);
--- 526,535 ----
SET_LOCKTAG_TRANSACTION(tag, xid);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on transaction with ID %u", xid)));
LockRelease(&tag, ShareLock, false);
*************** LockDatabaseObject(Oid classid, Oid obji
*** 535,541 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, false, false);
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
--- 590,600 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
*************** LockSharedObject(Oid classid, Oid objid,
*** 576,582 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, false, false);
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
--- 635,645 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
*************** LockSharedObjectForSession(Oid classid,
*** 618,624 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, true, false);
}
/*
--- 681,702 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, true, false) == LOCKACQUIRE_NOT_AVAIL)
! switch(classid)
! {
! case DatabaseRelationId:
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on database with ID %u",
! objid)));
! break;
! default:
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
! break;
! }
}
/*
diff -dcrpN postgresql.1/src/backend/storage/lmgr/lock.c postgresql.2/src/backend/storage/lmgr/lock.c
*** postgresql.1/src/backend/storage/lmgr/lock.c 2012-06-11 06:22:48.154921564 +0200
--- postgresql.2/src/backend/storage/lmgr/lock.c 2012-06-19 20:44:12.550192547 +0200
*************** static PROCLOCK *SetupLockInTable(LockMe
*** 343,349 ****
static void GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner);
static void BeginStrongLockAcquire(LOCALLOCK *locallock, uint32 fasthashcode);
static void FinishStrongLockAcquire(void);
! static void WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner);
static void ReleaseLockIfHeld(LOCALLOCK *locallock, bool sessionLock);
static bool UnGrantLock(LOCK *lock, LOCKMODE lockmode,
PROCLOCK *proclock, LockMethod lockMethodTable);
--- 343,349 ----
static void GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner);
static void BeginStrongLockAcquire(LOCALLOCK *locallock, uint32 fasthashcode);
static void FinishStrongLockAcquire(void);
! static int WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner);
static void ReleaseLockIfHeld(LOCALLOCK *locallock, bool sessionLock);
static bool UnGrantLock(LOCK *lock, LOCKMODE lockmode,
PROCLOCK *proclock, LockMethod lockMethodTable);
*************** ProcLockHashCode(const PROCLOCKTAG *proc
*** 549,555 ****
* dontWait: if true, don't wait to acquire lock
*
* Returns one of:
! * LOCKACQUIRE_NOT_AVAIL lock not available, and dontWait=true
* LOCKACQUIRE_OK lock successfully acquired
* LOCKACQUIRE_ALREADY_HELD incremented count for lock already held
*
--- 549,555 ----
* dontWait: if true, don't wait to acquire lock
*
* Returns one of:
! * LOCKACQUIRE_NOT_AVAIL lock not available, either dontWait=true or timeout
* LOCKACQUIRE_OK lock successfully acquired
* LOCKACQUIRE_ALREADY_HELD incremented count for lock already held
*
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 863,869 ****
locktag->locktag_type,
lockmode);
! WaitOnLock(locallock, owner);
TRACE_POSTGRESQL_LOCK_WAIT_DONE(locktag->locktag_field1,
locktag->locktag_field2,
--- 863,869 ----
locktag->locktag_type,
lockmode);
! status = WaitOnLock(locallock, owner);
TRACE_POSTGRESQL_LOCK_WAIT_DONE(locktag->locktag_field1,
locktag->locktag_field2,
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 878,905 ****
* done when the lock was granted to us --- see notes in WaitOnLock.
*/
! /*
! * Check the proclock entry status, in case something in the ipc
! * communication doesn't work correctly.
! */
! if (!(proclock->holdMask & LOCKBIT_ON(lockmode)))
{
! AbortStrongLockAcquire();
! PROCLOCK_PRINT("LockAcquire: INCONSISTENT", proclock);
! LOCK_PRINT("LockAcquire: INCONSISTENT", lock, lockmode);
! /* Should we retry ? */
! LWLockRelease(partitionLock);
! elog(ERROR, "LockAcquire failed");
}
- PROCLOCK_PRINT("LockAcquire: granted", proclock);
- LOCK_PRINT("LockAcquire: granted", lock, lockmode);
}
! /*
! * Lock state is fully up-to-date now; if we error out after this, no
! * special error cleanup is required.
! */
! FinishStrongLockAcquire();
LWLockRelease(partitionLock);
--- 878,928 ----
* done when the lock was granted to us --- see notes in WaitOnLock.
*/
! switch (status)
{
! case STATUS_OK:
! /*
! * Check the proclock entry status, in case something in the ipc
! * communication doesn't work correctly.
! */
! if (!(proclock->holdMask & LOCKBIT_ON(lockmode)))
! {
! AbortStrongLockAcquire();
! PROCLOCK_PRINT("LockAcquire: INCONSISTENT", proclock);
! LOCK_PRINT("LockAcquire: INCONSISTENT", lock, lockmode);
! /* Should we retry ? */
! LWLockRelease(partitionLock);
! elog(ERROR, "LockAcquire failed");
! }
! PROCLOCK_PRINT("LockAcquire: granted", proclock);
! LOCK_PRINT("LockAcquire: granted", lock, lockmode);
! break;
! case STATUS_WAITING:
! PROCLOCK_PRINT("LockAcquire: timed out", proclock);
! LOCK_PRINT("LockAcquire: timed out", lock, lockmode);
! break;
! default:
! elog(ERROR, "LockAcquire invalid status");
! break;
}
}
! if (status == STATUS_WAITING)
! {
! /*
! * lock_timeout was set and WaitOnLock() indicated
! * we timed out. Clean up manually.
! */
! AbortStrongLockAcquire();
! }
! else
! {
! /*
! * Lock state is fully up-to-date now; if we error out after this, no
! * special error cleanup is required.
! */
! FinishStrongLockAcquire();
! }
LWLockRelease(partitionLock);
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 918,924 ****
locktag->locktag_field2);
}
! return LOCKACQUIRE_OK;
}
/*
--- 941,947 ----
locktag->locktag_field2);
}
! return (status == STATUS_OK ? LOCKACQUIRE_OK : LOCKACQUIRE_NOT_AVAIL);
}
/*
*************** GrantAwaitedLock(void)
*** 1437,1450 ****
* Caller must have set MyProc->heldLocks to reflect locks already held
* on the lockable object by this process.
*
* The appropriate partition lock must be held at entry.
*/
! static void
WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner)
{
LOCKMETHODID lockmethodid = LOCALLOCK_LOCKMETHOD(*locallock);
LockMethod lockMethodTable = LockMethods[lockmethodid];
char *volatile new_status = NULL;
LOCK_PRINT("WaitOnLock: sleeping on lock",
locallock->lock, locallock->tag.mode);
--- 1460,1479 ----
* Caller must have set MyProc->heldLocks to reflect locks already held
* on the lockable object by this process.
*
+ * Result: returns value of ProcSleep()
+ * STATUS_OK if we acquired the lock
+ * STATUS_ERROR if not (deadlock)
+ * STATUS_WAITING if not (timeout)
+ *
* The appropriate partition lock must be held at entry.
*/
! static int
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);
*************** WaitOnLock(LOCALLOCK *locallock, Resourc
*** 1486,1493 ****
*/
PG_TRY();
{
! if (ProcSleep(locallock, lockMethodTable) != STATUS_OK)
{
/*
* We failed as a result of a deadlock, see CheckDeadLock(). Quit
* now.
--- 1515,1527 ----
*/
PG_TRY();
{
! wait_status = ProcSleep(locallock, lockMethodTable);
! switch (wait_status)
{
+ case STATUS_OK:
+ case STATUS_WAITING:
+ break;
+ default:
/*
* We failed as a result of a deadlock, see CheckDeadLock(). Quit
* now.
*************** WaitOnLock(LOCALLOCK *locallock, Resourc
*** 1532,1539 ****
pfree(new_status);
}
! LOCK_PRINT("WaitOnLock: wakeup on lock",
locallock->lock, locallock->tag.mode);
}
/*
--- 1566,1579 ----
pfree(new_status);
}
! 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);
+
+ return wait_status;
}
/*
*************** VirtualXactLock(VirtualTransactionId vxi
*** 3853,3859 ****
LWLockRelease(proc->backendLock);
/* Time to wait. */
! (void) LockAcquire(&tag, ShareLock, false, false);
LockRelease(&tag, ShareLock, false);
return true;
--- 3893,3903 ----
LWLockRelease(proc->backendLock);
/* Time to wait. */
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on virtual transaction with ID %u",
! vxid.localTransactionId)));
LockRelease(&tag, ShareLock, false);
return true;
diff -dcrpN 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-06-19 20:37:53.136249927 +0200
--- postgresql.2/src/backend/storage/lmgr/proc.c 2012-06-19 20:44:12.551192552 +0200
*************** LockErrorCleanup(void)
*** 638,645 ****
if (lockAwaited == NULL)
return;
! /* Turn off the deadlock timer, if it's still running (see ProcSleep) */
disable_timeout(DEADLOCK_TIMEOUT, false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
--- 638,649 ----
if (lockAwaited == NULL)
return;
! /*
! * Turn off the deadlock and lock timeout timers,
! * if they are still running (see ProcSleep)
! */
disable_timeout(DEADLOCK_TIMEOUT, false);
+ disable_timeout(LOCK_TIMEOUT, false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
*************** ProcQueueInit(PROC_QUEUE *queue)
*** 875,881 ****
* 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).
*
* ASSUME: that no one will fiddle with the queue until after
* we release the partition lock.
--- 879,888 ----
* 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)
! * STATUS_WAITING if not (timeout)
*
* ASSUME: that no one will fiddle with the queue until after
* we release the partition lock.
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 897,902 ****
--- 904,910 ----
LOCKMASK myHeldLocks = MyProc->heldLocks;
bool early_deadlock = false;
bool allow_autovacuum_cancel = true;
+ bool timeout_detected = false;
int myWaitStatus;
PGPROC *proc;
int i;
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1038,1045 ****
elog(FATAL, "could not set timer for process wakeup");
/*
! * 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
* saved wakeup might be leftover from a previous operation (for example,
* we aborted ProcWaitForSignal just before someone did ProcSendSignal).
--- 1046,1059 ----
elog(FATAL, "could not set timer for process wakeup");
/*
! * Queue the timer for lock timeout, too.
! */
! if (!enable_timeout(LOCK_TIMEOUT, LockTimeout))
! elog(FATAL, "could not set timer for process wakeup");
!
! /*
! * 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).
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1056,1062 ****
*/
do
{
! PGSemaphoreLock(&MyProc->sem, true);
/*
* waitStatus could change from STATUS_WAITING to something else
--- 1070,1081 ----
*/
do
{
! PGSemaphoreTimedLock(&MyProc->sem, true);
!
! /* Check and keep the lock timeout indicator for later checks */
! timeout_detected = get_timeout_indicator(LOCK_TIMEOUT);
! if (timeout_detected)
! break;
/*
* waitStatus could change from STATUS_WAITING to something else
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1185,1196 ****
} while (myWaitStatus == STATUS_WAITING);
/*
! * Disable the timer, if it's still running
*/
if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for process wakeup");
/*
* Re-acquire the lock table's partition lock. We have to do this to hold
* off cancel/die interrupts before we can mess with lockAwaited (else we
* might have a missed or duplicated locallock update).
--- 1204,1221 ----
} while (myWaitStatus == STATUS_WAITING);
/*
! * Disable the deadlock timer, if it's still running
*/
if (!disable_timeout(DEADLOCK_TIMEOUT, false))
elog(FATAL, "could not disable timer for process wakeup");
/*
+ * Disable the lock timeout timer, if it's still running
+ */
+ if (!disable_timeout(LOCK_TIMEOUT, false))
+ elog(FATAL, "could not disable timer for process wakeup");
+
+ /*
* Re-acquire the lock table's partition lock. We have to do this to hold
* off cancel/die interrupts before we can mess with lockAwaited (else we
* might have a missed or duplicated locallock update).
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1198,1203 ****
--- 1223,1237 ----
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;
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1211,1218 ****
/*
* We don't have to do anything else, because the awaker did all the
* necessary update of the lock table and MyProc.
*/
! return MyProc->waitStatus;
}
--- 1245,1254 ----
/*
* 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 (timeout_detected ? STATUS_WAITING : MyProc->waitStatus);
}
diff -dcrpN postgresql.1/src/backend/storage/lmgr/timeout.c postgresql.2/src/backend/storage/lmgr/timeout.c
*** postgresql.1/src/backend/storage/lmgr/timeout.c 2012-06-19 21:00:05.183188860 +0200
--- postgresql.2/src/backend/storage/lmgr/timeout.c 2012-06-19 20:59:59.319160408 +0200
***************
*** 20,30 ****
--- 20,34 ----
#include "storage/proc.h"
#include "storage/proctimeout.h"
#include "storage/timeout.h"
+ #include "utils/guc.h"
#include "utils/timestamp.h"
/* GUC variables */
int DeadlockTimeout = 1000;
int StatementTimeout = 0;
+ int LockTimeout = 0;
+
+ int LockTimeoutOption = LOCK_TIMEOUT_PER_LOCK;
/*
* Infrastructure for timeouts
*************** static void InitTimeout(TimeoutName tn,
*** 34,39 ****
--- 38,44 ----
static void DestroyTimeout(TimeoutName tn, bool keep_indicator);
static void DummyTimeoutFunction(void);
+ static TimestampTz GetLockTimeoutStart(void);
typedef void (*timeout_func)(void);
typedef TimestampTz (*timeout_start)(void);
*************** static timeout_params base_timeouts[TIME
*** 70,75 ****
--- 75,86 ----
},
{
+ LOCK_TIMEOUT, false, false,
+ DummyTimeoutFunction, GetLockTimeoutStart,
+ 0
+ },
+
+ {
STATEMENT_TIMEOUT, false, false,
StatementTimeoutFunction, GetCurrentStatementStartTimestamp
},
*************** DestroyTimeout(TimeoutName tn, bool keep
*** 211,217 ****
* CheckDummyTimeout
*
* Do nothing. Usable for timeout sources that don't need
! * extra processing, like authentication_timeout.
* The timeout indicator is set by the signal handler.
*/
static void
--- 222,228 ----
* CheckDummyTimeout
*
* Do nothing. Usable for timeout sources that don't need
! * extra processing, like authentication_timeout or lock_timeout.
* The timeout indicator is set by the signal handler.
*/
static void
*************** DummyTimeoutFunction(void)
*** 220,225 ****
--- 231,260 ----
return;
}
+ /*
+ * GetLockTimeoutStart
+ *
+ * Depending on lock_timeout_option, return either the current time
+ * ('per_lock') or the statement start time ('per_statement').
+ */
+ static TimestampTz
+ GetLockTimeoutStart(void)
+ {
+ switch (LockTimeoutOption)
+ {
+ case LOCK_TIMEOUT_PER_LOCK:
+ return GetCurrentTimestamp();
+ case LOCK_TIMEOUT_PER_STMT:
+ return GetCurrentStatementStartTimestamp();
+ default:
+ elog(ERROR, "unhandled lock_timeout_option value: \"%s\"",
+ GetConfigOptionByName("lock_timeout_option", NULL));
+ break;
+ }
+ /* Silence possible warnings. */
+ return 0;
+ }
+
/*****************************************************************************
* Public API
*****************************************************************************/
diff -dcrpN postgresql.1/src/backend/utils/adt/lockfuncs.c postgresql.2/src/backend/utils/adt/lockfuncs.c
*** postgresql.1/src/backend/utils/adt/lockfuncs.c 2012-06-11 06:22:48.162921604 +0200
--- postgresql.2/src/backend/utils/adt/lockfuncs.c 2012-06-19 20:44:12.553192562 +0200
*************** pg_advisory_lock_int8(PG_FUNCTION_ARGS)
*** 421,427 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ExclusiveLock, true, false);
PG_RETURN_VOID();
}
--- 421,431 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ExclusiveLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_int8(PG_FUNCTION_A
*** 438,444 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
PG_RETURN_VOID();
}
--- 442,452 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_shared_int8(PG_FUNCTION
*** 454,460 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ShareLock, true, false);
PG_RETURN_VOID();
}
--- 462,472 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ShareLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_shared_int8(PG_FUN
*** 471,477 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ShareLock, false, false);
PG_RETURN_VOID();
}
--- 483,493 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_int4(PG_FUNCTION_ARGS)
*** 604,610 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ExclusiveLock, true, false);
PG_RETURN_VOID();
}
--- 620,630 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ExclusiveLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_int4(PG_FUNCTION_A
*** 622,628 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
PG_RETURN_VOID();
}
--- 642,652 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_shared_int4(PG_FUNCTION
*** 639,645 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ShareLock, true, false);
PG_RETURN_VOID();
}
--- 663,673 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ShareLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_shared_int4(PG_FUN
*** 657,663 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ShareLock, false, false);
PG_RETURN_VOID();
}
--- 685,695 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
diff -dcrpN 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-06-19 20:23:28.364954147 +0200
--- postgresql.2/src/backend/utils/misc/guc.c 2012-06-19 20:44:12.558192589 +0200
*************** static const struct config_enum_entry sy
*** 389,394 ****
--- 389,405 ----
};
/*
+ * 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[];
*************** static struct config_int ConfigureNamesI
*** 1861,1866 ****
--- 1872,1888 ----
},
{
+ {"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
*************** static struct config_enum ConfigureNames
*** 3141,3146 ****
--- 3163,3178 ----
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 -dcrpN 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-06-19 20:44:12.559192594 +0200
***************
*** 528,533 ****
--- 528,538 ----
#------------------------------------------------------------------------------
#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 -dcrpN postgresql.1/src/include/storage/pg_sema.h postgresql.2/src/include/storage/pg_sema.h
*** postgresql.1/src/include/storage/pg_sema.h 2012-04-16 19:57:22.672918205 +0200
--- postgresql.2/src/include/storage/pg_sema.h 2012-06-19 20:44:12.559192594 +0200
*************** extern void PGSemaphoreUnlock(PGSemaphor
*** 80,83 ****
--- 80,89 ----
/* 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 void PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK);
+
#endif /* PG_SEMA_H */
diff -dcrpN postgresql.1/src/include/storage/timeout.h postgresql.2/src/include/storage/timeout.h
*** postgresql.1/src/include/storage/timeout.h 2012-06-19 20:33:16.942858484 +0200
--- postgresql.2/src/include/storage/timeout.h 2012-06-19 20:48:46.107613917 +0200
***************
*** 19,34 ****
--- 19,43 ----
/* configurable options */
extern int DeadlockTimeout;
extern int StatementTimeout;
+ extern int LockTimeout;
typedef enum TimeoutName {
AUTHENTICATION_TIMEOUT,
DEADLOCK_TIMEOUT,
+ LOCK_TIMEOUT,
STATEMENT_TIMEOUT,
STANDBY_DEADLOCK_TIMEOUT,
STANDBY_TIMEOUT,
TIMEOUT_MAX
} TimeoutName;
+ typedef enum LockTimeoutOptions {
+ LOCK_TIMEOUT_PER_LOCK,
+ LOCK_TIMEOUT_PER_STMT
+ } LockTimeoutOptions;
+
+ extern int LockTimeoutOption;
+
extern bool enable_timeout(TimeoutName tn, int delayms);
extern bool disable_timeout(TimeoutName tn, bool keep_indicator);
extern bool disable_all_timeouts(bool keep_indicators);
diff -dcrpN 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-06-19 20:44:12.561192604 +0200
*************** set statement_timeout to 2000;
*** 198,203 ****
--- 198,223 ----
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: could not obtain lock on relation "pxtest3"
+ LINE 1: 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;
+ ERROR: could not obtain lock on relation "pxtest3"
+ LINE 1: SELECT * FROM pxtest3;
+ ^
+ 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
*************** set statement_timeout to 2000;
*** 213,218 ****
--- 233,245 ----
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: could not obtain lock on relation "pxtest3"
+ LINE 1: SELECT * FROM pxtest3;
+ ^
+ reset lock_timeout;
-- Commit table creation
COMMIT PREPARED 'regress-one';
\d pxtest2
diff -dcrpN 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-06-19 20:44:12.561192604 +0200
*************** set statement_timeout to 2000;
*** 126,131 ****
--- 126,147 ----
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 -
*************** set statement_timeout to 2000;
*** 137,142 ****
--- 153,163 ----
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
I cleaned up the framework patch a bit. My version's attached. Mainly,
returning false for failure in some code paths that are only going to
have the caller elog(FATAL) is rather pointless -- it seems much better
to just have the code itself do the elog(FATAL). In any case, I
searched for reports of those error messages being reported in the wild
and saw none.
There are other things but they are in the nitpick department. (A
reference to "->check_timeout" remains that needs to be fixed too).
There is one thing that still bothers me on this whole framework patch,
but I'm not sure it's easily fixable. Basically the API to timeout.c is
the whole list of timeouts and their whole behaviors. If you want to
add a new one, you can't just call into the entry points, you have to
modify timeout.c itself (as well as timeout.h as well as whatever code
it is that you want to add timeouts to). This may be good enough, but I
don't like it. I think it boils down to proctimeout.h is cheating.
The interface I would actually like to have is something that lets the
modules trying to get a timeout register the timeout-checking function
as a callback. API-wise, it would be much cleaner. However, I'm not
raelly sure that code-wise it would be any cleaner at all. In fact I
think it'd be rather cumbersome. However, if you could give that idea
some thought, it'd be swell.
I haven't looked at the second patch at all yet.
--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support
Attachments:
1-timeout-framework-v9.patchapplication/octet-stream; name=1-timeout-framework-v9.patchDownload
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index dade5cc..bfa6f49 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -89,6 +89,7 @@
#include "storage/proc.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
+#include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
@@ -484,7 +485,7 @@ AutoVacLauncherMain(int argc, char *argv[])
/* Forget any pending QueryCancel request */
QueryCancelPending = false;
- disable_sig_alarm(true);
+ disable_all_timeouts(false);
QueryCancelPending = false; /* again in case timeout occurred */
/* Report the error to the server log */
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 913734f..89ac0a0 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -112,7 +112,7 @@
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
-#include "storage/proc.h"
+#include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/datetime.h"
@@ -3488,8 +3488,7 @@ BackendInitialize(Port *port)
* indefinitely. PreAuthDelay and any DNS interactions above don't count
* against the time limit.
*/
- if (!enable_sig_alarm(AuthenticationTimeout * 1000, false))
- elog(FATAL, "could not set timer for startup packet timeout");
+ enable_timeout(AUTHENTICATION_TIMEOUT, AuthenticationTimeout * 1000);
/*
* Receive the startup packet (which might turn out to be a cancel request
@@ -3526,8 +3525,7 @@ BackendInitialize(Port *port)
/*
* Disable the timeout, and prevent SIGTERM/SIGQUIT again.
*/
- if (!disable_sig_alarm(false))
- elog(FATAL, "could not disable timer for startup packet timeout");
+ disable_timeout(AUTHENTICATION_TIMEOUT, false);
PG_SETMASK(&BlockSig);
}
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index ed75d09..f9a6cf9 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -27,7 +27,7 @@
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
-#include "storage/proc.h"
+#include "storage/timeout.h"
#include "utils/guc.h"
@@ -195,8 +195,8 @@ StartupProcessMain(void)
pqsignal(SIGTERM, StartupProcShutdownHandler); /* request shutdown */
pqsignal(SIGQUIT, startupproc_quickdie); /* hard crash time */
if (EnableHotStandby)
- pqsignal(SIGALRM, handle_standby_sig_alarm); /* ignored unless
- * InHotStandby */
+ pqsignal(SIGALRM, handle_sig_alarm); /* ignored unless
+ * InHotStandby */
else
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c
index 995b68a..6b8233f 100644
--- a/src/backend/storage/ipc/standby.c
+++ b/src/backend/storage/ipc/standby.c
@@ -27,6 +27,7 @@
#include "storage/procarray.h"
#include "storage/sinvaladt.h"
#include "storage/standby.h"
+#include "storage/timeout.h"
#include "utils/ps_status.h"
#include "utils/timestamp.h"
@@ -394,7 +395,6 @@ ResolveRecoveryConflictWithLock(Oid dbOid, Oid relOid)
void
ResolveRecoveryConflictWithBufferPin(void)
{
- bool sig_alarm_enabled = false;
TimestampTz ltime;
TimestampTz now;
@@ -409,10 +409,7 @@ ResolveRecoveryConflictWithBufferPin(void)
* We're willing to wait forever for conflicts, so set timeout for
* deadlock check (only)
*/
- if (enable_standby_sig_alarm(now, now, true))
- sig_alarm_enabled = true;
- else
- elog(FATAL, "could not set timer for process wakeup");
+ enable_timeout(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout);
}
else if (now >= ltime)
{
@@ -423,24 +420,26 @@ ResolveRecoveryConflictWithBufferPin(void)
}
else
{
+ long secs,
+ msecs;
+ int usecs;
+
/*
* Wake up at ltime, and check for deadlocks as well if we will be
* waiting longer than deadlock_timeout
*/
- if (enable_standby_sig_alarm(now, ltime, false))
- sig_alarm_enabled = true;
- else
- elog(FATAL, "could not set timer for process wakeup");
+ enable_timeout(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout);
+
+ TimestampDifference(now, ltime, &secs, &usecs);
+ msecs = secs * 1000 + usecs / 1000;
+
+ enable_timeout(STANDBY_TIMEOUT, msecs);
}
/* Wait to be signaled by UnpinBuffer() */
ProcWaitForSignal();
- if (sig_alarm_enabled)
- {
- if (!disable_standby_sig_alarm())
- elog(FATAL, "could not disable timer for process wakeup");
- }
+ disable_all_timeouts(false);
}
void
diff --git a/src/backend/storage/lmgr/Makefile b/src/backend/storage/lmgr/Makefile
index e12a854..f0f7814 100644
--- a/src/backend/storage/lmgr/Makefile
+++ b/src/backend/storage/lmgr/Makefile
@@ -12,7 +12,7 @@ subdir = src/backend/storage/lmgr
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
-OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o predicate.o
+OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o predicate.o timeout.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 21598d3..daf4c5c 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -48,12 +48,11 @@
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/spin.h"
+#include "storage/timeout.h"
#include "utils/timestamp.h"
/* GUC variables */
-int DeadlockTimeout = 1000;
-int StatementTimeout = 0;
bool log_lock_waits = false;
/* Pointer to this process's PGPROC and PGXACT structs, if any */
@@ -77,27 +76,12 @@ PGPROC *PreparedXactProcs = NULL;
/* If we are waiting for a lock, this points to the associated LOCALLOCK */
static LOCALLOCK *lockAwaited = NULL;
-/* Mark these volatile because they can be changed by signal handler */
-static volatile bool standby_timeout_active = false;
-static volatile bool statement_timeout_active = false;
-static volatile bool deadlock_timeout_active = false;
-static volatile DeadLockState deadlock_state = DS_NOT_YET_CHECKED;
-volatile bool cancel_from_timeout = false;
-
-/* timeout_start_time is set when log_lock_waits is true */
-static TimestampTz timeout_start_time;
-
-/* statement_fin_time is valid only if statement_timeout_active is true */
-static TimestampTz statement_fin_time;
-static TimestampTz statement_fin_time2; /* valid only in recovery */
-
+/* Mark this volatile because it can be changed by signal handler */
+static volatile DeadLockState deadlock_state;
static void RemoveProcFromArray(int code, Datum arg);
static void ProcKill(int code, Datum arg);
static void AuxiliaryProcKill(int code, Datum arg);
-static bool CheckStatementTimeout(void);
-static bool CheckStandbyTimeout(void);
-
/*
* Report shared-memory space needed by InitProcGlobal.
@@ -653,7 +637,7 @@ LockErrorCleanup(void)
return;
/* Turn off the deadlock timer, if it's still running (see ProcSleep) */
- disable_sig_alarm(false);
+ disable_timeout(DEADLOCK_TIMEOUT, false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
@@ -1048,8 +1032,7 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable)
* 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 (!enable_sig_alarm(DeadlockTimeout, false))
- elog(FATAL, "could not set timer for process wakeup");
+ enable_timeout(DEADLOCK_TIMEOUT, DeadlockTimeout);
/*
* If someone wakes us between LWLockRelease and PGSemaphoreLock,
@@ -1138,7 +1121,8 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable)
DescribeLockTag(&buf, &locallock->tag.lock);
modename = GetLockmodeName(locallock->tag.lock.locktag_lockmethodid,
lockmode);
- TimestampDifference(timeout_start_time, GetCurrentTimestamp(),
+ TimestampDifference(get_timeout_start(DEADLOCK_TIMEOUT),
+ GetCurrentTimestamp(),
&secs, &usecs);
msecs = secs * 1000 + usecs / 1000;
usecs = usecs % 1000;
@@ -1200,8 +1184,7 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable)
/*
* Disable the timer, if it's still running
*/
- if (!disable_sig_alarm(false))
- elog(FATAL, "could not disable timer for process wakeup");
+ disable_timeout(DEADLOCK_TIMEOUT, false);
/*
* Re-acquire the lock table's partition lock. We have to do this to hold
@@ -1344,7 +1327,7 @@ ProcLockWakeup(LockMethod lockMethodTable, LOCK *lock)
* NB: this is run inside a signal handler, so be very wary about what is done
* here or in called routines.
*/
-static void
+void
CheckDeadLock(void)
{
int i;
@@ -1499,318 +1482,29 @@ ProcSendSignal(int pid)
PGSemaphoreUnlock(&proc->sem);
}
-
-/*****************************************************************************
- * SIGALRM interrupt support
- *
- * Maybe these should be in pqsignal.c?
- *****************************************************************************/
-
-/*
- * Enable the SIGALRM interrupt to fire after the specified delay
- *
- * Delay is given in milliseconds. Caller should be sure a SIGALRM
- * signal handler is installed before this is called.
- *
- * This code properly handles nesting of deadlock timeout alarms within
- * statement timeout alarms.
- *
- * Returns TRUE if okay, FALSE on failure.
- */
-bool
-enable_sig_alarm(int delayms, bool is_statement_timeout)
-{
- TimestampTz fin_time;
- struct itimerval timeval;
-
- if (is_statement_timeout)
- {
- /*
- * Begin statement-level timeout
- *
- * Note that we compute statement_fin_time with reference to the
- * statement_timestamp, but apply the specified delay without any
- * correction; that is, we ignore whatever time has elapsed since
- * statement_timestamp was set. In the normal case only a small
- * interval will have elapsed and so this doesn't matter, but there
- * are corner cases (involving multi-statement query strings with
- * embedded COMMIT or ROLLBACK) where we might re-initialize the
- * statement timeout long after initial receipt of the message. In
- * such cases the enforcement of the statement timeout will be a bit
- * inconsistent. This annoyance is judged not worth the cost of
- * performing an additional gettimeofday() here.
- */
- Assert(!deadlock_timeout_active);
- fin_time = GetCurrentStatementStartTimestamp();
- fin_time = TimestampTzPlusMilliseconds(fin_time, delayms);
- statement_fin_time = fin_time;
- cancel_from_timeout = false;
- statement_timeout_active = true;
- }
- else if (statement_timeout_active)
- {
- /*
- * Begin deadlock timeout with statement-level timeout active
- *
- * Here, we want to interrupt at the closer of the two timeout times.
- * If fin_time >= statement_fin_time then we need not touch the
- * existing timer setting; else set up to interrupt at the deadlock
- * timeout time.
- *
- * NOTE: in this case it is possible that this routine will be
- * interrupted by the previously-set timer alarm. This is okay
- * because the signal handler will do only what it should do according
- * to the state variables. The deadlock checker may get run earlier
- * than normal, but that does no harm.
- */
- timeout_start_time = GetCurrentTimestamp();
- fin_time = TimestampTzPlusMilliseconds(timeout_start_time, delayms);
- deadlock_timeout_active = true;
- if (fin_time >= statement_fin_time)
- return true;
- }
- else
- {
- /* Begin deadlock timeout with no statement-level timeout */
- deadlock_timeout_active = true;
- /* GetCurrentTimestamp can be expensive, so only do it if we must */
- if (log_lock_waits)
- timeout_start_time = GetCurrentTimestamp();
- }
-
- /* If we reach here, okay to set the timer interrupt */
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = delayms / 1000;
- timeval.it_value.tv_usec = (delayms % 1000) * 1000;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- return true;
-}
-
-/*
- * Cancel the SIGALRM timer, either for a deadlock timeout or a statement
- * timeout. If a deadlock timeout is canceled, any active statement timeout
- * remains in force.
- *
- * Returns TRUE if okay, FALSE on failure.
- */
-bool
-disable_sig_alarm(bool is_statement_timeout)
-{
- /*
- * Always disable the interrupt if it is active; this avoids being
- * interrupted by the signal handler and thereby possibly getting
- * confused.
- *
- * We will re-enable the interrupt if necessary in CheckStatementTimeout.
- */
- if (statement_timeout_active || deadlock_timeout_active)
- {
- struct itimerval timeval;
-
- MemSet(&timeval, 0, sizeof(struct itimerval));
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- {
- statement_timeout_active = false;
- cancel_from_timeout = false;
- deadlock_timeout_active = false;
- return false;
- }
- }
-
- /* Always cancel deadlock timeout, in case this is error cleanup */
- deadlock_timeout_active = false;
-
- /* Cancel or reschedule statement timeout */
- if (is_statement_timeout)
- {
- statement_timeout_active = false;
- cancel_from_timeout = false;
- }
- else if (statement_timeout_active)
- {
- if (!CheckStatementTimeout())
- return false;
- }
- return true;
-}
-
-
/*
* Check for statement timeout. If the timeout time has come,
- * trigger a query-cancel interrupt; if not, reschedule the SIGALRM
- * interrupt to occur at the right time.
- *
- * Returns true if okay, false if failed to set the interrupt.
+ * trigger a query-cancel interrupt.
*/
-static bool
-CheckStatementTimeout(void)
+void
+StatementTimeoutFunction(void)
{
- TimestampTz now;
-
- if (!statement_timeout_active)
- return true; /* do nothing if not active */
-
- now = GetCurrentTimestamp();
-
- if (now >= statement_fin_time)
- {
- /* Time to die */
- statement_timeout_active = false;
- cancel_from_timeout = true;
#ifdef HAVE_SETSID
- /* try to signal whole process group */
- kill(-MyProcPid, SIGINT);
+ /* try to signal whole process group */
+ kill(-MyProcPid, SIGINT);
#endif
- kill(MyProcPid, SIGINT);
- }
- else
- {
- /* Not time yet, so (re)schedule the interrupt */
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
-
- /*
- * It's possible that the difference is less than a microsecond;
- * ensure we don't cancel, rather than set, the interrupt.
- */
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- }
-
- return true;
+ kill(MyProcPid, SIGINT);
}
-
/*
- * Signal handler for SIGALRM for normal user backends
- *
- * Process deadlock check and/or statement timeout check, as needed.
- * To avoid various edge cases, we must be careful to do nothing
- * when there is nothing to be done. We also need to be able to
- * reschedule the timer interrupt if called before end of statement.
+ * CheckStandbyDeadLock() will trigger if the deadlock timeout
+ * happens earlier than StandbyTimeout. If it triggers,
+ * StandbyTimeout will still be rescheduled.
*/
void
-handle_sig_alarm(SIGNAL_ARGS)
+StandbyDeadLockFunction(void)
{
- int save_errno = errno;
-
- /* SIGALRM is cause for waking anything waiting on the process latch */
- if (MyProc)
- SetLatch(&MyProc->procLatch);
-
- if (deadlock_timeout_active)
- {
- deadlock_timeout_active = false;
- CheckDeadLock();
- }
-
- if (statement_timeout_active)
- (void) CheckStatementTimeout();
-
- errno = save_errno;
-}
-
-/*
- * Signal handler for SIGALRM in Startup process
- *
- * To avoid various edge cases, we must be careful to do nothing
- * when there is nothing to be done. We also need to be able to
- * reschedule the timer interrupt if called before end of statement.
- *
- * We set either deadlock_timeout_active or statement_timeout_active
- * or both. Interrupts are enabled if standby_timeout_active.
- */
-bool
-enable_standby_sig_alarm(TimestampTz now, TimestampTz fin_time, bool deadlock_only)
-{
- TimestampTz deadlock_time = TimestampTzPlusMilliseconds(now,
- DeadlockTimeout);
-
- if (deadlock_only)
- {
- /*
- * Wake up at deadlock_time only, then wait forever
- */
- statement_fin_time = deadlock_time;
- deadlock_timeout_active = true;
- statement_timeout_active = false;
- }
- else if (fin_time > deadlock_time)
- {
- /*
- * Wake up at deadlock_time, then again at fin_time
- */
- statement_fin_time = deadlock_time;
- statement_fin_time2 = fin_time;
- deadlock_timeout_active = true;
- statement_timeout_active = true;
- }
- else
- {
- /*
- * Wake only at fin_time because its fairly soon
- */
- statement_fin_time = fin_time;
- deadlock_timeout_active = false;
- statement_timeout_active = true;
- }
-
- if (deadlock_timeout_active || statement_timeout_active)
- {
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- standby_timeout_active = true;
- }
-
- return true;
-}
-
-bool
-disable_standby_sig_alarm(void)
-{
- /*
- * Always disable the interrupt if it is active; this avoids being
- * interrupted by the signal handler and thereby possibly getting
- * confused.
- *
- * We will re-enable the interrupt if necessary in CheckStandbyTimeout.
- */
- if (standby_timeout_active)
- {
- struct itimerval timeval;
-
- MemSet(&timeval, 0, sizeof(struct itimerval));
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- {
- standby_timeout_active = false;
- return false;
- }
- }
-
- standby_timeout_active = false;
-
- return true;
+ SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
}
/*
@@ -1818,81 +1512,8 @@ disable_standby_sig_alarm(void)
* SIGALRM handler. Timers will only be set when InHotStandby.
* We simply ignore any signals unless the timer has been set.
*/
-static bool
-CheckStandbyTimeout(void)
-{
- TimestampTz now;
- bool reschedule = false;
-
- standby_timeout_active = false;
-
- now = GetCurrentTimestamp();
-
- /*
- * Reschedule the timer if its not time to wake yet, or if we have both
- * timers set and the first one has just been reached.
- */
- if (now >= statement_fin_time)
- {
- if (deadlock_timeout_active)
- {
- /*
- * We're still waiting when we reach deadlock timeout, so send out
- * a request to have other backends check themselves for deadlock.
- * Then continue waiting until statement_fin_time, if that's set.
- */
- SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
- deadlock_timeout_active = false;
-
- /*
- * Begin second waiting period if required.
- */
- if (statement_timeout_active)
- {
- reschedule = true;
- statement_fin_time = statement_fin_time2;
- }
- }
- else
- {
- /*
- * We've now reached statement_fin_time, so ask all conflicts to
- * leave, so we can press ahead with applying changes in recovery.
- */
- SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
- }
- }
- else
- reschedule = true;
-
- if (reschedule)
- {
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- standby_timeout_active = true;
- }
-
- return true;
-}
-
void
-handle_standby_sig_alarm(SIGNAL_ARGS)
+StandbyTimeoutFunction(void)
{
- int save_errno = errno;
-
- if (standby_timeout_active)
- (void) CheckStandbyTimeout();
-
- errno = save_errno;
+ SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
}
diff --git a/src/backend/storage/lmgr/timeout.c b/src/backend/storage/lmgr/timeout.c
new file mode 100644
index 0000000..ebed1b3
--- /dev/null
+++ b/src/backend/storage/lmgr/timeout.c
@@ -0,0 +1,423 @@
+/*-------------------------------------------------------------------------
+ *
+ * timeout.c
+ * routines to manage timeout sources handled by SIGALRM
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/storage/lmgr/timeout.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <sys/time.h>
+
+#include "access/xact.h"
+#include "storage/proc.h"
+#include "storage/timeout.h"
+#include "utils/timestamp.h"
+
+/* GUC variables */
+int DeadlockTimeout = 1000;
+int StatementTimeout = 0;
+
+/*
+ * Infrastructure for timeouts
+ */
+
+static void InitTimeout(TimeoutName tn, TimestampTz start_time,
+ TimestampTz fin_time);
+static void DestroyTimeout(TimeoutName tn, bool keep_indicator);
+static void DummyTimeoutFunction(void);
+
+typedef void (*timeout_func)(void);
+typedef TimestampTz (*timeout_start)(void);
+
+typedef struct timeout_params
+{
+ TimeoutName index;
+ bool resched_next;
+
+ /* volatile because it may be changed from the signal handler */
+ volatile bool indicator;
+
+ timeout_func timeout_func;
+ timeout_start timeout_start;
+
+ TimestampTz start_time;
+ TimestampTz fin_time;
+} timeout_params;
+
+/*
+ * List of possible timeout reasons in the order of enum TimeoutName.
+ * The priority of timeouts (in case two of them would trigger at the
+ * same time) is determined by this order: the earlier one in the list
+ * has higher priority.
+ */
+static timeout_params base_timeouts[TIMEOUT_MAX] = {
+ {
+ AUTHENTICATION_TIMEOUT, false, false,
+ DummyTimeoutFunction, GetCurrentTimestamp
+ },
+
+ {
+ DEADLOCK_TIMEOUT, true, false,
+ CheckDeadLock, GetCurrentTimestamp
+ },
+
+ {
+ STATEMENT_TIMEOUT, false, false,
+ StatementTimeoutFunction, GetCurrentStatementStartTimestamp
+ },
+
+ {
+ STANDBY_DEADLOCK_TIMEOUT, true, false,
+ StandbyDeadLockFunction, GetCurrentTimestamp
+ },
+
+ {
+ STANDBY_TIMEOUT, false, false,
+ StandbyTimeoutFunction, GetCurrentTimestamp
+ }
+};
+
+/*
+ * List of active timeouts ordered by their fin_time and priority.
+ */
+static int n_timeouts = 0;
+static timeout_params *timeouts[TIMEOUT_MAX];
+
+/*****************************************************************************
+ * Internal helper functions
+ *****************************************************************************/
+
+/*
+ * Find the index of a given timeout type in the active array
+ */
+static int
+find_active_timeout(TimeoutName tn)
+{
+ int i;
+
+ for (i = 0; i < n_timeouts; i++)
+ {
+ if (timeouts[i]->index == tn)
+ return i;
+ }
+
+ return -1;
+}
+
+#define is_timeout_active(tn) (find_active_timeout(tn) >= 0)
+
+/*
+ * Insert tn'th timeout into the list of timeouts at the given index
+ * if the previous timeout allows rescheduling the next one in the list.
+ */
+static void
+insert_timeout(TimeoutName tn, int index)
+{
+ int i;
+
+ if (index < 0 || index > n_timeouts)
+ elog(FATAL, "timeout index %d out of range 0..%d", index, n_timeouts);
+
+ if (index > 0 && !timeouts[index - 1]->resched_next)
+ return;
+
+ for (i = n_timeouts - 1; i >= index; i--)
+ timeouts[i + 1] = timeouts[i];
+
+ timeouts[index] = &base_timeouts[tn];
+ n_timeouts++;
+}
+
+/*
+ * Remove the index'th element from the timeout list.
+ */
+static void
+remove_timeout_index(int index)
+{
+ int i;
+
+ if (index < 0 || index > n_timeouts)
+ elog(FATAL, "timeout index %d out of range 0..%d", index, n_timeouts);
+
+ for (i = index + 1; i < n_timeouts; i++)
+ timeouts[i - 1] = timeouts[i];
+
+ if (n_timeouts > 0 && index < n_timeouts)
+ n_timeouts--;
+}
+
+/*
+ * (Re)schedule the next active timeout
+ */
+static void
+schedule_timeout(TimestampTz now)
+{
+ long secs;
+ int usecs;
+ struct itimerval timeval;
+
+ /* There is no active timeout, do nothing. */
+ if (n_timeouts == 0)
+ return;
+
+ TimestampDifference(now, timeouts[0]->fin_time,
+ &secs, &usecs);
+
+ /*
+ * It's possible that the difference is less than a microsecond;
+ * ensure we don't cancel, rather than set, the interrupt.
+ */
+ if (secs == 0 && usecs == 0)
+ usecs = 1;
+
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ timeval.it_value.tv_sec = secs;
+ timeval.it_value.tv_usec = usecs;
+ if (setitimer(ITIMER_REAL, &timeval, NULL) != 0)
+ elog(FATAL, "could not enable timer");
+}
+
+/*****************************************************************************
+ * Init, Destroy and callback functions for different timeouts.
+ *****************************************************************************/
+
+/*
+ * Common Init and Destroy functions
+ */
+
+static void
+InitTimeout(TimeoutName tn, TimestampTz start_time, TimestampTz fin_time)
+{
+ base_timeouts[tn].indicator = false;
+ base_timeouts[tn].start_time = start_time;
+ base_timeouts[tn].fin_time = fin_time;
+}
+
+static void
+DestroyTimeout(TimeoutName tn, bool keep_indicator)
+{
+ if (!keep_indicator)
+ base_timeouts[tn].indicator = false;
+ base_timeouts[tn].start_time = 0;
+ base_timeouts[tn].fin_time = 0;
+}
+
+/*
+ * CheckDummyTimeout
+ *
+ * Do nothing. Usable for timeout sources that don't need
+ * extra processing, like authentication_timeout.
+ * The timeout indicator is set by the signal handler.
+ */
+static void
+DummyTimeoutFunction(void)
+{
+ return;
+}
+
+/*****************************************************************************
+ * Public API
+ *****************************************************************************/
+
+/*
+ * Enable the SIGALRM interrupt to fire after the specified delay
+ *
+ * Delay is given in milliseconds. Caller should be sure a SIGALRM
+ * signal handler is installed before this is called.
+ *
+ * This code properly handles nesting of different timeout alarms.
+ */
+void
+enable_timeout(TimeoutName tn, int delayms)
+{
+ TimestampTz start_time;
+ TimestampTz fin_time;
+ int i;
+
+ Assert(!is_timeout_active(tn));
+
+ if (delayms <= 0)
+ return;
+
+ start_time = base_timeouts[tn].timeout_start();
+ fin_time = TimestampTzPlusMilliseconds(start_time, delayms);
+
+ /* Find out the index where to insert the new timeout. */
+ for (i = 0; i < n_timeouts; i++)
+ {
+ /*
+ * The new timeout triggers earlier than a previously added one:
+ * insert here.
+ */
+ if (fin_time < timeouts[i]->fin_time)
+ break;
+ /*
+ * The new timeout triggers at the same time as the previously added
+ * one but has greater priority.
+ */
+ if (fin_time == timeouts[i]->fin_time && tn < timeouts[i]->index)
+ break;
+ }
+
+ /*
+ * Initialize the timeout parameters
+ */
+ InitTimeout(tn, start_time, fin_time);
+
+ insert_timeout(tn, i);
+
+ /*
+ * If the timeout was inserted in the first position, set the timer.
+ */
+ if (i == 0)
+ schedule_timeout(start_time);
+}
+
+/*
+ * Cancel the SIGALRM timer for the specific timeout.
+ * If a timeout is canceled, any other active timeout remains in force.
+ *
+ * It's not an error to disable a timeout that is not enabled.
+ */
+void
+disable_timeout(TimeoutName tn, bool keep_indicator)
+{
+ int i;
+
+ /*
+ * Always disable the timer if it is active; this avoids being
+ * interrupted by the signal handler and thereby possibly getting
+ * confused.
+ *
+ * We will re-enable the interrupt if necessary in ->check_timeout().
+ */
+ if (n_timeouts > 0)
+ {
+ struct itimerval timeval;
+
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ if (setitimer(ITIMER_REAL, &timeval, NULL) != 0)
+ elog(FATAL, "could not disable timer");
+ }
+
+ /* Find the timeout and remove from the list. */
+ i = find_active_timeout(tn);
+ if (i < 0)
+ return;
+ remove_timeout_index(i);
+
+ /* Do cleanup. */
+ DestroyTimeout(tn, keep_indicator);
+
+ /*
+ * If the first timeout was removed from the list and there is
+ * at least one active left, reschedule it.
+ */
+ if (i == 0 && n_timeouts > 0)
+ schedule_timeout(GetCurrentTimestamp());
+}
+
+/*
+ * Disable SIGALRM and remove all timeouts from the list and
+ * reset the timeout indicators.
+ */
+void
+disable_all_timeouts(bool keep_indicators)
+{
+ struct itimerval timeval;
+ int i;
+
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ if (setitimer(ITIMER_REAL, &timeval, NULL) != 0)
+ elog(FATAL, "could not disable timer");
+
+ n_timeouts = 0;
+ for (i = 0; i < TIMEOUT_MAX; i++)
+ DestroyTimeout(i, keep_indicators);
+}
+
+/*
+ * Return the timeout indicator
+ */
+bool
+get_timeout_indicator(TimeoutName tn)
+{
+ return base_timeouts[tn].indicator;
+}
+
+/*
+ * Return the start of the timer for this timeout
+ */
+TimestampTz
+get_timeout_start(TimeoutName tn)
+{
+ return base_timeouts[tn].start_time;
+}
+
+/*
+ * Signal handler for SIGALRM
+ *
+ * Process the check for the currently active timeout source and
+ * reschedule the next as needed. To avoid various edge cases,
+ * we must be careful to do nothing when there is nothing to be done.
+ */
+void
+handle_sig_alarm(SIGNAL_ARGS)
+{
+ int save_errno = errno;
+
+ /*
+ * SIGALRM is cause for waking anything waiting on the process latch.
+ * Cope with MyProc not being there, as the startup process also uses
+ * this signal handler.
+ */
+ if (MyProc)
+ SetLatch(&MyProc->procLatch);
+
+ if (n_timeouts > 0)
+ {
+ TimestampTz now;
+ bool reschedule;
+
+ now = GetCurrentTimestamp();
+
+ /* If our time has not come yet, do nothing. */
+ if (now < timeouts[0]->fin_time)
+ goto out;
+
+ /*
+ * Set the trigger indicator before calling the checker function.
+ * Setting if after may have side effects that lead to detecting
+ * a different event, like "user pressed Ctrl-C" is detected instead
+ * of the statement timeout having triggered.
+ */
+ timeouts[0]->indicator = true;
+
+ /* Call the timeout checker. */
+ timeouts[0]->timeout_func();
+
+ /*
+ * Remove the current timeout source and reschedule
+ * the next if needed. Short circuit disable_timeout(..., true)
+ * for the timeout source that just triggered.
+ */
+ reschedule = timeouts[0]->resched_next;
+ remove_timeout_index(0);
+
+ if (reschedule)
+ schedule_timeout(now);
+ else
+ disable_all_timeouts(true);
+ }
+
+out:
+ errno = save_errno;
+}
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 9a5438f..804adbf 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -64,6 +64,7 @@
#include "storage/proc.h"
#include "storage/procsignal.h"
#include "storage/sinval.h"
+#include "storage/timeout.h"
#include "tcop/fastpath.h"
#include "tcop/pquery.h"
#include "tcop/tcopprot.h"
@@ -2396,9 +2397,9 @@ start_xact_command(void)
/* Set statement timeout running, if any */
/* NB: this mustn't be enabled until we are within an xact */
if (StatementTimeout > 0)
- enable_sig_alarm(StatementTimeout, true);
+ enable_timeout(STATEMENT_TIMEOUT, StatementTimeout);
else
- cancel_from_timeout = false;
+ disable_timeout(STATEMENT_TIMEOUT, false);
xact_started = true;
}
@@ -2410,7 +2411,7 @@ finish_xact_command(void)
if (xact_started)
{
/* Cancel any active statement timeout before committing */
- disable_sig_alarm(true);
+ disable_all_timeouts(false);
/* Now commit the command */
ereport(DEBUG3,
@@ -2891,7 +2892,7 @@ ProcessInterrupts(void)
(errcode(ERRCODE_QUERY_CANCELED),
errmsg("canceling authentication due to timeout")));
}
- if (cancel_from_timeout)
+ if (get_timeout_indicator(STATEMENT_TIMEOUT))
{
ImmediateInterruptOK = false; /* not idle anymore */
DisableNotifyInterrupt();
@@ -3802,10 +3803,10 @@ PostgresMain(int argc, char *argv[], const char *username)
/*
* Forget any pending QueryCancel request, since we're returning to
- * the idle loop anyway, and cancel the statement timer if running.
+ * the idle loop anyway, and cancel the timer if running.
*/
QueryCancelPending = false;
- disable_sig_alarm(true);
+ disable_all_timeouts(false);
QueryCancelPending = false; /* again in case timeout occurred */
/*
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 4d4a895..36e61cd 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -41,12 +41,12 @@
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lmgr.h"
-#include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/proc.h"
#include "storage/sinvaladt.h"
#include "storage/smgr.h"
+#include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
#include "utils/fmgroids.h"
@@ -205,8 +205,7 @@ PerformAuthentication(Port *port)
* during authentication. Since we're inside a transaction and might do
* database access, we have to use the statement_timeout infrastructure.
*/
- if (!enable_sig_alarm(AuthenticationTimeout * 1000, true))
- elog(FATAL, "could not set timer for authorization timeout");
+ enable_timeout(STATEMENT_TIMEOUT, AuthenticationTimeout * 1000);
/*
* Now perform authentication exchange.
@@ -216,8 +215,7 @@ PerformAuthentication(Port *port)
/*
* Done with authentication. Disable the timeout, and log if needed.
*/
- if (!disable_sig_alarm(true))
- elog(FATAL, "could not disable timer for authorization timeout");
+ disable_timeout(STATEMENT_TIMEOUT, false);
if (Log_connections)
{
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 965d325..bc1e41d 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -64,6 +64,7 @@
#include "storage/fd.h"
#include "storage/proc.h"
#include "storage/predicate.h"
+#include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "tsearch/ts_cache.h"
#include "utils/builtins.h"
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 31f7099..f06d8c7 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -218,13 +218,8 @@ extern PGPROC *PreparedXactProcs;
/* configurable options */
-extern int DeadlockTimeout;
-extern int StatementTimeout;
extern bool log_lock_waits;
-extern volatile bool cancel_from_timeout;
-
-
/*
* Function Prototypes
*/
@@ -252,13 +247,4 @@ extern void LockErrorCleanup(void);
extern void ProcWaitForSignal(void);
extern void ProcSendSignal(int pid);
-extern bool enable_sig_alarm(int delayms, bool is_statement_timeout);
-extern bool disable_sig_alarm(bool is_statement_timeout);
-extern void handle_sig_alarm(SIGNAL_ARGS);
-
-extern bool enable_standby_sig_alarm(TimestampTz now,
- TimestampTz fin_time, bool deadlock_only);
-extern bool disable_standby_sig_alarm(void);
-extern void handle_standby_sig_alarm(SIGNAL_ARGS);
-
#endif /* PROC_H */
diff --git a/src/include/storage/proctimeout.h b/src/include/storage/proctimeout.h
new file mode 100644
index 0000000..5226357
--- /dev/null
+++ b/src/include/storage/proctimeout.h
@@ -0,0 +1,22 @@
+/*-------------------------------------------------------------------------
+ *
+ * proctimeout.h
+ * declaration of timeout checker functions
+ *
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/storage/proctimeout.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PROCTIMEOUT_H
+#define PROCTIMEOUT_H
+
+extern void CheckDeadLock(void);
+extern void StatementTimeoutFunction(void);
+extern void StandbyDeadLockFunction(void);
+extern void StandbyTimeoutFunction(void);
+
+#endif /* PROCTIMEOUT_H */
2012-06-26 06:59 keltezéssel, Alvaro Herrera írta:
I cleaned up the framework patch a bit. My version's attached. Mainly,
returning false for failure in some code paths that are only going to
have the caller elog(FATAL) is rather pointless -- it seems much better
to just have the code itself do the elog(FATAL). In any case, I
searched for reports of those error messages being reported in the wild
and saw none.
OK. The cleanups are always good.
One nitpick, though. Your version doesn't contain the timeout.h
and compilation fails because of it. :-) You might have forgotten
to do "git commit -a".
There are other things but they are in the nitpick department. (A
reference to "->check_timeout" remains that needs to be fixed too).
Yes, it's called ->timeout_func() now.
There is one thing that still bothers me on this whole framework patch,
but I'm not sure it's easily fixable. Basically the API to timeout.c is
the whole list of timeouts and their whole behaviors. If you want to
add a new one, you can't just call into the entry points, you have to
modify timeout.c itself (as well as timeout.h as well as whatever code
it is that you want to add timeouts to). This may be good enough, but I
don't like it. I think it boils down to proctimeout.h is cheating.The interface I would actually like to have is something that lets the
modules trying to get a timeout register the timeout-checking function
as a callback. API-wise, it would be much cleaner. However, I'm not
raelly sure that code-wise it would be any cleaner at all. In fact I
think it'd be rather cumbersome. However, if you could give that idea
some thought, it'd be swell.
Well, I can make the registration interface similar to how LWLocks
are treated, but that doesn't avoid modification of the base_timeouts
array in case a new internal use case arises. Say:
#define USER_TIMEOUTS 4
int n_timeouts = TIMEOUT_MAX;
static timeout_params base_timeouts[TIMEOUT_MAX + USER_TIMEOUTS];
and register_timeout() adds data after TIMEOUT_MAX.
I haven't looked at the second patch at all yet.
This is the whole point of the first patch. But as I said above for
registering a new timeout source, it's a new internal use case.
One that touches a lot of places which cannot simply be done
by registering a new timeout source.
--
----------------------------------
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/
On Tue, Jun 26, 2012 at 3:59 AM, Boszormenyi Zoltan <zb@cybertec.at> wrote:
Well, I can make the registration interface similar to how LWLocks
are treated, but that doesn't avoid modification of the base_timeouts
array in case a new internal use case arises. Say:#define USER_TIMEOUTS 4
int n_timeouts = TIMEOUT_MAX;
static timeout_params base_timeouts[TIMEOUT_MAX + USER_TIMEOUTS];
Since timeouts - unlike lwlocks - do not need to touch shared memory,
there's no need for a hard-coded limit here. You can just allocate
the array using MemoryContextAlloc(TopMemoryContext, ...) and enlarge
it as necessary. To avoid needing to modify the base_timeouts array,
you can just have internal callers push their entries into the array
during process startup using the same function call that an external
module would use.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
2012-06-26 13:50 keltezéssel, Robert Haas írta:
On Tue, Jun 26, 2012 at 3:59 AM, Boszormenyi Zoltan <zb@cybertec.at> wrote:
Well, I can make the registration interface similar to how LWLocks
are treated, but that doesn't avoid modification of the base_timeouts
array in case a new internal use case arises. Say:#define USER_TIMEOUTS 4
int n_timeouts = TIMEOUT_MAX;
static timeout_params base_timeouts[TIMEOUT_MAX + USER_TIMEOUTS];Since timeouts - unlike lwlocks - do not need to touch shared memory,
there's no need for a hard-coded limit here. You can just allocate
the array using MemoryContextAlloc(TopMemoryContext, ...) and enlarge
it as necessary. To avoid needing to modify the base_timeouts array,
you can just have internal callers push their entries into the array
during process startup using the same function call that an external
module would use.
I know, but it doesn't feel right to "register" static functionality.
--
----------------------------------
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/
On Tue, Jun 26, 2012 at 8:03 AM, Boszormenyi Zoltan <zb@cybertec.at> wrote:
I know, but it doesn't feel right to "register" static functionality.
We do it elsewhere. The overhead is pretty minimal compared to other
things we already do during startup, and avoiding the need for the
array to have a fixed-size seems worth it, IMHO.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Robert Haas <robertmhaas@gmail.com> writes:
On Tue, Jun 26, 2012 at 8:03 AM, Boszormenyi Zoltan <zb@cybertec.at> wrote:
I know, but it doesn't feel right to "register" static functionality.
We do it elsewhere. The overhead is pretty minimal compared to other
things we already do during startup, and avoiding the need for the
array to have a fixed-size seems worth it, IMHO.
It's not even clear that the array needs to be dynamically resizable (yet).
Compare for instance syscache invalidation callbacks, which have so far
gotten along fine with a fixed-size array to hold registrations. It's
foreseeable that we'll need something better someday, but that day
hasn't arrived, and might never arrive.
I agree with the feeling that this patch isn't done if the "core"
timeout code has to know specifically about each usage. We have that
situation already.
regards, tom lane
Excerpts from Boszormenyi Zoltan's message of mar jun 26 03:59:06 -0400 2012:
2012-06-26 06:59 keltezéssel, Alvaro Herrera írta:
I cleaned up the framework patch a bit. My version's attached. Mainly,
returning false for failure in some code paths that are only going to
have the caller elog(FATAL) is rather pointless -- it seems much better
to just have the code itself do the elog(FATAL). In any case, I
searched for reports of those error messages being reported in the wild
and saw none.OK. The cleanups are always good.
One nitpick, though. Your version doesn't contain the timeout.h
and compilation fails because of it. :-) You might have forgotten
to do "git commit -a".
Oops. Attached. It's pretty much the same you had, except some "bools"
are changed to void.
There is one thing that still bothers me on this whole framework patch,
but I'm not sure it's easily fixable. Basically the API to timeout.c is
the whole list of timeouts and their whole behaviors. If you want to
add a new one, you can't just call into the entry points, you have to
modify timeout.c itself (as well as timeout.h as well as whatever code
it is that you want to add timeouts to). This may be good enough, but I
don't like it. I think it boils down to proctimeout.h is cheating.The interface I would actually like to have is something that lets the
modules trying to get a timeout register the timeout-checking function
as a callback. API-wise, it would be much cleaner. However, I'm not
raelly sure that code-wise it would be any cleaner at all. In fact I
think it'd be rather cumbersome. However, if you could give that idea
some thought, it'd be swell.Well, I can make the registration interface similar to how LWLocks
are treated, but that doesn't avoid modification of the base_timeouts
array in case a new internal use case arises. Say:#define USER_TIMEOUTS 4
int n_timeouts = TIMEOUT_MAX;
static timeout_params base_timeouts[TIMEOUT_MAX + USER_TIMEOUTS];and register_timeout() adds data after TIMEOUT_MAX.
Well, I don't expect that we're going to have many external uses. My
point about registration is so that internal callers use it as well. I
was thinking we could do something like xact.c does, which is to have
somewhere in proc.c a call like this:
TimeoutRegister(DEADLOCK_TIMEOUT, 1, true, CheckDeadLock, GetCurrentTimestamp);
at process startup (the magic value 1 is the priority. Maybe there's a
better way to handle this). That way, timeout.c or timeout.h do not
need to know anything about proc.c at all; we don't need proctimeout.h
at all. The only thing it (the timeout module) needs to know is that
there is a symbolic constant named DEADLOCK_TIMEOUT. Similarly for
statement timeout, etc.
When you call enable_timeout you first have to ensure that
DEADLOCK_TIMEOUT has been registered; and if it's not, die on an
Assert(). That way you ensure that there are no bugs where you try to
enable a timeout that hasn't been registered.
(If we later find the need to let external modules add timeouts, which I
find unlikely, we can have TimeoutRegister return TimeoutName and have
this value be dynamically allocated. But for now I think that would be
useless complication).
The difference with lwlocks is that each lwlock is just a number. The
lwlock.c module doesn't need to know anything about how each lwlock is
actually used. It's not the same thing here -- which is why I think it
would be better to have all modules, even the hardcoded ones, use a
registering interface.
... Now, it could be argued that it would be even better to have
TimeoutRegister not take the TimeoutName argument, and instead generate
it dynamically and pass it back to the caller -- that way you don't need
the enum in timeout.h. The problem I have with that idea is that it
would force the caller modules to have a global variable to keep track
of that, which seems worse to me.
I haven't looked at the second patch at all yet.
This is the whole point of the first patch.
I know that. But I want to get the details of the framework right
before we move on to add more stuff to it.
But as I said above for
registering a new timeout source, it's a new internal use case.
One that touches a lot of places which cannot simply be done
by registering a new timeout source.
Sure. That's going to be the case for any other timeout we want to add
(which is why I think we don't really need dynamic timeouts).
--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support
Attachments:
1-timeout-framework-v9a.patchapplication/octet-stream; name=1-timeout-framework-v9a.patchDownload
commit 157aaa0edc54a3818ba09f243e7a7f975d025def
Author: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Tue Jun 26 11:55:27 2012 -0400
missed file
diff --git a/src/include/storage/timeout.h b/src/include/storage/timeout.h
new file mode 100644
index 0000000..7c6b2a2
--- /dev/null
+++ b/src/include/storage/timeout.h
@@ -0,0 +1,41 @@
+/*-------------------------------------------------------------------------
+ *
+ * timeout.h
+ * SIGALRM timeout API
+ *
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/storage/timeout.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef TIMEOUT_H
+#define TIMEOUT_H
+
+#include "datatype/timestamp.h"
+#include "storage/proctimeout.h"
+
+/* configurable options */
+extern int DeadlockTimeout;
+extern int StatementTimeout;
+
+typedef enum TimeoutName
+{
+ AUTHENTICATION_TIMEOUT,
+ DEADLOCK_TIMEOUT,
+ STATEMENT_TIMEOUT,
+ STANDBY_DEADLOCK_TIMEOUT,
+ STANDBY_TIMEOUT,
+ TIMEOUT_MAX
+} TimeoutName;
+
+extern void enable_timeout(TimeoutName tn, int delayms);
+extern void disable_timeout(TimeoutName tn, bool keep_indicator);
+extern void disable_all_timeouts(bool keep_indicators);
+extern bool get_timeout_indicator(TimeoutName tn);
+extern TimestampTz get_timeout_start(TimeoutName tn);
+extern void handle_sig_alarm(SIGNAL_ARGS);
+
+#endif /* TIMEOUT_H */
2012-06-26 18:12 keltezéssel, Alvaro Herrera írta:
Excerpts from Boszormenyi Zoltan's message of mar jun 26 03:59:06 -0400 2012:
2012-06-26 06:59 keltezéssel, Alvaro Herrera írta:
I cleaned up the framework patch a bit. My version's attached. Mainly,
returning false for failure in some code paths that are only going to
have the caller elog(FATAL) is rather pointless -- it seems much better
to just have the code itself do the elog(FATAL). In any case, I
searched for reports of those error messages being reported in the wild
and saw none.OK. The cleanups are always good.
One nitpick, though. Your version doesn't contain the timeout.h
and compilation fails because of it. :-) You might have forgotten
to do "git commit -a".Oops. Attached. It's pretty much the same you had, except some "bools"
are changed to void.There is one thing that still bothers me on this whole framework patch,
but I'm not sure it's easily fixable. Basically the API to timeout.c is
the whole list of timeouts and their whole behaviors. If you want to
add a new one, you can't just call into the entry points, you have to
modify timeout.c itself (as well as timeout.h as well as whatever code
it is that you want to add timeouts to). This may be good enough, but I
don't like it. I think it boils down to proctimeout.h is cheating.The interface I would actually like to have is something that lets the
modules trying to get a timeout register the timeout-checking function
as a callback. API-wise, it would be much cleaner. However, I'm not
raelly sure that code-wise it would be any cleaner at all. In fact I
think it'd be rather cumbersome. However, if you could give that idea
some thought, it'd be swell.Well, I can make the registration interface similar to how LWLocks
are treated, but that doesn't avoid modification of the base_timeouts
array in case a new internal use case arises. Say:#define USER_TIMEOUTS 4
int n_timeouts = TIMEOUT_MAX;
static timeout_params base_timeouts[TIMEOUT_MAX + USER_TIMEOUTS];and register_timeout() adds data after TIMEOUT_MAX.
Well, I don't expect that we're going to have many external uses. My
point about registration is so that internal callers use it as well. I
was thinking we could do something like xact.c does, which is to have
somewhere in proc.c a call like this:TimeoutRegister(DEADLOCK_TIMEOUT, 1, true, CheckDeadLock, GetCurrentTimestamp);
at process startup (the magic value 1 is the priority. Maybe there's a
better way to handle this). That way, timeout.c or timeout.h do not
need to know anything about proc.c at all; we don't need proctimeout.h
at all. The only thing it (the timeout module) needs to know is that
there is a symbolic constant named DEADLOCK_TIMEOUT. Similarly for
statement timeout, etc.When you call enable_timeout you first have to ensure that
DEADLOCK_TIMEOUT has been registered; and if it's not, die on an
Assert(). That way you ensure that there are no bugs where you try to
enable a timeout that hasn't been registered.
Currently, TimeoutName (as an index value) and "priority" is the same
semantically.
I would also add an Assert into register_timeout() to avoid double registration
of the same to prevent overriding he callback function pointer. If that's in
place, the TimeoutName value may still work as is: an index into base_timeouts[].
(If we later find the need to let external modules add timeouts, which I
find unlikely, we can have TimeoutRegister return TimeoutName and have
this value be dynamically allocated. But for now I think that would be
useless complication).The difference with lwlocks is that each lwlock is just a number.
Strictly speaking, just as TimeoutName.
The
lwlock.c module doesn't need to know anything about how each lwlock is
actually used. It's not the same thing here -- which is why I think it
would be better to have all modules, even the hardcoded ones, use a
registering interface.
OK.
... Now, it could be argued that it would be even better to have
TimeoutRegister not take the TimeoutName argument, and instead generate
it dynamically and pass it back to the caller -- that way you don't need
the enum in timeout.h.
This was what I had in mind at first ...
The problem I have with that idea is that it
would force the caller modules to have a global variable to keep track
of that, which seems worse to me.
... and realized this as well.
So, should I keep the enum TimeoutName? Are global variables for
keeping dynamically assigned values preferred over the enum?
Currently we have 5 timeout sources in total, 3 of them are used by
regular backends, the remaining 2 are used by replication standby.
We can have a fixed size array (say with 8 or 16 elements) for future use
and this would be plenty.
Opinions?
I haven't looked at the second patch at all yet.
This is the whole point of the first patch.
I know that. But I want to get the details of the framework right
before we move on to add more stuff to it.But as I said above for
registering a new timeout source, it's a new internal use case.
One that touches a lot of places which cannot simply be done
by registering a new timeout source.Sure. That's going to be the case for any other timeout we want to add
(which is why I think we don't really need dynamic timeouts).
--
----------------------------------
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/
Excerpts from Boszormenyi Zoltan's message of mar jun 26 12:43:34 -0400 2012:
So, should I keep the enum TimeoutName? Are global variables for
keeping dynamically assigned values preferred over the enum?
Currently we have 5 timeout sources in total, 3 of them are used by
regular backends, the remaining 2 are used by replication standby.
We can have a fixed size array (say with 8 or 16 elements) for future use
and this would be plenty.Opinions?
My opinion is that the fixed size array is fine.
I'll go set the patch "waiting on author". Also, remember to review
some other people's patches.
--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support
2012-06-26 18:49 keltezéssel, Alvaro Herrera írta:
Excerpts from Boszormenyi Zoltan's message of mar jun 26 12:43:34 -0400 2012:
So, should I keep the enum TimeoutName? Are global variables for
keeping dynamically assigned values preferred over the enum?
Currently we have 5 timeout sources in total, 3 of them are used by
regular backends, the remaining 2 are used by replication standby.
We can have a fixed size array (say with 8 or 16 elements) for future use
and this would be plenty.Opinions?
My opinion is that the fixed size array is fine.
Attached is the version which uses a registration interface.
Also, to further minimize knowledge of timeouts in timeout.c,
all GUCs are moved back to proc.c
I'll go set the patch "waiting on author". Also, remember to review
some other people's patches.
I will look into it.
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/
Attachments:
1-timeout-framework-v13.patchtext/x-patch; name=1-timeout-framework-v13.patchDownload
diff -dcrpN postgresql/src/backend/postmaster/autovacuum.c postgresql.1/src/backend/postmaster/autovacuum.c
*** postgresql/src/backend/postmaster/autovacuum.c 2012-06-26 09:10:21.275759379 +0200
--- postgresql.1/src/backend/postmaster/autovacuum.c 2012-06-27 08:34:13.982510939 +0200
***************
*** 89,94 ****
--- 89,95 ----
#include "storage/proc.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
+ #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
*************** AutoVacLauncherMain(int argc, char *argv
*** 484,490 ****
/* Forget any pending QueryCancel request */
QueryCancelPending = false;
! disable_sig_alarm(true);
QueryCancelPending = false; /* again in case timeout occurred */
/* Report the error to the server log */
--- 485,491 ----
/* Forget any pending QueryCancel request */
QueryCancelPending = false;
! disable_all_timeouts(false);
QueryCancelPending = false; /* again in case timeout occurred */
/* Report the error to the server log */
diff -dcrpN postgresql/src/backend/postmaster/postmaster.c postgresql.1/src/backend/postmaster/postmaster.c
*** postgresql/src/backend/postmaster/postmaster.c 2012-06-26 09:10:21.277759395 +0200
--- postgresql.1/src/backend/postmaster/postmaster.c 2012-06-27 08:34:13.985510958 +0200
***************
*** 112,118 ****
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
! #include "storage/proc.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/datetime.h"
--- 112,118 ----
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
! #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/datetime.h"
*************** BackendInitialize(Port *port)
*** 3437,3442 ****
--- 3437,3450 ----
PG_SETMASK(&StartupBlockSig);
/*
+ * Initialize timeout sources and register authentication timeout early.
+ */
+ init_timeouts();
+ register_timeout(AUTHENTICATION_TIMEOUT, false,
+ DummyTimeoutFunction,
+ GetCurrentTimestamp);
+
+ /*
* Get the remote host name and port for logging and status display.
*/
remote_host[0] = '\0';
*************** BackendInitialize(Port *port)
*** 3488,3495 ****
* indefinitely. PreAuthDelay and any DNS interactions above don't count
* against the time limit.
*/
! if (!enable_sig_alarm(AuthenticationTimeout * 1000, false))
! elog(FATAL, "could not set timer for startup packet timeout");
/*
* Receive the startup packet (which might turn out to be a cancel request
--- 3496,3502 ----
* indefinitely. PreAuthDelay and any DNS interactions above don't count
* against the time limit.
*/
! enable_timeout(AUTHENTICATION_TIMEOUT, AuthenticationTimeout * 1000);
/*
* Receive the startup packet (which might turn out to be a cancel request
*************** BackendInitialize(Port *port)
*** 3526,3533 ****
/*
* Disable the timeout, and prevent SIGTERM/SIGQUIT again.
*/
! if (!disable_sig_alarm(false))
! elog(FATAL, "could not disable timer for startup packet timeout");
PG_SETMASK(&BlockSig);
}
--- 3533,3539 ----
/*
* Disable the timeout, and prevent SIGTERM/SIGQUIT again.
*/
! disable_timeout(AUTHENTICATION_TIMEOUT, false);
PG_SETMASK(&BlockSig);
}
diff -dcrpN postgresql/src/backend/postmaster/startup.c postgresql.1/src/backend/postmaster/startup.c
*** postgresql/src/backend/postmaster/startup.c 2012-04-16 19:57:22.442915536 +0200
--- postgresql.1/src/backend/postmaster/startup.c 2012-06-27 10:22:43.730344717 +0200
***************
*** 28,34 ****
--- 28,36 ----
#include "storage/latch.h"
#include "storage/pmsignal.h"
#include "storage/proc.h"
+ #include "storage/timeout.h"
#include "utils/guc.h"
+ #include "utils/timestamp.h"
/*
*************** StartupProcessMain(void)
*** 195,201 ****
pqsignal(SIGTERM, StartupProcShutdownHandler); /* request shutdown */
pqsignal(SIGQUIT, startupproc_quickdie); /* hard crash time */
if (EnableHotStandby)
! pqsignal(SIGALRM, handle_standby_sig_alarm); /* ignored unless
* InHotStandby */
else
pqsignal(SIGALRM, SIG_IGN);
--- 197,203 ----
pqsignal(SIGTERM, StartupProcShutdownHandler); /* request shutdown */
pqsignal(SIGQUIT, startupproc_quickdie); /* hard crash time */
if (EnableHotStandby)
! pqsignal(SIGALRM, handle_sig_alarm); /* ignored unless
* InHotStandby */
else
pqsignal(SIGALRM, SIG_IGN);
*************** StartupProcessMain(void)
*** 204,209 ****
--- 206,222 ----
pqsignal(SIGUSR2, StartupProcTriggerHandler);
/*
+ * Initialize timeouts and register timeouts needed for standby.
+ */
+ init_timeouts();
+ register_timeout(STANDBY_DEADLOCK_TIMEOUT, true,
+ StandbyDeadLockFunction,
+ GetCurrentTimestamp);
+ register_timeout(STANDBY_TIMEOUT, false,
+ StandbyTimeoutFunction,
+ GetCurrentTimestamp);
+
+ /*
* Reset some signals that are accepted by postmaster but not here
*/
pqsignal(SIGCHLD, SIG_DFL);
diff -dcrpN postgresql/src/backend/storage/ipc/standby.c postgresql.1/src/backend/storage/ipc/standby.c
*** postgresql/src/backend/storage/ipc/standby.c 2012-06-26 09:10:21.280759421 +0200
--- postgresql.1/src/backend/storage/ipc/standby.c 2012-06-27 08:34:13.986510964 +0200
***************
*** 27,32 ****
--- 27,33 ----
#include "storage/procarray.h"
#include "storage/sinvaladt.h"
#include "storage/standby.h"
+ #include "storage/timeout.h"
#include "utils/ps_status.h"
#include "utils/timestamp.h"
*************** ResolveRecoveryConflictWithLock(Oid dbOi
*** 394,400 ****
void
ResolveRecoveryConflictWithBufferPin(void)
{
- bool sig_alarm_enabled = false;
TimestampTz ltime;
TimestampTz now;
--- 395,400 ----
*************** ResolveRecoveryConflictWithBufferPin(voi
*** 409,418 ****
* We're willing to wait forever for conflicts, so set timeout for
* deadlock check (only)
*/
! if (enable_standby_sig_alarm(now, now, true))
! sig_alarm_enabled = true;
! else
! elog(FATAL, "could not set timer for process wakeup");
}
else if (now >= ltime)
{
--- 409,415 ----
* We're willing to wait forever for conflicts, so set timeout for
* deadlock check (only)
*/
! enable_timeout(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout);
}
else if (now >= ltime)
{
*************** ResolveRecoveryConflictWithBufferPin(voi
*** 423,446 ****
}
else
{
/*
* Wake up at ltime, and check for deadlocks as well if we will be
* waiting longer than deadlock_timeout
*/
! if (enable_standby_sig_alarm(now, ltime, false))
! sig_alarm_enabled = true;
! else
! elog(FATAL, "could not set timer for process wakeup");
}
/* Wait to be signaled by UnpinBuffer() */
ProcWaitForSignal();
! if (sig_alarm_enabled)
! {
! if (!disable_standby_sig_alarm())
! elog(FATAL, "could not disable timer for process wakeup");
! }
}
void
--- 420,445 ----
}
else
{
+ long secs,
+ msecs;
+ int usecs;
+
/*
* Wake up at ltime, and check for deadlocks as well if we will be
* waiting longer than deadlock_timeout
*/
! enable_timeout(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout);
!
! TimestampDifference(now, ltime, &secs, &usecs);
! msecs = secs * 1000 + usecs / 1000;
!
! enable_timeout(STANDBY_TIMEOUT, msecs);
}
/* Wait to be signaled by UnpinBuffer() */
ProcWaitForSignal();
! disable_all_timeouts(false);
}
void
diff -dcrpN postgresql/src/backend/storage/lmgr/Makefile postgresql.1/src/backend/storage/lmgr/Makefile
*** postgresql/src/backend/storage/lmgr/Makefile 2012-04-16 19:57:22.458915722 +0200
--- postgresql.1/src/backend/storage/lmgr/Makefile 2012-06-27 08:34:13.987510970 +0200
*************** subdir = src/backend/storage/lmgr
*** 12,18 ****
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
! OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o predicate.o
include $(top_srcdir)/src/backend/common.mk
--- 12,18 ----
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
! OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o predicate.o timeout.o
include $(top_srcdir)/src/backend/common.mk
diff -dcrpN postgresql/src/backend/storage/lmgr/proc.c postgresql.1/src/backend/storage/lmgr/proc.c
*** postgresql/src/backend/storage/lmgr/proc.c 2012-06-26 09:10:21.282759438 +0200
--- postgresql.1/src/backend/storage/lmgr/proc.c 2012-06-27 09:21:15.411010772 +0200
***************
*** 48,53 ****
--- 48,54 ----
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/spin.h"
+ #include "storage/timeout.h"
#include "utils/timestamp.h"
*************** PGPROC *PreparedXactProcs = NULL;
*** 77,103 ****
/* If we are waiting for a lock, this points to the associated LOCALLOCK */
static LOCALLOCK *lockAwaited = NULL;
! /* Mark these volatile because they can be changed by signal handler */
! static volatile bool standby_timeout_active = false;
! static volatile bool statement_timeout_active = false;
! static volatile bool deadlock_timeout_active = false;
! static volatile DeadLockState deadlock_state = DS_NOT_YET_CHECKED;
! volatile bool cancel_from_timeout = false;
!
! /* timeout_start_time is set when log_lock_waits is true */
! static TimestampTz timeout_start_time;
!
! /* statement_fin_time is valid only if statement_timeout_active is true */
! static TimestampTz statement_fin_time;
! static TimestampTz statement_fin_time2; /* valid only in recovery */
!
static void RemoveProcFromArray(int code, Datum arg);
static void ProcKill(int code, Datum arg);
static void AuxiliaryProcKill(int code, Datum arg);
- static bool CheckStatementTimeout(void);
- static bool CheckStandbyTimeout(void);
-
/*
* Report shared-memory space needed by InitProcGlobal.
--- 78,89 ----
/* If we are waiting for a lock, this points to the associated LOCALLOCK */
static LOCALLOCK *lockAwaited = NULL;
! /* Mark this volatile because it can be changed by signal handler */
! static volatile DeadLockState deadlock_state;
static void RemoveProcFromArray(int code, Datum arg);
static void ProcKill(int code, Datum arg);
static void AuxiliaryProcKill(int code, Datum arg);
/*
* Report shared-memory space needed by InitProcGlobal.
*************** LockErrorCleanup(void)
*** 653,659 ****
return;
/* Turn off the deadlock timer, if it's still running (see ProcSleep) */
! disable_sig_alarm(false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
--- 639,645 ----
return;
/* Turn off the deadlock timer, if it's still running (see ProcSleep) */
! disable_timeout(DEADLOCK_TIMEOUT, false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1048,1055 ****
* 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 (!enable_sig_alarm(DeadlockTimeout, false))
! elog(FATAL, "could not set timer for process wakeup");
/*
* If someone wakes us between LWLockRelease and PGSemaphoreLock,
--- 1034,1040 ----
* By delaying the check until we've waited for a bit, we can avoid
* running the rather expensive deadlock-check code in most cases.
*/
! enable_timeout(DEADLOCK_TIMEOUT, DeadlockTimeout);
/*
* If someone wakes us between LWLockRelease and PGSemaphoreLock,
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1138,1144 ****
DescribeLockTag(&buf, &locallock->tag.lock);
modename = GetLockmodeName(locallock->tag.lock.locktag_lockmethodid,
lockmode);
! TimestampDifference(timeout_start_time, GetCurrentTimestamp(),
&secs, &usecs);
msecs = secs * 1000 + usecs / 1000;
usecs = usecs % 1000;
--- 1123,1130 ----
DescribeLockTag(&buf, &locallock->tag.lock);
modename = GetLockmodeName(locallock->tag.lock.locktag_lockmethodid,
lockmode);
! TimestampDifference(get_timeout_start(DEADLOCK_TIMEOUT),
! GetCurrentTimestamp(),
&secs, &usecs);
msecs = secs * 1000 + usecs / 1000;
usecs = usecs % 1000;
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1200,1207 ****
/*
* Disable the timer, if it's still running
*/
! if (!disable_sig_alarm(false))
! elog(FATAL, "could not disable timer for process wakeup");
/*
* Re-acquire the lock table's partition lock. We have to do this to hold
--- 1186,1192 ----
/*
* Disable the timer, if it's still running
*/
! disable_timeout(DEADLOCK_TIMEOUT, false);
/*
* Re-acquire the lock table's partition lock. We have to do this to hold
*************** ProcLockWakeup(LockMethod lockMethodTabl
*** 1344,1350 ****
* NB: this is run inside a signal handler, so be very wary about what is done
* here or in called routines.
*/
! static void
CheckDeadLock(void)
{
int i;
--- 1329,1335 ----
* NB: this is run inside a signal handler, so be very wary about what is done
* here or in called routines.
*/
! void
CheckDeadLock(void)
{
int i;
*************** ProcSendSignal(int pid)
*** 1499,1816 ****
PGSemaphoreUnlock(&proc->sem);
}
-
- /*****************************************************************************
- * SIGALRM interrupt support
- *
- * Maybe these should be in pqsignal.c?
- *****************************************************************************/
-
- /*
- * Enable the SIGALRM interrupt to fire after the specified delay
- *
- * Delay is given in milliseconds. Caller should be sure a SIGALRM
- * signal handler is installed before this is called.
- *
- * This code properly handles nesting of deadlock timeout alarms within
- * statement timeout alarms.
- *
- * Returns TRUE if okay, FALSE on failure.
- */
- bool
- enable_sig_alarm(int delayms, bool is_statement_timeout)
- {
- TimestampTz fin_time;
- struct itimerval timeval;
-
- if (is_statement_timeout)
- {
- /*
- * Begin statement-level timeout
- *
- * Note that we compute statement_fin_time with reference to the
- * statement_timestamp, but apply the specified delay without any
- * correction; that is, we ignore whatever time has elapsed since
- * statement_timestamp was set. In the normal case only a small
- * interval will have elapsed and so this doesn't matter, but there
- * are corner cases (involving multi-statement query strings with
- * embedded COMMIT or ROLLBACK) where we might re-initialize the
- * statement timeout long after initial receipt of the message. In
- * such cases the enforcement of the statement timeout will be a bit
- * inconsistent. This annoyance is judged not worth the cost of
- * performing an additional gettimeofday() here.
- */
- Assert(!deadlock_timeout_active);
- fin_time = GetCurrentStatementStartTimestamp();
- fin_time = TimestampTzPlusMilliseconds(fin_time, delayms);
- statement_fin_time = fin_time;
- cancel_from_timeout = false;
- statement_timeout_active = true;
- }
- else if (statement_timeout_active)
- {
- /*
- * Begin deadlock timeout with statement-level timeout active
- *
- * Here, we want to interrupt at the closer of the two timeout times.
- * If fin_time >= statement_fin_time then we need not touch the
- * existing timer setting; else set up to interrupt at the deadlock
- * timeout time.
- *
- * NOTE: in this case it is possible that this routine will be
- * interrupted by the previously-set timer alarm. This is okay
- * because the signal handler will do only what it should do according
- * to the state variables. The deadlock checker may get run earlier
- * than normal, but that does no harm.
- */
- timeout_start_time = GetCurrentTimestamp();
- fin_time = TimestampTzPlusMilliseconds(timeout_start_time, delayms);
- deadlock_timeout_active = true;
- if (fin_time >= statement_fin_time)
- return true;
- }
- else
- {
- /* Begin deadlock timeout with no statement-level timeout */
- deadlock_timeout_active = true;
- /* GetCurrentTimestamp can be expensive, so only do it if we must */
- if (log_lock_waits)
- timeout_start_time = GetCurrentTimestamp();
- }
-
- /* If we reach here, okay to set the timer interrupt */
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = delayms / 1000;
- timeval.it_value.tv_usec = (delayms % 1000) * 1000;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- return true;
- }
-
- /*
- * Cancel the SIGALRM timer, either for a deadlock timeout or a statement
- * timeout. If a deadlock timeout is canceled, any active statement timeout
- * remains in force.
- *
- * Returns TRUE if okay, FALSE on failure.
- */
- bool
- disable_sig_alarm(bool is_statement_timeout)
- {
- /*
- * Always disable the interrupt if it is active; this avoids being
- * interrupted by the signal handler and thereby possibly getting
- * confused.
- *
- * We will re-enable the interrupt if necessary in CheckStatementTimeout.
- */
- if (statement_timeout_active || deadlock_timeout_active)
- {
- struct itimerval timeval;
-
- MemSet(&timeval, 0, sizeof(struct itimerval));
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- {
- statement_timeout_active = false;
- cancel_from_timeout = false;
- deadlock_timeout_active = false;
- return false;
- }
- }
-
- /* Always cancel deadlock timeout, in case this is error cleanup */
- deadlock_timeout_active = false;
-
- /* Cancel or reschedule statement timeout */
- if (is_statement_timeout)
- {
- statement_timeout_active = false;
- cancel_from_timeout = false;
- }
- else if (statement_timeout_active)
- {
- if (!CheckStatementTimeout())
- return false;
- }
- return true;
- }
-
-
/*
* Check for statement timeout. If the timeout time has come,
! * trigger a query-cancel interrupt; if not, reschedule the SIGALRM
! * interrupt to occur at the right time.
! *
! * Returns true if okay, false if failed to set the interrupt.
*/
! static bool
! CheckStatementTimeout(void)
{
- TimestampTz now;
-
- if (!statement_timeout_active)
- return true; /* do nothing if not active */
-
- now = GetCurrentTimestamp();
-
- if (now >= statement_fin_time)
- {
- /* Time to die */
- statement_timeout_active = false;
- cancel_from_timeout = true;
#ifdef HAVE_SETSID
! /* try to signal whole process group */
! kill(-MyProcPid, SIGINT);
#endif
! kill(MyProcPid, SIGINT);
! }
! else
! {
! /* Not time yet, so (re)schedule the interrupt */
! long secs;
! int usecs;
! struct itimerval timeval;
!
! TimestampDifference(now, statement_fin_time,
! &secs, &usecs);
!
! /*
! * It's possible that the difference is less than a microsecond;
! * ensure we don't cancel, rather than set, the interrupt.
! */
! if (secs == 0 && usecs == 0)
! usecs = 1;
! MemSet(&timeval, 0, sizeof(struct itimerval));
! timeval.it_value.tv_sec = secs;
! timeval.it_value.tv_usec = usecs;
! if (setitimer(ITIMER_REAL, &timeval, NULL))
! return false;
! }
!
! return true;
}
-
/*
! * Signal handler for SIGALRM for normal user backends
! *
! * Process deadlock check and/or statement timeout check, as needed.
! * To avoid various edge cases, we must be careful to do nothing
! * when there is nothing to be done. We also need to be able to
! * reschedule the timer interrupt if called before end of statement.
*/
void
! handle_sig_alarm(SIGNAL_ARGS)
! {
! int save_errno = errno;
!
! /* SIGALRM is cause for waking anything waiting on the process latch */
! if (MyProc)
! SetLatch(&MyProc->procLatch);
!
! if (deadlock_timeout_active)
! {
! deadlock_timeout_active = false;
! CheckDeadLock();
! }
!
! if (statement_timeout_active)
! (void) CheckStatementTimeout();
!
! errno = save_errno;
! }
!
! /*
! * Signal handler for SIGALRM in Startup process
! *
! * To avoid various edge cases, we must be careful to do nothing
! * when there is nothing to be done. We also need to be able to
! * reschedule the timer interrupt if called before end of statement.
! *
! * We set either deadlock_timeout_active or statement_timeout_active
! * or both. Interrupts are enabled if standby_timeout_active.
! */
! bool
! enable_standby_sig_alarm(TimestampTz now, TimestampTz fin_time, bool deadlock_only)
! {
! TimestampTz deadlock_time = TimestampTzPlusMilliseconds(now,
! DeadlockTimeout);
!
! if (deadlock_only)
! {
! /*
! * Wake up at deadlock_time only, then wait forever
! */
! statement_fin_time = deadlock_time;
! deadlock_timeout_active = true;
! statement_timeout_active = false;
! }
! else if (fin_time > deadlock_time)
! {
! /*
! * Wake up at deadlock_time, then again at fin_time
! */
! statement_fin_time = deadlock_time;
! statement_fin_time2 = fin_time;
! deadlock_timeout_active = true;
! statement_timeout_active = true;
! }
! else
! {
! /*
! * Wake only at fin_time because its fairly soon
! */
! statement_fin_time = fin_time;
! deadlock_timeout_active = false;
! statement_timeout_active = true;
! }
!
! if (deadlock_timeout_active || statement_timeout_active)
! {
! long secs;
! int usecs;
! struct itimerval timeval;
!
! TimestampDifference(now, statement_fin_time,
! &secs, &usecs);
! if (secs == 0 && usecs == 0)
! usecs = 1;
! MemSet(&timeval, 0, sizeof(struct itimerval));
! timeval.it_value.tv_sec = secs;
! timeval.it_value.tv_usec = usecs;
! if (setitimer(ITIMER_REAL, &timeval, NULL))
! return false;
! standby_timeout_active = true;
! }
!
! return true;
! }
!
! bool
! disable_standby_sig_alarm(void)
{
! /*
! * Always disable the interrupt if it is active; this avoids being
! * interrupted by the signal handler and thereby possibly getting
! * confused.
! *
! * We will re-enable the interrupt if necessary in CheckStandbyTimeout.
! */
! if (standby_timeout_active)
! {
! struct itimerval timeval;
!
! MemSet(&timeval, 0, sizeof(struct itimerval));
! if (setitimer(ITIMER_REAL, &timeval, NULL))
! {
! standby_timeout_active = false;
! return false;
! }
! }
!
! standby_timeout_active = false;
!
! return true;
}
/*
--- 1484,1512 ----
PGSemaphoreUnlock(&proc->sem);
}
/*
* Check for statement timeout. If the timeout time has come,
! * trigger a query-cancel interrupt.
*/
! void
! StatementTimeoutFunction(void)
{
#ifdef HAVE_SETSID
! /* try to signal whole process group */
! kill(-MyProcPid, SIGINT);
#endif
! kill(MyProcPid, SIGINT);
}
/*
! * CheckStandbyDeadLock() will trigger if the deadlock timeout
! * happens earlier than StandbyTimeout. If it triggers,
! * StandbyTimeout will still be rescheduled.
*/
void
! StandbyDeadLockFunction(void)
{
! SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
}
/*
*************** disable_standby_sig_alarm(void)
*** 1818,1898 ****
* SIGALRM handler. Timers will only be set when InHotStandby.
* We simply ignore any signals unless the timer has been set.
*/
- static bool
- CheckStandbyTimeout(void)
- {
- TimestampTz now;
- bool reschedule = false;
-
- standby_timeout_active = false;
-
- now = GetCurrentTimestamp();
-
- /*
- * Reschedule the timer if its not time to wake yet, or if we have both
- * timers set and the first one has just been reached.
- */
- if (now >= statement_fin_time)
- {
- if (deadlock_timeout_active)
- {
- /*
- * We're still waiting when we reach deadlock timeout, so send out
- * a request to have other backends check themselves for deadlock.
- * Then continue waiting until statement_fin_time, if that's set.
- */
- SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
- deadlock_timeout_active = false;
-
- /*
- * Begin second waiting period if required.
- */
- if (statement_timeout_active)
- {
- reschedule = true;
- statement_fin_time = statement_fin_time2;
- }
- }
- else
- {
- /*
- * We've now reached statement_fin_time, so ask all conflicts to
- * leave, so we can press ahead with applying changes in recovery.
- */
- SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
- }
- }
- else
- reschedule = true;
-
- if (reschedule)
- {
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- standby_timeout_active = true;
- }
-
- return true;
- }
-
void
! handle_standby_sig_alarm(SIGNAL_ARGS)
{
! int save_errno = errno;
!
! if (standby_timeout_active)
! (void) CheckStandbyTimeout();
!
! errno = save_errno;
}
--- 1514,1521 ----
* SIGALRM handler. Timers will only be set when InHotStandby.
* We simply ignore any signals unless the timer has been set.
*/
void
! StandbyTimeoutFunction(void)
{
! SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
}
diff -dcrpN postgresql/src/backend/storage/lmgr/timeout.c postgresql.1/src/backend/storage/lmgr/timeout.c
*** postgresql/src/backend/storage/lmgr/timeout.c 1970-01-01 01:00:00.000000000 +0100
--- postgresql.1/src/backend/storage/lmgr/timeout.c 2012-06-27 09:55:21.784879822 +0200
***************
*** 0 ****
--- 1,448 ----
+ /*-------------------------------------------------------------------------
+ *
+ * timeout.c
+ * routines to manage timeout sources handled by SIGALRM
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/storage/lmgr/timeout.c
+ *
+ *-------------------------------------------------------------------------
+ */
+ #include "postgres.h"
+
+ #include <sys/time.h>
+
+ #include "access/xact.h"
+ #include "storage/proc.h"
+ #include "storage/timeout.h"
+ #include "utils/timestamp.h"
+
+ /*
+ * Infrastructure for timeouts
+ */
+
+ static void InitTimeout(TimeoutName tn, TimestampTz start_time,
+ TimestampTz fin_time);
+ static void DestroyTimeout(TimeoutName tn, bool keep_indicator);
+
+ typedef void (*timeout_func)(void);
+ typedef TimestampTz (*timeout_start)(void);
+
+ typedef struct timeout_params
+ {
+ TimeoutName index;
+ bool resched_next;
+
+ /* volatile because it may be changed from the signal handler */
+ volatile bool indicator;
+
+ timeout_func timeout_func;
+ timeout_start timeout_start;
+
+ TimestampTz start_time;
+ TimestampTz fin_time;
+ } timeout_params;
+
+ /*
+ * List of possible timeout reasons in the order of enum TimeoutName.
+ * The priority of timeouts (in case two of them would trigger at the
+ * same time) is determined by this order: the earlier one in the list
+ * has higher priority. InitializeBackend() and PostgresMain() initialize
+ * this array and register timeout sources.
+ */
+ static bool base_timeouts_initialized = false;
+ static timeout_params base_timeouts[TIMEOUT_MAX];
+
+ /*
+ * List of active timeouts ordered by their fin_time and priority.
+ */
+ static int n_timeouts = 0;
+ static timeout_params *timeouts[TIMEOUT_MAX];
+
+ /*****************************************************************************
+ * Internal helper functions
+ *****************************************************************************/
+
+ /*
+ * Find the index of a given timeout type in the active array
+ */
+ static int
+ find_active_timeout(TimeoutName tn)
+ {
+ int i;
+
+ for (i = 0; i < n_timeouts; i++)
+ {
+ if (timeouts[i]->index == tn)
+ return i;
+ }
+
+ return -1;
+ }
+
+ #define is_timeout_active(tn) (find_active_timeout(tn) >= 0)
+
+ /*
+ * Insert tn'th timeout into the list of timeouts at the given index
+ * if the previous timeout allows rescheduling the next one in the list.
+ */
+ static void
+ insert_timeout(TimeoutName tn, int index)
+ {
+ int i;
+
+ if (index < 0 || index > n_timeouts)
+ elog(FATAL, "timeout index %d out of range 0..%d", index, n_timeouts);
+
+ if (index > 0 && !timeouts[index - 1]->resched_next)
+ return;
+
+ for (i = n_timeouts - 1; i >= index; i--)
+ timeouts[i + 1] = timeouts[i];
+
+ timeouts[index] = &base_timeouts[tn];
+ n_timeouts++;
+ }
+
+ /*
+ * Remove the index'th element from the timeout list.
+ */
+ static void
+ remove_timeout_index(int index)
+ {
+ int i;
+
+ if (index < 0 || index > n_timeouts)
+ elog(FATAL, "timeout index %d out of range 0..%d", index, n_timeouts);
+
+ for (i = index + 1; i < n_timeouts; i++)
+ timeouts[i - 1] = timeouts[i];
+
+ if (n_timeouts > 0 && index < n_timeouts)
+ n_timeouts--;
+ }
+
+ /*
+ * (Re)schedule the next active timeout
+ */
+ static void
+ schedule_timeout(TimestampTz now)
+ {
+ long secs;
+ int usecs;
+ struct itimerval timeval;
+
+ /* There is no active timeout, do nothing. */
+ if (n_timeouts == 0)
+ return;
+
+ TimestampDifference(now, timeouts[0]->fin_time,
+ &secs, &usecs);
+
+ /*
+ * It's possible that the difference is less than a microsecond;
+ * ensure we don't cancel, rather than set, the interrupt.
+ */
+ if (secs == 0 && usecs == 0)
+ usecs = 1;
+
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ timeval.it_value.tv_sec = secs;
+ timeval.it_value.tv_usec = usecs;
+ if (setitimer(ITIMER_REAL, &timeval, NULL) != 0)
+ elog(FATAL, "could not enable timer");
+ }
+
+ /*****************************************************************************
+ * Init, Destroy and callback functions for different timeouts.
+ *****************************************************************************/
+
+ /*
+ * Common Init and Destroy functions
+ */
+
+ static void
+ InitTimeout(TimeoutName tn, TimestampTz start_time, TimestampTz fin_time)
+ {
+ base_timeouts[tn].indicator = false;
+ base_timeouts[tn].start_time = start_time;
+ base_timeouts[tn].fin_time = fin_time;
+ }
+
+ static void
+ DestroyTimeout(TimeoutName tn, bool keep_indicator)
+ {
+ if (!keep_indicator)
+ base_timeouts[tn].indicator = false;
+ base_timeouts[tn].start_time = 0;
+ base_timeouts[tn].fin_time = 0;
+ }
+
+ /*
+ * CheckDummyTimeout
+ *
+ * Do nothing. Usable for timeout sources that don't need
+ * extra processing, like authentication_timeout.
+ * The timeout indicator is set by the signal handler.
+ */
+ void
+ DummyTimeoutFunction(void)
+ {
+ return;
+ }
+
+ /*****************************************************************************
+ * Public API
+ *****************************************************************************/
+
+ /*
+ * Initialize
+ */
+ void
+ init_timeouts(void)
+ {
+ int i;
+
+ if (base_timeouts_initialized)
+ return;
+
+ for (i = 0; i < TIMEOUT_MAX; i++)
+ {
+ base_timeouts[i].index = i;
+ base_timeouts[i].resched_next = false;
+ base_timeouts[i].indicator = false;
+ base_timeouts[i].timeout_func = NULL;
+ base_timeouts[i].timeout_start = NULL;
+ base_timeouts[i].start_time = 0;
+ base_timeouts[i].fin_time = 0;
+ }
+
+ base_timeouts_initialized = true;
+ }
+
+ /*
+ * Register a timeout source
+ */
+ int
+ register_timeout(TimeoutName tn, bool resched_next,
+ timeout_func func, timeout_start start)
+ {
+ Assert(base_timeouts_initialized);
+ Assert(base_timeouts[tn].timeout_func == NULL);
+
+ if (tn < 0)
+ {
+ for (tn = USER_TIMEOUT; tn < TIMEOUT_MAX; tn++)
+ if (base_timeouts[tn].timeout_func == 0)
+ break;
+ if (tn == TIMEOUT_MAX)
+ elog(FATAL, "Cannot add more timeout source");
+ }
+
+ base_timeouts[tn].resched_next = resched_next;
+ base_timeouts[tn].timeout_func = func;
+ base_timeouts[tn].timeout_start = start;
+
+ return tn;
+ }
+
+ /*
+ * Enable the SIGALRM interrupt to fire after the specified delay
+ *
+ * Delay is given in milliseconds. Caller should be sure a SIGALRM
+ * signal handler is installed before this is called.
+ *
+ * This code properly handles nesting of different timeout alarms.
+ */
+ void
+ enable_timeout(TimeoutName tn, int delayms)
+ {
+ TimestampTz start_time;
+ TimestampTz fin_time;
+ int i;
+
+ Assert(base_timeouts_initialized);
+ Assert(base_timeouts[tn].timeout_func != NULL);
+ Assert(!is_timeout_active(tn));
+
+ if (delayms <= 0)
+ return;
+
+ start_time = base_timeouts[tn].timeout_start();
+ fin_time = TimestampTzPlusMilliseconds(start_time, delayms);
+
+ /* Find out the index where to insert the new timeout. */
+ for (i = 0; i < n_timeouts; i++)
+ {
+ /*
+ * The new timeout triggers earlier than a previously added one:
+ * insert here.
+ */
+ if (fin_time < timeouts[i]->fin_time)
+ break;
+ /*
+ * The new timeout triggers at the same time as the previously added
+ * one but has greater priority.
+ */
+ if (fin_time == timeouts[i]->fin_time && tn < timeouts[i]->index)
+ break;
+ }
+
+ /*
+ * Initialize the timeout parameters
+ */
+ InitTimeout(tn, start_time, fin_time);
+
+ insert_timeout(tn, i);
+
+ /*
+ * If the timeout was inserted in the first position, set the timer.
+ */
+ if (i == 0)
+ schedule_timeout(start_time);
+ }
+
+ /*
+ * Cancel the SIGALRM timer for the specific timeout.
+ * If a timeout is canceled, any other active timeout remains in force.
+ *
+ * It's not an error to disable a timeout that is not enabled.
+ */
+ void
+ disable_timeout(TimeoutName tn, bool keep_indicator)
+ {
+ int i;
+
+ /*
+ * Always disable the timer if it is active; this avoids being
+ * interrupted by the signal handler and thereby possibly getting
+ * confused.
+ *
+ * We will re-enable the interrupt if necessary later in this function.
+ */
+ if (n_timeouts > 0)
+ {
+ struct itimerval timeval;
+
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ if (setitimer(ITIMER_REAL, &timeval, NULL) != 0)
+ elog(FATAL, "could not disable timer");
+ }
+
+ /* Find the timeout and remove from the list. */
+ i = find_active_timeout(tn);
+ if (i < 0)
+ return;
+ remove_timeout_index(i);
+
+ /* Do cleanup. */
+ DestroyTimeout(tn, keep_indicator);
+
+ /*
+ * If the first timeout was removed from the list and there is
+ * at least one active left, reschedule it.
+ */
+ if (i == 0 && n_timeouts > 0)
+ schedule_timeout(GetCurrentTimestamp());
+ }
+
+ /*
+ * Disable SIGALRM and remove all timeouts from the list and
+ * reset the timeout indicators.
+ */
+ void
+ disable_all_timeouts(bool keep_indicators)
+ {
+ struct itimerval timeval;
+ int i;
+
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ if (setitimer(ITIMER_REAL, &timeval, NULL) != 0)
+ elog(FATAL, "could not disable timer");
+
+ n_timeouts = 0;
+ for (i = 0; i < TIMEOUT_MAX; i++)
+ DestroyTimeout(i, keep_indicators);
+ }
+
+ /*
+ * Return the timeout indicator
+ */
+ bool
+ get_timeout_indicator(TimeoutName tn)
+ {
+ return base_timeouts[tn].indicator;
+ }
+
+ /*
+ * Return the start of the timer for this timeout
+ */
+ TimestampTz
+ get_timeout_start(TimeoutName tn)
+ {
+ return base_timeouts[tn].start_time;
+ }
+
+ /*
+ * Signal handler for SIGALRM
+ *
+ * Process the check for the currently active timeout source and
+ * reschedule the next as needed. To avoid various edge cases,
+ * we must be careful to do nothing when there is nothing to be done.
+ */
+ void
+ handle_sig_alarm(SIGNAL_ARGS)
+ {
+ int save_errno = errno;
+
+ /*
+ * SIGALRM is cause for waking anything waiting on the process latch.
+ * Cope with MyProc not being there, as the startup process also uses
+ * this signal handler.
+ */
+ if (MyProc)
+ SetLatch(&MyProc->procLatch);
+
+ if (n_timeouts > 0)
+ {
+ TimestampTz now;
+ bool reschedule;
+
+ now = GetCurrentTimestamp();
+
+ /* If our time has not come yet, do nothing. */
+ if (now < timeouts[0]->fin_time)
+ goto out;
+
+ /*
+ * Set the trigger indicator before calling the checker function.
+ * Setting if after may have side effects that lead to detecting
+ * a different event, like "user pressed Ctrl-C" is detected instead
+ * of the statement timeout having triggered.
+ */
+ timeouts[0]->indicator = true;
+
+ /* Call the timeout checker. */
+ timeouts[0]->timeout_func();
+
+ /*
+ * Remove the current timeout source and reschedule
+ * the next if needed. Short circuit disable_timeout(..., true)
+ * for the timeout source that just triggered.
+ */
+ reschedule = timeouts[0]->resched_next;
+ remove_timeout_index(0);
+
+ if (reschedule)
+ schedule_timeout(now);
+ else
+ disable_all_timeouts(true);
+ }
+
+ out:
+ errno = save_errno;
+ }
diff -dcrpN postgresql/src/backend/tcop/postgres.c postgresql.1/src/backend/tcop/postgres.c
*** postgresql/src/backend/tcop/postgres.c 2012-06-26 09:10:21.283759446 +0200
--- postgresql.1/src/backend/tcop/postgres.c 2012-06-27 10:01:22.219982497 +0200
***************
*** 64,69 ****
--- 64,70 ----
#include "storage/proc.h"
#include "storage/procsignal.h"
#include "storage/sinval.h"
+ #include "storage/timeout.h"
#include "tcop/fastpath.h"
#include "tcop/pquery.h"
#include "tcop/tcopprot.h"
*************** start_xact_command(void)
*** 2396,2404 ****
/* Set statement timeout running, if any */
/* NB: this mustn't be enabled until we are within an xact */
if (StatementTimeout > 0)
! enable_sig_alarm(StatementTimeout, true);
else
! cancel_from_timeout = false;
xact_started = true;
}
--- 2397,2405 ----
/* Set statement timeout running, if any */
/* NB: this mustn't be enabled until we are within an xact */
if (StatementTimeout > 0)
! enable_timeout(STATEMENT_TIMEOUT, StatementTimeout);
else
! disable_timeout(STATEMENT_TIMEOUT, false);
xact_started = true;
}
*************** finish_xact_command(void)
*** 2410,2416 ****
if (xact_started)
{
/* Cancel any active statement timeout before committing */
! disable_sig_alarm(true);
/* Now commit the command */
ereport(DEBUG3,
--- 2411,2417 ----
if (xact_started)
{
/* Cancel any active statement timeout before committing */
! disable_all_timeouts(false);
/* Now commit the command */
ereport(DEBUG3,
*************** ProcessInterrupts(void)
*** 2891,2897 ****
(errcode(ERRCODE_QUERY_CANCELED),
errmsg("canceling authentication due to timeout")));
}
! if (cancel_from_timeout)
{
ImmediateInterruptOK = false; /* not idle anymore */
DisableNotifyInterrupt();
--- 2892,2898 ----
(errcode(ERRCODE_QUERY_CANCELED),
errmsg("canceling authentication due to timeout")));
}
! if (get_timeout_indicator(STATEMENT_TIMEOUT))
{
ImmediateInterruptOK = false; /* not idle anymore */
DisableNotifyInterrupt();
*************** PostgresMain(int argc, char *argv[], con
*** 3617,3622 ****
--- 3618,3634 ----
pqsignal(SIGALRM, handle_sig_alarm); /* timeout conditions */
/*
+ * Register timeout sources needed by backend operation.
+ */
+ init_timeouts();
+ register_timeout(DEADLOCK_TIMEOUT, true,
+ CheckDeadLock,
+ GetCurrentTimestamp);
+ register_timeout(STATEMENT_TIMEOUT, false,
+ StatementTimeoutFunction,
+ GetCurrentStatementStartTimestamp);
+
+ /*
* Ignore failure to write to frontend. Note: if frontend closes
* connection, we will notice it and exit cleanly when control next
* returns to outer loop. This seems safer than forcing exit in the
*************** PostgresMain(int argc, char *argv[], con
*** 3802,3811 ****
/*
* Forget any pending QueryCancel request, since we're returning to
! * the idle loop anyway, and cancel the statement timer if running.
*/
QueryCancelPending = false;
! disable_sig_alarm(true);
QueryCancelPending = false; /* again in case timeout occurred */
/*
--- 3814,3823 ----
/*
* Forget any pending QueryCancel request, since we're returning to
! * the idle loop anyway, and cancel the timer if running.
*/
QueryCancelPending = false;
! disable_all_timeouts(false);
QueryCancelPending = false; /* again in case timeout occurred */
/*
diff -dcrpN postgresql/src/backend/utils/init/postinit.c postgresql.1/src/backend/utils/init/postinit.c
*** postgresql/src/backend/utils/init/postinit.c 2012-06-26 09:10:21.287759479 +0200
--- postgresql.1/src/backend/utils/init/postinit.c 2012-06-27 08:34:13.992511002 +0200
***************
*** 41,52 ****
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lmgr.h"
- #include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/proc.h"
#include "storage/sinvaladt.h"
#include "storage/smgr.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
#include "utils/fmgroids.h"
--- 41,52 ----
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lmgr.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/proc.h"
#include "storage/sinvaladt.h"
#include "storage/smgr.h"
+ #include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
#include "utils/fmgroids.h"
*************** PerformAuthentication(Port *port)
*** 205,212 ****
* during authentication. Since we're inside a transaction and might do
* database access, we have to use the statement_timeout infrastructure.
*/
! if (!enable_sig_alarm(AuthenticationTimeout * 1000, true))
! elog(FATAL, "could not set timer for authorization timeout");
/*
* Now perform authentication exchange.
--- 205,211 ----
* during authentication. Since we're inside a transaction and might do
* database access, we have to use the statement_timeout infrastructure.
*/
! enable_timeout(STATEMENT_TIMEOUT, AuthenticationTimeout * 1000);
/*
* Now perform authentication exchange.
*************** PerformAuthentication(Port *port)
*** 216,223 ****
/*
* Done with authentication. Disable the timeout, and log if needed.
*/
! if (!disable_sig_alarm(true))
! elog(FATAL, "could not disable timer for authorization timeout");
if (Log_connections)
{
--- 215,221 ----
/*
* Done with authentication. Disable the timeout, and log if needed.
*/
! disable_timeout(STATEMENT_TIMEOUT, false);
if (Log_connections)
{
diff -dcrpN postgresql/src/include/storage/proc.h postgresql.1/src/include/storage/proc.h
*** postgresql/src/include/storage/proc.h 2012-06-26 09:10:21.316759721 +0200
--- postgresql.1/src/include/storage/proc.h 2012-06-27 09:21:57.139257370 +0200
*************** extern int DeadlockTimeout;
*** 222,230 ****
extern int StatementTimeout;
extern bool log_lock_waits;
- extern volatile bool cancel_from_timeout;
-
-
/*
* Function Prototypes
*/
--- 222,227 ----
*************** extern void LockErrorCleanup(void);
*** 252,264 ****
extern void ProcWaitForSignal(void);
extern void ProcSendSignal(int pid);
! extern bool enable_sig_alarm(int delayms, bool is_statement_timeout);
! extern bool disable_sig_alarm(bool is_statement_timeout);
! extern void handle_sig_alarm(SIGNAL_ARGS);
!
! extern bool enable_standby_sig_alarm(TimestampTz now,
! TimestampTz fin_time, bool deadlock_only);
! extern bool disable_standby_sig_alarm(void);
! extern void handle_standby_sig_alarm(SIGNAL_ARGS);
#endif /* PROC_H */
--- 249,258 ----
extern void ProcWaitForSignal(void);
extern void ProcSendSignal(int pid);
! /* Timeout functions exported for register_timeout() */
! extern void CheckDeadLock(void);
! extern void StatementTimeoutFunction(void);
! extern void StandbyDeadLockFunction(void);
! extern void StandbyTimeoutFunction(void);
#endif /* PROC_H */
diff -dcrpN postgresql/src/include/storage/timeout.h postgresql.1/src/include/storage/timeout.h
*** postgresql/src/include/storage/timeout.h 1970-01-01 01:00:00.000000000 +0100
--- postgresql.1/src/include/storage/timeout.h 2012-06-27 09:49:16.789761575 +0200
***************
*** 0 ****
--- 1,46 ----
+ /*-------------------------------------------------------------------------
+ *
+ * timeout.h
+ * SIGALRM timeout API
+ *
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/storage/timeout.h
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef _TIMEOUT_H_
+ #define _TIMEOUT_H_
+
+ #include "datatype/timestamp.h"
+
+ typedef enum TimeoutName {
+ AUTHENTICATION_TIMEOUT,
+ DEADLOCK_TIMEOUT,
+ STATEMENT_TIMEOUT,
+ STANDBY_DEADLOCK_TIMEOUT,
+ STANDBY_TIMEOUT,
+ /* First user timeout source */
+ USER_TIMEOUT,
+ /* Maximum number of timeout sources */
+ TIMEOUT_MAX = 16
+ } TimeoutName;
+
+ typedef void (*timeout_func)(void);
+ typedef TimestampTz (*timeout_start)(void);
+
+ extern void init_timeouts(void);
+ extern int register_timeout(TimeoutName tn, bool resched_next,
+ timeout_func func, timeout_start start);
+ extern void enable_timeout(TimeoutName tn, int delayms);
+ extern void disable_timeout(TimeoutName tn, bool keep_indicator);
+ extern void disable_all_timeouts(bool keep_indicators);
+ extern bool get_timeout_indicator(TimeoutName tn);
+ extern TimestampTz get_timeout_start(TimeoutName tn);
+ extern void handle_sig_alarm(SIGNAL_ARGS);
+
+ extern void DummyTimeoutFunction(void);
+
+ #endif /* _TIMEOUT_H_ */
2-lock_timeout-v13.patchtext/x-patch; name=2-lock_timeout-v13.patchDownload
diff -dcrpN postgresql.1/doc/src/sgml/config.sgml postgresql.2/doc/src/sgml/config.sgml
*** postgresql.1/doc/src/sgml/config.sgml 2012-05-11 09:23:17.030668613 +0200
--- postgresql.2/doc/src/sgml/config.sgml 2012-06-27 08:54:56.836819721 +0200
*************** COPY postgres_log FROM '/full/path/to/lo
*** 4980,4986 ****
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.
</para>
<para>
--- 4980,4989 ----
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. 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>
*************** COPY postgres_log FROM '/full/path/to/lo
*** 4991,4996 ****
--- 4994,5053 ----
</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 -dcrpN 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-06-27 08:54:56.837819727 +0200
*************** LOCK [ TABLE ] [ ONLY ] <replaceable cla
*** 39,46 ****
<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.)
</para>
--- 39,49 ----
<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. 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 -dcrpN 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-06-27 08:54:56.839819738 +0200
*************** FOR SHARE [ OF <replaceable class="param
*** 1199,1204 ****
--- 1199,1212 ----
</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 -dcrpN postgresql.1/src/backend/port/posix_sema.c postgresql.2/src/backend/port/posix_sema.c
*** postgresql.1/src/backend/port/posix_sema.c 2012-04-16 19:57:22.438915489 +0200
--- postgresql.2/src/backend/port/posix_sema.c 2012-06-27 08:54:56.839819738 +0200
***************
*** 24,29 ****
--- 24,30 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/timeout.h"
#ifdef USE_NAMED_POSIX_SEMAPHORES
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 313,315 ****
--- 314,341 ----
return true;
}
+
+ /*
+ * PGSemaphoreTimedLock
+ *
+ * Lock a semaphore (decrement count), blocking if count would be < 0
+ * Return if lock_timeout expired
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ int errStatus;
+
+ do
+ {
+ ImmediateInterruptOK = interruptOK;
+ CHECK_FOR_INTERRUPTS();
+ errStatus = sem_wait(PG_SEM_REF(sema));
+ ImmediateInterruptOK = false;
+ } while (errStatus < 0 && errno == EINTR && !get_timeout_indicator(LOCK_TIMEOUT));
+
+ if (get_timeout_indicator(LOCK_TIMEOUT))
+ return;
+ if (errStatus < 0)
+ elog(FATAL, "sem_wait failed: %m");
+ }
diff -dcrpN postgresql.1/src/backend/port/sysv_sema.c postgresql.2/src/backend/port/sysv_sema.c
*** postgresql.1/src/backend/port/sysv_sema.c 2012-05-14 08:20:56.284830580 +0200
--- postgresql.2/src/backend/port/sysv_sema.c 2012-06-27 08:54:56.840819743 +0200
***************
*** 27,32 ****
--- 27,33 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/timeout.h"
#ifndef HAVE_UNION_SEMUN
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 492,494 ****
--- 493,525 ----
return true;
}
+
+ /*
+ * PGSemaphoreTimedLock
+ *
+ * Lock a semaphore (decrement count), blocking if count would be < 0
+ * Return if lock_timeout expired
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ int errStatus;
+ 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;
+ } while (errStatus < 0 && errno == EINTR && !get_timeout_indicator(LOCK_TIMEOUT));
+
+ if (get_timeout_indicator(LOCK_TIMEOUT))
+ return;
+ if (errStatus < 0)
+ elog(FATAL, "semop(id=%d) failed: %m", sema->semId);
+ }
diff -dcrpN postgresql.1/src/backend/port/win32_sema.c postgresql.2/src/backend/port/win32_sema.c
*** postgresql.1/src/backend/port/win32_sema.c 2012-06-11 06:22:48.137921483 +0200
--- postgresql.2/src/backend/port/win32_sema.c 2012-06-27 08:54:56.841819749 +0200
***************
*** 16,21 ****
--- 16,22 ----
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
+ #include "storage/timeout.h"
static HANDLE *mySemSet; /* IDs of sema sets acquired so far */
static int numSems; /* number of sema sets acquired so far */
*************** PGSemaphoreTryLock(PGSemaphore sema)
*** 209,211 ****
--- 210,271 ----
/* 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.
+ */
+ void
+ PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK)
+ {
+ DWORD ret;
+ HANDLE wh[2];
+
+ /*
+ * 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;
+ } while (errno == EINTR && !get_timeout_indicator(LOCK_TIMEOUT));
+
+ if (get_timeout_indicator(LOCK_TIMEOUT))
+ return;
+ if (errno != 0)
+ ereport(FATAL,
+ (errmsg("could not lock semaphore: error code %d", (int) GetLastError())));
+ }
diff -dcrpN postgresql.1/src/backend/storage/lmgr/lmgr.c postgresql.2/src/backend/storage/lmgr/lmgr.c
*** postgresql.1/src/backend/storage/lmgr/lmgr.c 2012-04-16 19:57:22.459915733 +0200
--- postgresql.2/src/backend/storage/lmgr/lmgr.c 2012-06-27 08:54:56.842819755 +0200
***************
*** 19,26 ****
--- 19,29 ----
#include "access/transam.h"
#include "access/xact.h"
#include "catalog/catalog.h"
+ #include "catalog/pg_database.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
+ #include "utils/lsyscache.h"
+ #include "storage/proc.h"
#include "storage/procarray.h"
#include "utils/inval.h"
*************** LockRelationOid(Oid relid, LOCKMODE lock
*** 78,83 ****
--- 81,101 ----
res = LockAcquire(&tag, lockmode, false, false);
+ if (res == LOCKACQUIRE_NOT_AVAIL)
+ {
+ char *relname = get_rel_name(relid);
+ if (relname)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ relname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation with OID %u",
+ relid)));
+ }
+
/*
* Now that we have the lock, check for invalidation messages, so that we
* will update or flush any stale relcache entry before we try to use it.
*************** LockRelation(Relation relation, LOCKMODE
*** 174,179 ****
--- 192,203 ----
res = LockAcquire(&tag, lockmode, false, false);
+ if (res == LOCKACQUIRE_NOT_AVAIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ RelationGetRelationName(relation))));
+
/*
* Now that we have the lock, check for invalidation messages; see notes
* in LockRelationOid.
*************** LockRelationIdForSession(LockRelId *reli
*** 251,257 ****
SET_LOCKTAG_RELATION(tag, relid->dbId, relid->relId);
! (void) LockAcquire(&tag, lockmode, true, false);
}
/*
--- 275,294 ----
SET_LOCKTAG_RELATION(tag, relid->dbId, relid->relId);
! if (LockAcquire(&tag, lockmode, true, false) == LOCKACQUIRE_NOT_AVAIL)
! {
! char *relname = get_rel_name(relid->relId);
! if (relname)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on relation \"%s\"",
! relname)));
! else
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on relation with OID %u",
! relid->relId)));
! }
}
/*
*************** LockRelationForExtension(Relation relati
*** 286,292 ****
relation->rd_lockInfo.lockRelId.dbId,
relation->rd_lockInfo.lockRelId.relId);
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 323,333 ----
relation->rd_lockInfo.lockRelId.dbId,
relation->rd_lockInfo.lockRelId.relId);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on index \"%s\"",
! RelationGetRelationName(relation))));
}
/*
*************** LockPage(Relation relation, BlockNumber
*** 320,326 ****
relation->rd_lockInfo.lockRelId.relId,
blkno);
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 361,371 ----
relation->rd_lockInfo.lockRelId.relId,
blkno);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on page %u of relation \"%s\"",
! blkno, RelationGetRelationName(relation))));
}
/*
*************** LockTuple(Relation relation, ItemPointer
*** 376,382 ****
ItemPointerGetBlockNumber(tid),
ItemPointerGetOffsetNumber(tid));
! (void) LockAcquire(&tag, lockmode, false, false);
}
/*
--- 421,431 ----
ItemPointerGetBlockNumber(tid),
ItemPointerGetOffsetNumber(tid));
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on row in relation \"%s\"",
! RelationGetRelationName(relation))));
}
/*
*************** XactLockTableInsert(TransactionId xid)
*** 430,436 ****
SET_LOCKTAG_TRANSACTION(tag, xid);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
}
/*
--- 479,488 ----
SET_LOCKTAG_TRANSACTION(tag, xid);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on transaction with ID %u", xid)));
}
/*
*************** XactLockTableWait(TransactionId xid)
*** 474,480 ****
SET_LOCKTAG_TRANSACTION(tag, xid);
! (void) LockAcquire(&tag, ShareLock, false, false);
LockRelease(&tag, ShareLock, false);
--- 526,535 ----
SET_LOCKTAG_TRANSACTION(tag, xid);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on transaction with ID %u", xid)));
LockRelease(&tag, ShareLock, false);
*************** LockDatabaseObject(Oid classid, Oid obji
*** 535,541 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, false, false);
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
--- 590,600 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
*************** LockSharedObject(Oid classid, Oid objid,
*** 576,582 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, false, false);
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
--- 635,645 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
*************** LockSharedObjectForSession(Oid classid,
*** 618,624 ****
objid,
objsubid);
! (void) LockAcquire(&tag, lockmode, true, false);
}
/*
--- 681,702 ----
objid,
objsubid);
! if (LockAcquire(&tag, lockmode, true, false) == LOCKACQUIRE_NOT_AVAIL)
! switch(classid)
! {
! case DatabaseRelationId:
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on database with ID %u",
! objid)));
! break;
! default:
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on class:object: %u:%u",
! classid, objid)));
! break;
! }
}
/*
diff -dcrpN postgresql.1/src/backend/storage/lmgr/lock.c postgresql.2/src/backend/storage/lmgr/lock.c
*** postgresql.1/src/backend/storage/lmgr/lock.c 2012-06-26 09:10:21.280759421 +0200
--- postgresql.2/src/backend/storage/lmgr/lock.c 2012-06-27 08:54:56.845819772 +0200
*************** static PROCLOCK *SetupLockInTable(LockMe
*** 344,350 ****
static void GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner);
static void BeginStrongLockAcquire(LOCALLOCK *locallock, uint32 fasthashcode);
static void FinishStrongLockAcquire(void);
! static void WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner);
static void ReleaseLockIfHeld(LOCALLOCK *locallock, bool sessionLock);
static void LockReassignOwner(LOCALLOCK *locallock, ResourceOwner parent);
static bool UnGrantLock(LOCK *lock, LOCKMODE lockmode,
--- 344,350 ----
static void GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner);
static void BeginStrongLockAcquire(LOCALLOCK *locallock, uint32 fasthashcode);
static void FinishStrongLockAcquire(void);
! static int WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner);
static void ReleaseLockIfHeld(LOCALLOCK *locallock, bool sessionLock);
static void LockReassignOwner(LOCALLOCK *locallock, ResourceOwner parent);
static bool UnGrantLock(LOCK *lock, LOCKMODE lockmode,
*************** ProcLockHashCode(const PROCLOCKTAG *proc
*** 551,557 ****
* dontWait: if true, don't wait to acquire lock
*
* Returns one of:
! * LOCKACQUIRE_NOT_AVAIL lock not available, and dontWait=true
* LOCKACQUIRE_OK lock successfully acquired
* LOCKACQUIRE_ALREADY_HELD incremented count for lock already held
*
--- 551,557 ----
* dontWait: if true, don't wait to acquire lock
*
* Returns one of:
! * LOCKACQUIRE_NOT_AVAIL lock not available, either dontWait=true or timeout
* LOCKACQUIRE_OK lock successfully acquired
* LOCKACQUIRE_ALREADY_HELD incremented count for lock already held
*
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 865,871 ****
locktag->locktag_type,
lockmode);
! WaitOnLock(locallock, owner);
TRACE_POSTGRESQL_LOCK_WAIT_DONE(locktag->locktag_field1,
locktag->locktag_field2,
--- 865,871 ----
locktag->locktag_type,
lockmode);
! status = WaitOnLock(locallock, owner);
TRACE_POSTGRESQL_LOCK_WAIT_DONE(locktag->locktag_field1,
locktag->locktag_field2,
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 880,907 ****
* done when the lock was granted to us --- see notes in WaitOnLock.
*/
! /*
! * Check the proclock entry status, in case something in the ipc
! * communication doesn't work correctly.
! */
! if (!(proclock->holdMask & LOCKBIT_ON(lockmode)))
{
! AbortStrongLockAcquire();
! PROCLOCK_PRINT("LockAcquire: INCONSISTENT", proclock);
! LOCK_PRINT("LockAcquire: INCONSISTENT", lock, lockmode);
! /* Should we retry ? */
! LWLockRelease(partitionLock);
! elog(ERROR, "LockAcquire failed");
}
- PROCLOCK_PRINT("LockAcquire: granted", proclock);
- LOCK_PRINT("LockAcquire: granted", lock, lockmode);
}
! /*
! * Lock state is fully up-to-date now; if we error out after this, no
! * special error cleanup is required.
! */
! FinishStrongLockAcquire();
LWLockRelease(partitionLock);
--- 880,930 ----
* done when the lock was granted to us --- see notes in WaitOnLock.
*/
! switch (status)
{
! case STATUS_OK:
! /*
! * Check the proclock entry status, in case something in the ipc
! * communication doesn't work correctly.
! */
! if (!(proclock->holdMask & LOCKBIT_ON(lockmode)))
! {
! AbortStrongLockAcquire();
! PROCLOCK_PRINT("LockAcquire: INCONSISTENT", proclock);
! LOCK_PRINT("LockAcquire: INCONSISTENT", lock, lockmode);
! /* Should we retry ? */
! LWLockRelease(partitionLock);
! elog(ERROR, "LockAcquire failed");
! }
! PROCLOCK_PRINT("LockAcquire: granted", proclock);
! LOCK_PRINT("LockAcquire: granted", lock, lockmode);
! break;
! case STATUS_WAITING:
! PROCLOCK_PRINT("LockAcquire: timed out", proclock);
! LOCK_PRINT("LockAcquire: timed out", lock, lockmode);
! break;
! default:
! elog(ERROR, "LockAcquire invalid status");
! break;
}
}
! if (status == STATUS_WAITING)
! {
! /*
! * lock_timeout was set and WaitOnLock() indicated
! * we timed out. Clean up manually.
! */
! AbortStrongLockAcquire();
! }
! else
! {
! /*
! * Lock state is fully up-to-date now; if we error out after this, no
! * special error cleanup is required.
! */
! FinishStrongLockAcquire();
! }
LWLockRelease(partitionLock);
*************** LockAcquireExtended(const LOCKTAG *lockt
*** 920,926 ****
locktag->locktag_field2);
}
! return LOCKACQUIRE_OK;
}
/*
--- 943,949 ----
locktag->locktag_field2);
}
! return (status == STATUS_OK ? LOCKACQUIRE_OK : LOCKACQUIRE_NOT_AVAIL);
}
/*
*************** GrantAwaitedLock(void)
*** 1450,1463 ****
* Caller must have set MyProc->heldLocks to reflect locks already held
* on the lockable object by this process.
*
* The appropriate partition lock must be held at entry.
*/
! static void
WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner)
{
LOCKMETHODID lockmethodid = LOCALLOCK_LOCKMETHOD(*locallock);
LockMethod lockMethodTable = LockMethods[lockmethodid];
char *volatile new_status = NULL;
LOCK_PRINT("WaitOnLock: sleeping on lock",
locallock->lock, locallock->tag.mode);
--- 1473,1492 ----
* Caller must have set MyProc->heldLocks to reflect locks already held
* on the lockable object by this process.
*
+ * Result: returns value of ProcSleep()
+ * STATUS_OK if we acquired the lock
+ * STATUS_ERROR if not (deadlock)
+ * STATUS_WAITING if not (timeout)
+ *
* The appropriate partition lock must be held at entry.
*/
! static int
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);
*************** WaitOnLock(LOCALLOCK *locallock, Resourc
*** 1499,1506 ****
*/
PG_TRY();
{
! if (ProcSleep(locallock, lockMethodTable) != STATUS_OK)
{
/*
* We failed as a result of a deadlock, see CheckDeadLock(). Quit
* now.
--- 1528,1540 ----
*/
PG_TRY();
{
! wait_status = ProcSleep(locallock, lockMethodTable);
! switch (wait_status)
{
+ case STATUS_OK:
+ case STATUS_WAITING:
+ break;
+ default:
/*
* We failed as a result of a deadlock, see CheckDeadLock(). Quit
* now.
*************** WaitOnLock(LOCALLOCK *locallock, Resourc
*** 1545,1552 ****
pfree(new_status);
}
! LOCK_PRINT("WaitOnLock: wakeup on lock",
locallock->lock, locallock->tag.mode);
}
/*
--- 1579,1592 ----
pfree(new_status);
}
! 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);
+
+ return wait_status;
}
/*
*************** VirtualXactLock(VirtualTransactionId vxi
*** 3910,3916 ****
LWLockRelease(proc->backendLock);
/* Time to wait. */
! (void) LockAcquire(&tag, ShareLock, false, false);
LockRelease(&tag, ShareLock, false);
return true;
--- 3950,3960 ----
LWLockRelease(proc->backendLock);
/* Time to wait. */
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain lock on virtual transaction with ID %u",
! vxid.localTransactionId)));
LockRelease(&tag, ShareLock, false);
return true;
diff -dcrpN 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-06-27 09:21:15.411010772 +0200
--- postgresql.2/src/backend/storage/lmgr/proc.c 2012-06-27 09:38:51.569173285 +0200
***************
*** 49,60 ****
--- 49,63 ----
#include "storage/procsignal.h"
#include "storage/spin.h"
#include "storage/timeout.h"
+ #include "utils/guc.h"
#include "utils/timestamp.h"
/* 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 */
*************** LockErrorCleanup(void)
*** 638,645 ****
if (lockAwaited == NULL)
return;
! /* Turn off the deadlock timer, if it's still running (see ProcSleep) */
disable_timeout(DEADLOCK_TIMEOUT, false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
--- 641,652 ----
if (lockAwaited == NULL)
return;
! /*
! * Turn off the deadlock and lock timeout timers,
! * if they are still running (see ProcSleep)
! */
disable_timeout(DEADLOCK_TIMEOUT, false);
+ disable_timeout(LOCK_TIMEOUT, false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
*************** ProcQueueInit(PROC_QUEUE *queue)
*** 875,881 ****
* 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).
*
* ASSUME: that no one will fiddle with the queue until after
* we release the partition lock.
--- 882,891 ----
* 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)
! * STATUS_WAITING if not (timeout)
*
* ASSUME: that no one will fiddle with the queue until after
* we release the partition lock.
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 897,902 ****
--- 907,913 ----
LOCKMASK myHeldLocks = MyProc->heldLocks;
bool early_deadlock = false;
bool allow_autovacuum_cancel = true;
+ bool timeout_detected = false;
int myWaitStatus;
PGPROC *proc;
int i;
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1037,1044 ****
enable_timeout(DEADLOCK_TIMEOUT, DeadlockTimeout);
/*
! * 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
* saved wakeup might be leftover from a previous operation (for example,
* we aborted ProcWaitForSignal just before someone did ProcSendSignal).
--- 1048,1060 ----
enable_timeout(DEADLOCK_TIMEOUT, DeadlockTimeout);
/*
! * Queue the timer for lock timeout, too.
! */
! enable_timeout(LOCK_TIMEOUT, LockTimeout);
!
! /*
! * 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).
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1055,1061 ****
*/
do
{
! PGSemaphoreLock(&MyProc->sem, true);
/*
* waitStatus could change from STATUS_WAITING to something else
--- 1071,1082 ----
*/
do
{
! PGSemaphoreTimedLock(&MyProc->sem, true);
!
! /* Check and keep the lock timeout indicator for later checks */
! timeout_detected = get_timeout_indicator(LOCK_TIMEOUT);
! if (timeout_detected)
! break;
/*
* waitStatus could change from STATUS_WAITING to something else
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1184,1192 ****
} while (myWaitStatus == STATUS_WAITING);
/*
! * Disable the timer, if it's still running
*/
disable_timeout(DEADLOCK_TIMEOUT, false);
/*
* Re-acquire the lock table's partition lock. We have to do this to hold
--- 1205,1214 ----
} while (myWaitStatus == STATUS_WAITING);
/*
! * Disable the timers, if they are still running
*/
disable_timeout(DEADLOCK_TIMEOUT, false);
+ disable_timeout(LOCK_TIMEOUT, false);
/*
* Re-acquire the lock table's partition lock. We have to do this to hold
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1196,1201 ****
--- 1218,1232 ----
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;
*************** ProcSleep(LOCALLOCK *locallock, LockMeth
*** 1209,1216 ****
/*
* We don't have to do anything else, because the awaker did all the
* necessary update of the lock table and MyProc.
*/
! return MyProc->waitStatus;
}
--- 1240,1249 ----
/*
* 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 (timeout_detected ? STATUS_WAITING : MyProc->waitStatus);
}
*************** StandbyTimeoutFunction(void)
*** 1519,1521 ****
--- 1552,1578 ----
{
SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
}
+
+ /*
+ * GetLockTimeoutStart
+ *
+ * Depending on lock_timeout_option, return either the current time
+ * ('per_lock') or the statement start time ('per_statement').
+ */
+ TimestampTz
+ GetLockTimeoutStart(void)
+ {
+ switch (LockTimeoutOption)
+ {
+ case LOCK_TIMEOUT_PER_LOCK:
+ return GetCurrentTimestamp();
+ case LOCK_TIMEOUT_PER_STMT:
+ return GetCurrentStatementStartTimestamp();
+ default:
+ elog(ERROR, "unhandled lock_timeout_option value: \"%s\"",
+ GetConfigOptionByName("lock_timeout_option", NULL));
+ break;
+ }
+ /* Silence possible warnings. */
+ return 0;
+ }
diff -dcrpN postgresql.1/src/backend/storage/lmgr/timeout.c postgresql.2/src/backend/storage/lmgr/timeout.c
*** postgresql.1/src/backend/storage/lmgr/timeout.c 2012-06-27 09:55:21.784879822 +0200
--- postgresql.2/src/backend/storage/lmgr/timeout.c 2012-06-27 09:54:59.824751149 +0200
*************** DestroyTimeout(TimeoutName tn, bool keep
*** 186,192 ****
* CheckDummyTimeout
*
* Do nothing. Usable for timeout sources that don't need
! * extra processing, like authentication_timeout.
* The timeout indicator is set by the signal handler.
*/
void
--- 186,192 ----
* CheckDummyTimeout
*
* Do nothing. Usable for timeout sources that don't need
! * extra processing, like authentication_timeout or lock_timeout.
* The timeout indicator is set by the signal handler.
*/
void
diff -dcrpN postgresql.1/src/backend/tcop/postgres.c postgresql.2/src/backend/tcop/postgres.c
*** postgresql.1/src/backend/tcop/postgres.c 2012-06-27 10:01:22.219982497 +0200
--- postgresql.2/src/backend/tcop/postgres.c 2012-06-27 10:14:00.823362086 +0200
*************** PostgresMain(int argc, char *argv[], con
*** 3624,3629 ****
--- 3624,3632 ----
register_timeout(DEADLOCK_TIMEOUT, true,
CheckDeadLock,
GetCurrentTimestamp);
+ register_timeout(LOCK_TIMEOUT, false,
+ DummyTimeoutFunction,
+ GetLockTimeoutStart);
register_timeout(STATEMENT_TIMEOUT, false,
StatementTimeoutFunction,
GetCurrentStatementStartTimestamp);
diff -dcrpN postgresql.1/src/backend/utils/adt/lockfuncs.c postgresql.2/src/backend/utils/adt/lockfuncs.c
*** postgresql.1/src/backend/utils/adt/lockfuncs.c 2012-06-11 06:22:48.162921604 +0200
--- postgresql.2/src/backend/utils/adt/lockfuncs.c 2012-06-27 08:54:56.850819800 +0200
*************** pg_advisory_lock_int8(PG_FUNCTION_ARGS)
*** 421,427 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ExclusiveLock, true, false);
PG_RETURN_VOID();
}
--- 421,431 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ExclusiveLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_int8(PG_FUNCTION_A
*** 438,444 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
PG_RETURN_VOID();
}
--- 442,452 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_shared_int8(PG_FUNCTION
*** 454,460 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ShareLock, true, false);
PG_RETURN_VOID();
}
--- 462,472 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ShareLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_shared_int8(PG_FUN
*** 471,477 ****
SET_LOCKTAG_INT64(tag, key);
! (void) LockAcquire(&tag, ShareLock, false, false);
PG_RETURN_VOID();
}
--- 483,493 ----
SET_LOCKTAG_INT64(tag, key);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on value %llu",
! (long long)key)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_int4(PG_FUNCTION_ARGS)
*** 604,610 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ExclusiveLock, true, false);
PG_RETURN_VOID();
}
--- 620,630 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ExclusiveLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_int4(PG_FUNCTION_A
*** 622,628 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ExclusiveLock, false, false);
PG_RETURN_VOID();
}
--- 642,652 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain exclusive advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_lock_shared_int4(PG_FUNCTION
*** 639,645 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ShareLock, true, false);
PG_RETURN_VOID();
}
--- 663,673 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ShareLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
*************** pg_advisory_xact_lock_shared_int4(PG_FUN
*** 657,663 ****
SET_LOCKTAG_INT32(tag, key1, key2);
! (void) LockAcquire(&tag, ShareLock, false, false);
PG_RETURN_VOID();
}
--- 685,695 ----
SET_LOCKTAG_INT32(tag, key1, key2);
! if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
! ereport(ERROR,
! (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
! errmsg("could not obtain shared advisory lock on values %u:%u",
! key1, key2)));
PG_RETURN_VOID();
}
diff -dcrpN 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-06-27 09:22:20.997398278 +0200
--- postgresql.2/src/backend/utils/misc/guc.c 2012-06-27 09:09:59.800940943 +0200
*************** static const struct config_enum_entry sy
*** 389,394 ****
--- 389,405 ----
};
/*
+ * 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[];
*************** static struct config_int ConfigureNamesI
*** 1861,1866 ****
--- 1872,1888 ----
},
{
+ {"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
*************** static struct config_enum ConfigureNames
*** 3141,3146 ****
--- 3163,3178 ----
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 -dcrpN 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-06-27 08:54:56.852819812 +0200
***************
*** 528,533 ****
--- 528,538 ----
#------------------------------------------------------------------------------
#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 -dcrpN postgresql.1/src/include/storage/pg_sema.h postgresql.2/src/include/storage/pg_sema.h
*** postgresql.1/src/include/storage/pg_sema.h 2012-04-16 19:57:22.672918205 +0200
--- postgresql.2/src/include/storage/pg_sema.h 2012-06-27 08:54:56.852819812 +0200
*************** extern void PGSemaphoreUnlock(PGSemaphor
*** 80,83 ****
--- 80,89 ----
/* 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 void PGSemaphoreTimedLock(PGSemaphore sema, bool interruptOK);
+
#endif /* PG_SEMA_H */
diff -dcrpN postgresql.1/src/include/storage/proc.h postgresql.2/src/include/storage/proc.h
*** postgresql.1/src/include/storage/proc.h 2012-06-27 09:21:57.139257370 +0200
--- postgresql.2/src/include/storage/proc.h 2012-06-27 09:07:30.702021019 +0200
*************** extern PGPROC *PreparedXactProcs;
*** 220,227 ****
--- 220,234 ----
/* 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
*/
*************** extern void CheckDeadLock(void);
*** 254,258 ****
--- 261,266 ----
extern void StatementTimeoutFunction(void);
extern void StandbyDeadLockFunction(void);
extern void StandbyTimeoutFunction(void);
+ extern TimestampTz GetLockTimeoutStart(void);
#endif /* PROC_H */
diff -dcrpN postgresql.1/src/include/storage/timeout.h postgresql.2/src/include/storage/timeout.h
*** postgresql.1/src/include/storage/timeout.h 2012-06-27 09:49:16.789761575 +0200
--- postgresql.2/src/include/storage/timeout.h 2012-06-27 09:53:23.135183903 +0200
***************
*** 19,24 ****
--- 19,25 ----
typedef enum TimeoutName {
AUTHENTICATION_TIMEOUT,
DEADLOCK_TIMEOUT,
+ LOCK_TIMEOUT,
STATEMENT_TIMEOUT,
STANDBY_DEADLOCK_TIMEOUT,
STANDBY_TIMEOUT,
diff -dcrpN 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-06-27 08:54:56.853819817 +0200
*************** set statement_timeout to 2000;
*** 198,203 ****
--- 198,223 ----
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: could not obtain lock on relation "pxtest3"
+ LINE 1: 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;
+ ERROR: could not obtain lock on relation "pxtest3"
+ LINE 1: SELECT * FROM pxtest3;
+ ^
+ 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
*************** set statement_timeout to 2000;
*** 213,218 ****
--- 233,245 ----
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: could not obtain lock on relation "pxtest3"
+ LINE 1: SELECT * FROM pxtest3;
+ ^
+ reset lock_timeout;
-- Commit table creation
COMMIT PREPARED 'regress-one';
\d pxtest2
diff -dcrpN 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-06-27 08:54:56.853819817 +0200
*************** set statement_timeout to 2000;
*** 126,131 ****
--- 126,147 ----
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 -
*************** set statement_timeout to 2000;
*** 137,142 ****
--- 153,163 ----
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
2012-06-27 10:34 keltezéssel, Boszormenyi Zoltan írta:
2012-06-26 18:49 keltezéssel, Alvaro Herrera írta:
Excerpts from Boszormenyi Zoltan's message of mar jun 26 12:43:34 -0400 2012:
So, should I keep the enum TimeoutName? Are global variables for
keeping dynamically assigned values preferred over the enum?
Currently we have 5 timeout sources in total, 3 of them are used by
regular backends, the remaining 2 are used by replication standby.
We can have a fixed size array (say with 8 or 16 elements) for future use
and this would be plenty.Opinions?
My opinion is that the fixed size array is fine.
Attached is the version which uses a registration interface.
Also, to further minimize knowledge of timeouts in timeout.c,
all GUCs are moved back to proc.cI'll go set the patch "waiting on author". Also, remember to review
some other people's patches.I will look into it.
Best regards,
Zoltán Böszörményi
Does anyone have a little time to look at the latest timeout framework
with the registration interface and the 2nd patch too? I am at work
until Friday next week, after that I will be on vacation for two weeks.
Just in case there is anything that needs tweaking to make it more
acceptable.
Thanks in advance,
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/
Excerpts from Boszormenyi Zoltan's message of vie jun 29 14:30:28 -0400 2012:
Does anyone have a little time to look at the latest timeout framework
with the registration interface and the 2nd patch too? I am at work
until Friday next week, after that I will be on vacation for two weeks.
Just in case there is anything that needs tweaking to make it more
acceptable.
I cleaned up this a bit more and now I think it's ready to commit --
as soon as somebody tests that the standby bits still work.
I still have not looked at the second patch.
--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support
Attachments:
1-timeout-framework-v14.patchapplication/octet-stream; name=1-timeout-framework-v14.patchDownload
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index dade5cc..bfa6f49 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -89,6 +89,7 @@
#include "storage/proc.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
+#include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
@@ -484,7 +485,7 @@ AutoVacLauncherMain(int argc, char *argv[])
/* Forget any pending QueryCancel request */
QueryCancelPending = false;
- disable_sig_alarm(true);
+ disable_all_timeouts(false);
QueryCancelPending = false; /* again in case timeout occurred */
/* Report the error to the server log */
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 913734f..a501d0d 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -112,7 +112,7 @@
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
-#include "storage/proc.h"
+#include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/datetime.h"
@@ -3365,6 +3365,17 @@ report_fork_failure_to_client(Port *port, int errnum)
} while (rc < 0 && errno == EINTR);
}
+/*
+ * DummyTimeoutFunction
+ *
+ * Do nothing -- just to break out of a sleeping system call.
+ * The timeout indicator is set by the signal handler.
+ */
+static void
+DummyTimeoutFunction(void)
+{
+ return;
+}
/*
* BackendInitialize -- initialize an interactive (postmaster-child)
@@ -3437,6 +3448,13 @@ BackendInitialize(Port *port)
PG_SETMASK(&StartupBlockSig);
/*
+ * Initialize timeout sources and register authentication timeout early.
+ */
+ InitializeTimeouts();
+ RegisterTimeout(AUTHENTICATION_TIMEOUT, false,
+ DummyTimeoutFunction, GetCurrentTimestamp);
+
+ /*
* Get the remote host name and port for logging and status display.
*/
remote_host[0] = '\0';
@@ -3488,8 +3506,7 @@ BackendInitialize(Port *port)
* indefinitely. PreAuthDelay and any DNS interactions above don't count
* against the time limit.
*/
- if (!enable_sig_alarm(AuthenticationTimeout * 1000, false))
- elog(FATAL, "could not set timer for startup packet timeout");
+ enable_timeout(AUTHENTICATION_TIMEOUT, AuthenticationTimeout * 1000);
/*
* Receive the startup packet (which might turn out to be a cancel request
@@ -3526,8 +3543,7 @@ BackendInitialize(Port *port)
/*
* Disable the timeout, and prevent SIGTERM/SIGQUIT again.
*/
- if (!disable_sig_alarm(false))
- elog(FATAL, "could not disable timer for startup packet timeout");
+ disable_timeout(AUTHENTICATION_TIMEOUT, false);
PG_SETMASK(&BlockSig);
}
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index ed75d09..ffeb0e0 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -28,7 +28,9 @@
#include "storage/latch.h"
#include "storage/pmsignal.h"
#include "storage/proc.h"
+#include "storage/timeout.h"
#include "utils/guc.h"
+#include "utils/timestamp.h"
/*
@@ -184,19 +186,23 @@ StartupProcessMain(void)
#endif
/*
+ * Initialize timeouts and register timeouts needed for standby.
+ */
+ InitializeTimeouts();
+ RegisterTimeout(STANDBY_DEADLOCK_TIMEOUT, true,
+ StandbyDeadLockHandler, GetCurrentTimestamp);
+ RegisterTimeout(STANDBY_TIMEOUT, false,
+ StandbyTimeoutHandler, GetCurrentTimestamp);
+
+ /*
* Properly accept or ignore signals the postmaster might send us.
- *
- * Note: ideally we'd not enable handle_standby_sig_alarm unless actually
- * doing hot standby, but we don't know that yet. Rely on it to not do
- * anything if it shouldn't.
*/
pqsignal(SIGHUP, StartupProcSigHupHandler); /* reload config file */
pqsignal(SIGINT, SIG_IGN); /* ignore query cancel */
pqsignal(SIGTERM, StartupProcShutdownHandler); /* request shutdown */
pqsignal(SIGQUIT, startupproc_quickdie); /* hard crash time */
if (EnableHotStandby)
- pqsignal(SIGALRM, handle_standby_sig_alarm); /* ignored unless
- * InHotStandby */
+ pqsignal(SIGALRM, handle_sig_alarm);
else
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c
index 995b68a..6b8233f 100644
--- a/src/backend/storage/ipc/standby.c
+++ b/src/backend/storage/ipc/standby.c
@@ -27,6 +27,7 @@
#include "storage/procarray.h"
#include "storage/sinvaladt.h"
#include "storage/standby.h"
+#include "storage/timeout.h"
#include "utils/ps_status.h"
#include "utils/timestamp.h"
@@ -394,7 +395,6 @@ ResolveRecoveryConflictWithLock(Oid dbOid, Oid relOid)
void
ResolveRecoveryConflictWithBufferPin(void)
{
- bool sig_alarm_enabled = false;
TimestampTz ltime;
TimestampTz now;
@@ -409,10 +409,7 @@ ResolveRecoveryConflictWithBufferPin(void)
* We're willing to wait forever for conflicts, so set timeout for
* deadlock check (only)
*/
- if (enable_standby_sig_alarm(now, now, true))
- sig_alarm_enabled = true;
- else
- elog(FATAL, "could not set timer for process wakeup");
+ enable_timeout(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout);
}
else if (now >= ltime)
{
@@ -423,24 +420,26 @@ ResolveRecoveryConflictWithBufferPin(void)
}
else
{
+ long secs,
+ msecs;
+ int usecs;
+
/*
* Wake up at ltime, and check for deadlocks as well if we will be
* waiting longer than deadlock_timeout
*/
- if (enable_standby_sig_alarm(now, ltime, false))
- sig_alarm_enabled = true;
- else
- elog(FATAL, "could not set timer for process wakeup");
+ enable_timeout(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout);
+
+ TimestampDifference(now, ltime, &secs, &usecs);
+ msecs = secs * 1000 + usecs / 1000;
+
+ enable_timeout(STANDBY_TIMEOUT, msecs);
}
/* Wait to be signaled by UnpinBuffer() */
ProcWaitForSignal();
- if (sig_alarm_enabled)
- {
- if (!disable_standby_sig_alarm())
- elog(FATAL, "could not disable timer for process wakeup");
- }
+ disable_all_timeouts(false);
}
void
diff --git a/src/backend/storage/lmgr/Makefile b/src/backend/storage/lmgr/Makefile
index e12a854..f0f7814 100644
--- a/src/backend/storage/lmgr/Makefile
+++ b/src/backend/storage/lmgr/Makefile
@@ -12,7 +12,7 @@ subdir = src/backend/storage/lmgr
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
-OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o predicate.o
+OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o predicate.o timeout.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 21598d3..4b5b8d0 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -48,6 +48,7 @@
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/spin.h"
+#include "storage/timeout.h"
#include "utils/timestamp.h"
@@ -77,27 +78,12 @@ PGPROC *PreparedXactProcs = NULL;
/* If we are waiting for a lock, this points to the associated LOCALLOCK */
static LOCALLOCK *lockAwaited = NULL;
-/* Mark these volatile because they can be changed by signal handler */
-static volatile bool standby_timeout_active = false;
-static volatile bool statement_timeout_active = false;
-static volatile bool deadlock_timeout_active = false;
-static volatile DeadLockState deadlock_state = DS_NOT_YET_CHECKED;
-volatile bool cancel_from_timeout = false;
-
-/* timeout_start_time is set when log_lock_waits is true */
-static TimestampTz timeout_start_time;
-
-/* statement_fin_time is valid only if statement_timeout_active is true */
-static TimestampTz statement_fin_time;
-static TimestampTz statement_fin_time2; /* valid only in recovery */
-
+/* Mark this volatile because it can be changed by signal handler */
+static volatile DeadLockState deadlock_state;
static void RemoveProcFromArray(int code, Datum arg);
static void ProcKill(int code, Datum arg);
static void AuxiliaryProcKill(int code, Datum arg);
-static bool CheckStatementTimeout(void);
-static bool CheckStandbyTimeout(void);
-
/*
* Report shared-memory space needed by InitProcGlobal.
@@ -653,7 +639,7 @@ LockErrorCleanup(void)
return;
/* Turn off the deadlock timer, if it's still running (see ProcSleep) */
- disable_sig_alarm(false);
+ disable_timeout(DEADLOCK_TIMEOUT, false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
@@ -1048,8 +1034,7 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable)
* 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 (!enable_sig_alarm(DeadlockTimeout, false))
- elog(FATAL, "could not set timer for process wakeup");
+ enable_timeout(DEADLOCK_TIMEOUT, DeadlockTimeout);
/*
* If someone wakes us between LWLockRelease and PGSemaphoreLock,
@@ -1138,7 +1123,8 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable)
DescribeLockTag(&buf, &locallock->tag.lock);
modename = GetLockmodeName(locallock->tag.lock.locktag_lockmethodid,
lockmode);
- TimestampDifference(timeout_start_time, GetCurrentTimestamp(),
+ TimestampDifference(get_timeout_start(DEADLOCK_TIMEOUT),
+ GetCurrentTimestamp(),
&secs, &usecs);
msecs = secs * 1000 + usecs / 1000;
usecs = usecs % 1000;
@@ -1200,8 +1186,7 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable)
/*
* Disable the timer, if it's still running
*/
- if (!disable_sig_alarm(false))
- elog(FATAL, "could not disable timer for process wakeup");
+ disable_timeout(DEADLOCK_TIMEOUT, false);
/*
* Re-acquire the lock table's partition lock. We have to do this to hold
@@ -1344,7 +1329,7 @@ ProcLockWakeup(LockMethod lockMethodTable, LOCK *lock)
* NB: this is run inside a signal handler, so be very wary about what is done
* here or in called routines.
*/
-static void
+void
CheckDeadLock(void)
{
int i;
@@ -1499,400 +1484,38 @@ ProcSendSignal(int pid)
PGSemaphoreUnlock(&proc->sem);
}
-
-/*****************************************************************************
- * SIGALRM interrupt support
- *
- * Maybe these should be in pqsignal.c?
- *****************************************************************************/
-
-/*
- * Enable the SIGALRM interrupt to fire after the specified delay
- *
- * Delay is given in milliseconds. Caller should be sure a SIGALRM
- * signal handler is installed before this is called.
- *
- * This code properly handles nesting of deadlock timeout alarms within
- * statement timeout alarms.
- *
- * Returns TRUE if okay, FALSE on failure.
- */
-bool
-enable_sig_alarm(int delayms, bool is_statement_timeout)
-{
- TimestampTz fin_time;
- struct itimerval timeval;
-
- if (is_statement_timeout)
- {
- /*
- * Begin statement-level timeout
- *
- * Note that we compute statement_fin_time with reference to the
- * statement_timestamp, but apply the specified delay without any
- * correction; that is, we ignore whatever time has elapsed since
- * statement_timestamp was set. In the normal case only a small
- * interval will have elapsed and so this doesn't matter, but there
- * are corner cases (involving multi-statement query strings with
- * embedded COMMIT or ROLLBACK) where we might re-initialize the
- * statement timeout long after initial receipt of the message. In
- * such cases the enforcement of the statement timeout will be a bit
- * inconsistent. This annoyance is judged not worth the cost of
- * performing an additional gettimeofday() here.
- */
- Assert(!deadlock_timeout_active);
- fin_time = GetCurrentStatementStartTimestamp();
- fin_time = TimestampTzPlusMilliseconds(fin_time, delayms);
- statement_fin_time = fin_time;
- cancel_from_timeout = false;
- statement_timeout_active = true;
- }
- else if (statement_timeout_active)
- {
- /*
- * Begin deadlock timeout with statement-level timeout active
- *
- * Here, we want to interrupt at the closer of the two timeout times.
- * If fin_time >= statement_fin_time then we need not touch the
- * existing timer setting; else set up to interrupt at the deadlock
- * timeout time.
- *
- * NOTE: in this case it is possible that this routine will be
- * interrupted by the previously-set timer alarm. This is okay
- * because the signal handler will do only what it should do according
- * to the state variables. The deadlock checker may get run earlier
- * than normal, but that does no harm.
- */
- timeout_start_time = GetCurrentTimestamp();
- fin_time = TimestampTzPlusMilliseconds(timeout_start_time, delayms);
- deadlock_timeout_active = true;
- if (fin_time >= statement_fin_time)
- return true;
- }
- else
- {
- /* Begin deadlock timeout with no statement-level timeout */
- deadlock_timeout_active = true;
- /* GetCurrentTimestamp can be expensive, so only do it if we must */
- if (log_lock_waits)
- timeout_start_time = GetCurrentTimestamp();
- }
-
- /* If we reach here, okay to set the timer interrupt */
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = delayms / 1000;
- timeval.it_value.tv_usec = (delayms % 1000) * 1000;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- return true;
-}
-
-/*
- * Cancel the SIGALRM timer, either for a deadlock timeout or a statement
- * timeout. If a deadlock timeout is canceled, any active statement timeout
- * remains in force.
- *
- * Returns TRUE if okay, FALSE on failure.
- */
-bool
-disable_sig_alarm(bool is_statement_timeout)
-{
- /*
- * Always disable the interrupt if it is active; this avoids being
- * interrupted by the signal handler and thereby possibly getting
- * confused.
- *
- * We will re-enable the interrupt if necessary in CheckStatementTimeout.
- */
- if (statement_timeout_active || deadlock_timeout_active)
- {
- struct itimerval timeval;
-
- MemSet(&timeval, 0, sizeof(struct itimerval));
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- {
- statement_timeout_active = false;
- cancel_from_timeout = false;
- deadlock_timeout_active = false;
- return false;
- }
- }
-
- /* Always cancel deadlock timeout, in case this is error cleanup */
- deadlock_timeout_active = false;
-
- /* Cancel or reschedule statement timeout */
- if (is_statement_timeout)
- {
- statement_timeout_active = false;
- cancel_from_timeout = false;
- }
- else if (statement_timeout_active)
- {
- if (!CheckStatementTimeout())
- return false;
- }
- return true;
-}
-
-
/*
* Check for statement timeout. If the timeout time has come,
- * trigger a query-cancel interrupt; if not, reschedule the SIGALRM
- * interrupt to occur at the right time.
- *
- * Returns true if okay, false if failed to set the interrupt.
+ * trigger a query-cancel interrupt.
*/
-static bool
-CheckStatementTimeout(void)
+void
+StatementTimeoutHandler(void)
{
- TimestampTz now;
-
- if (!statement_timeout_active)
- return true; /* do nothing if not active */
-
- now = GetCurrentTimestamp();
-
- if (now >= statement_fin_time)
- {
- /* Time to die */
- statement_timeout_active = false;
- cancel_from_timeout = true;
#ifdef HAVE_SETSID
- /* try to signal whole process group */
- kill(-MyProcPid, SIGINT);
+ /* try to signal whole process group */
+ kill(-MyProcPid, SIGINT);
#endif
- kill(MyProcPid, SIGINT);
- }
- else
- {
- /* Not time yet, so (re)schedule the interrupt */
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
-
- /*
- * It's possible that the difference is less than a microsecond;
- * ensure we don't cancel, rather than set, the interrupt.
- */
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- }
-
- return true;
+ kill(MyProcPid, SIGINT);
}
-
/*
- * Signal handler for SIGALRM for normal user backends
- *
- * Process deadlock check and/or statement timeout check, as needed.
- * To avoid various edge cases, we must be careful to do nothing
- * when there is nothing to be done. We also need to be able to
- * reschedule the timer interrupt if called before end of statement.
+ * StandbyDeadLockHandler() will trigger if the deadlock timeout
+ * happens earlier than StandbyTimeout. If it triggers,
+ * StandbyTimeout will still be rescheduled.
*/
void
-handle_sig_alarm(SIGNAL_ARGS)
+StandbyDeadLockHandler(void)
{
- int save_errno = errno;
-
- /* SIGALRM is cause for waking anything waiting on the process latch */
- if (MyProc)
- SetLatch(&MyProc->procLatch);
-
- if (deadlock_timeout_active)
- {
- deadlock_timeout_active = false;
- CheckDeadLock();
- }
-
- if (statement_timeout_active)
- (void) CheckStatementTimeout();
-
- errno = save_errno;
-}
-
-/*
- * Signal handler for SIGALRM in Startup process
- *
- * To avoid various edge cases, we must be careful to do nothing
- * when there is nothing to be done. We also need to be able to
- * reschedule the timer interrupt if called before end of statement.
- *
- * We set either deadlock_timeout_active or statement_timeout_active
- * or both. Interrupts are enabled if standby_timeout_active.
- */
-bool
-enable_standby_sig_alarm(TimestampTz now, TimestampTz fin_time, bool deadlock_only)
-{
- TimestampTz deadlock_time = TimestampTzPlusMilliseconds(now,
- DeadlockTimeout);
-
- if (deadlock_only)
- {
- /*
- * Wake up at deadlock_time only, then wait forever
- */
- statement_fin_time = deadlock_time;
- deadlock_timeout_active = true;
- statement_timeout_active = false;
- }
- else if (fin_time > deadlock_time)
- {
- /*
- * Wake up at deadlock_time, then again at fin_time
- */
- statement_fin_time = deadlock_time;
- statement_fin_time2 = fin_time;
- deadlock_timeout_active = true;
- statement_timeout_active = true;
- }
- else
- {
- /*
- * Wake only at fin_time because its fairly soon
- */
- statement_fin_time = fin_time;
- deadlock_timeout_active = false;
- statement_timeout_active = true;
- }
-
- if (deadlock_timeout_active || statement_timeout_active)
- {
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- standby_timeout_active = true;
- }
-
- return true;
-}
-
-bool
-disable_standby_sig_alarm(void)
-{
- /*
- * Always disable the interrupt if it is active; this avoids being
- * interrupted by the signal handler and thereby possibly getting
- * confused.
- *
- * We will re-enable the interrupt if necessary in CheckStandbyTimeout.
- */
- if (standby_timeout_active)
- {
- struct itimerval timeval;
-
- MemSet(&timeval, 0, sizeof(struct itimerval));
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- {
- standby_timeout_active = false;
- return false;
- }
- }
-
- standby_timeout_active = false;
-
- return true;
+ SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
}
/*
- * CheckStandbyTimeout() runs unconditionally in the Startup process
+ * StandbyTimeoutHandler() runs unconditionally in the Startup process
* SIGALRM handler. Timers will only be set when InHotStandby.
* We simply ignore any signals unless the timer has been set.
*/
-static bool
-CheckStandbyTimeout(void)
-{
- TimestampTz now;
- bool reschedule = false;
-
- standby_timeout_active = false;
-
- now = GetCurrentTimestamp();
-
- /*
- * Reschedule the timer if its not time to wake yet, or if we have both
- * timers set and the first one has just been reached.
- */
- if (now >= statement_fin_time)
- {
- if (deadlock_timeout_active)
- {
- /*
- * We're still waiting when we reach deadlock timeout, so send out
- * a request to have other backends check themselves for deadlock.
- * Then continue waiting until statement_fin_time, if that's set.
- */
- SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
- deadlock_timeout_active = false;
-
- /*
- * Begin second waiting period if required.
- */
- if (statement_timeout_active)
- {
- reschedule = true;
- statement_fin_time = statement_fin_time2;
- }
- }
- else
- {
- /*
- * We've now reached statement_fin_time, so ask all conflicts to
- * leave, so we can press ahead with applying changes in recovery.
- */
- SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
- }
- }
- else
- reschedule = true;
-
- if (reschedule)
- {
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- standby_timeout_active = true;
- }
-
- return true;
-}
-
void
-handle_standby_sig_alarm(SIGNAL_ARGS)
+StandbyTimeoutHandler(void)
{
- int save_errno = errno;
-
- if (standby_timeout_active)
- (void) CheckStandbyTimeout();
-
- errno = save_errno;
+ SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
}
diff --git a/src/backend/storage/lmgr/timeout.c b/src/backend/storage/lmgr/timeout.c
new file mode 100644
index 0000000..de1bb09
--- /dev/null
+++ b/src/backend/storage/lmgr/timeout.c
@@ -0,0 +1,432 @@
+/*-------------------------------------------------------------------------
+ *
+ * timeout.c
+ * routines to manage timeout sources handled by SIGALRM
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/storage/lmgr/timeout.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <sys/time.h>
+
+#include "storage/proc.h"
+#include "storage/timeout.h"
+#include "utils/timestamp.h"
+
+
+static void InitTimeout(TimeoutName tn, TimestampTz start_time,
+ TimestampTz fin_time);
+static void DestroyTimeout(TimeoutName tn, bool keep_indicator);
+
+typedef struct timeout_params
+{
+ TimeoutName index;
+ bool resched_next;
+
+ /* volatile because it may be changed from the signal handler */
+ volatile bool indicator;
+
+ timeout_func timeout_func;
+ timeout_start timeout_start;
+
+ TimestampTz start_time;
+ TimestampTz fin_time;
+} timeout_params;
+
+/*
+ * List of possible timeout reasons in the order of enum TimeoutName.
+ * The priority of timeouts (in case two of them would trigger at the
+ * same time) is determined by this order: the earlier one in the list
+ * has higher priority. InitializeBackend() and PostgresMain() initialize
+ * this array and register timeout sources.
+ */
+static bool base_timeouts_initialized = false;
+static timeout_params base_timeouts[TIMEOUT_MAX];
+
+/*
+ * List of active timeouts ordered by their fin_time and priority.
+ */
+static int NumActiveTimeouts = 0;
+static timeout_params *ActiveTimeouts[TIMEOUT_MAX];
+
+/*****************************************************************************
+ * Internal helper functions
+ *****************************************************************************/
+
+/*
+ * Find the index of a given timeout type in the active array
+ */
+static int
+find_active_timeout(TimeoutName tn)
+{
+ int i;
+
+ for (i = 0; i < NumActiveTimeouts; i++)
+ {
+ if (ActiveTimeouts[i]->index == tn)
+ return i;
+ }
+
+ return -1;
+}
+
+#define is_timeout_active(tn) (find_active_timeout(tn) >= 0)
+
+/*
+ * Insert tn'th timeout into the list of timeouts at the given index
+ * if the previous timeout allows rescheduling the next one in the list.
+ */
+static void
+insert_timeout(TimeoutName tn, int index)
+{
+ int i;
+
+ if (index < 0 || index > NumActiveTimeouts)
+ elog(FATAL, "timeout index %d out of range 0..%d", index,
+ NumActiveTimeouts);
+
+ if (index > 0 && !ActiveTimeouts[index - 1]->resched_next)
+ return;
+
+ for (i = NumActiveTimeouts - 1; i >= index; i--)
+ ActiveTimeouts[i + 1] = ActiveTimeouts[i];
+
+ ActiveTimeouts[index] = &base_timeouts[tn];
+ NumActiveTimeouts++;
+}
+
+/*
+ * Remove the index'th element from the timeout list.
+ */
+static void
+remove_timeout_index(int index)
+{
+ int i;
+
+ if (index < 0 || index > NumActiveTimeouts)
+ elog(FATAL, "timeout index %d out of range 0..%d", index,
+ NumActiveTimeouts);
+
+ for (i = index + 1; i < NumActiveTimeouts; i++)
+ ActiveTimeouts[i - 1] = ActiveTimeouts[i];
+
+ if (NumActiveTimeouts > 0 && index < NumActiveTimeouts)
+ NumActiveTimeouts--;
+}
+
+/*
+ * (Re)schedule alarm for the next active timeout
+ */
+static void
+schedule_alarm(TimestampTz now)
+{
+ long secs;
+ int usecs;
+ struct itimerval timeval;
+
+ /* There is no active timeout, do nothing. */
+ if (NumActiveTimeouts == 0)
+ return;
+
+ TimestampDifference(now, ActiveTimeouts[0]->fin_time,
+ &secs, &usecs);
+
+ /*
+ * It's possible that the difference is less than a microsecond;
+ * ensure we don't cancel, rather than set, the interrupt.
+ */
+ if (secs == 0 && usecs == 0)
+ usecs = 1;
+
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ timeval.it_value.tv_sec = secs;
+ timeval.it_value.tv_usec = usecs;
+ if (setitimer(ITIMER_REAL, &timeval, NULL) != 0)
+ elog(FATAL, "could not enable timer");
+}
+
+/*****************************************************************************
+ * Init and Destroy functions
+ *****************************************************************************/
+
+static void
+InitTimeout(TimeoutName tn, TimestampTz start_time, TimestampTz fin_time)
+{
+ base_timeouts[tn].indicator = false;
+ base_timeouts[tn].start_time = start_time;
+ base_timeouts[tn].fin_time = fin_time;
+}
+
+static void
+DestroyTimeout(TimeoutName tn, bool keep_indicator)
+{
+ if (!keep_indicator)
+ base_timeouts[tn].indicator = false;
+ base_timeouts[tn].start_time = 0;
+ base_timeouts[tn].fin_time = 0;
+}
+
+/*****************************************************************************
+ * Public API
+ *****************************************************************************/
+
+/*
+ * Initialize timeout module.
+ *
+ * This must be called on every process that wants to use timeouts.
+ */
+void
+InitializeTimeouts(void)
+{
+ int i;
+
+ if (base_timeouts_initialized)
+ {
+ elog(PANIC, "timeouts already initialized");
+ return;
+ }
+
+ for (i = 0; i < TIMEOUT_MAX; i++)
+ {
+ base_timeouts[i].index = i;
+ base_timeouts[i].resched_next = false;
+ base_timeouts[i].indicator = false;
+ base_timeouts[i].timeout_func = NULL;
+ base_timeouts[i].timeout_start = NULL;
+ base_timeouts[i].start_time = 0;
+ base_timeouts[i].fin_time = 0;
+ }
+
+ base_timeouts_initialized = true;
+}
+
+/*
+ * Register a timeout source
+ */
+TimeoutName
+RegisterTimeout(TimeoutName tn, bool resched_next,
+ timeout_func func, timeout_start start)
+{
+ Assert(base_timeouts_initialized);
+ Assert(base_timeouts[tn].timeout_func == NULL);
+
+ if (tn < 0)
+ {
+ for (tn = USER_TIMEOUT; tn < TIMEOUT_MAX; tn++)
+ if (base_timeouts[tn].timeout_func == NULL)
+ break;
+ if (tn >= TIMEOUT_MAX)
+ ereport(FATAL,
+ (errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
+ errmsg("cannot add more timeout sources")));
+ }
+
+ base_timeouts[tn].resched_next = resched_next;
+ base_timeouts[tn].timeout_func = func;
+ base_timeouts[tn].timeout_start = start;
+
+ return tn;
+}
+
+/*
+ * Enable the SIGALRM interrupt to fire after the specified delay
+ *
+ * Delay is given in milliseconds. Caller should be sure a SIGALRM
+ * signal handler is installed before this is called.
+ *
+ * This code properly handles nesting of different timeout alarms.
+ */
+void
+enable_timeout(TimeoutName tn, int delayms)
+{
+ TimestampTz start_time;
+ TimestampTz fin_time;
+ int i;
+
+ Assert(base_timeouts_initialized);
+ Assert(base_timeouts[tn].timeout_func != NULL);
+ Assert(!is_timeout_active(tn));
+
+ if (delayms <= 0)
+ return;
+
+ start_time = base_timeouts[tn].timeout_start();
+ fin_time = TimestampTzPlusMilliseconds(start_time, delayms);
+
+ /* Find out the index where to insert the new timeout. */
+ for (i = 0; i < NumActiveTimeouts; i++)
+ {
+ /*
+ * The new timeout triggers earlier than a previously added one:
+ * insert here.
+ */
+ if (fin_time < ActiveTimeouts[i]->fin_time)
+ break;
+ /*
+ * The new timeout triggers at the same time as the previously added
+ * one but has greater priority.
+ */
+ if (fin_time == ActiveTimeouts[i]->fin_time &&
+ tn < ActiveTimeouts[i]->index)
+ break;
+ }
+
+ /*
+ * Initialize the timeout parameters
+ */
+ InitTimeout(tn, start_time, fin_time);
+
+ insert_timeout(tn, i);
+
+ /*
+ * If the timeout was inserted in the first position, set the timer.
+ */
+ if (i == 0)
+ schedule_alarm(start_time);
+}
+
+/*
+ * Cancel the SIGALRM timer for the specific timeout.
+ * If a timeout is canceled, any other active timeout remains in force.
+ *
+ * It's not an error to disable a timeout that is not enabled.
+ */
+void
+disable_timeout(TimeoutName tn, bool keep_indicator)
+{
+ int i;
+
+ /*
+ * Always disable the timer if it is active; this avoids being
+ * interrupted by the signal handler and thereby possibly getting
+ * confused.
+ *
+ * We will re-enable the interrupt if necessary later in this function.
+ */
+ if (NumActiveTimeouts > 0)
+ {
+ struct itimerval timeval;
+
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ if (setitimer(ITIMER_REAL, &timeval, NULL) != 0)
+ elog(FATAL, "could not disable timer");
+ }
+
+ /* Find the timeout and remove from the list. */
+ i = find_active_timeout(tn);
+ if (i >= 0)
+ {
+ remove_timeout_index(i);
+ DestroyTimeout(tn, keep_indicator);
+ }
+
+ /*
+ * Now re-enable the timer, if necessary.
+ */
+ if (NumActiveTimeouts > 0)
+ schedule_alarm(GetCurrentTimestamp());
+}
+
+/*
+ * Disable SIGALRM and remove all timeouts from the list and
+ * reset the timeout indicators.
+ */
+void
+disable_all_timeouts(bool keep_indicators)
+{
+ struct itimerval timeval;
+ int i;
+
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ if (setitimer(ITIMER_REAL, &timeval, NULL) != 0)
+ elog(FATAL, "could not disable timer");
+
+ NumActiveTimeouts = 0;
+ for (i = 0; i < TIMEOUT_MAX; i++)
+ DestroyTimeout(i, keep_indicators);
+}
+
+/*
+ * Return the timeout indicator
+ */
+bool
+get_timeout_indicator(TimeoutName tn)
+{
+ return base_timeouts[tn].indicator;
+}
+
+/*
+ * Return the start of the timer for this timeout
+ */
+TimestampTz
+get_timeout_start(TimeoutName tn)
+{
+ return base_timeouts[tn].start_time;
+}
+
+/*
+ * Signal handler for SIGALRM
+ *
+ * Process the check for the currently active timeout source and
+ * reschedule the next as needed. To avoid various edge cases,
+ * we must be careful to do nothing when there is nothing to be done.
+ */
+void
+handle_sig_alarm(SIGNAL_ARGS)
+{
+ int save_errno = errno;
+
+ /*
+ * SIGALRM is cause for waking anything waiting on the process latch.
+ * Cope with MyProc not being there, as the startup process also uses
+ * this signal handler.
+ */
+ if (MyProc)
+ SetLatch(&MyProc->procLatch);
+
+ if (NumActiveTimeouts > 0)
+ {
+ TimestampTz now;
+ bool reschedule;
+
+ now = GetCurrentTimestamp();
+
+ /* If this timeout's time has not come yet, do nothing. */
+ if (now < ActiveTimeouts[0]->fin_time)
+ goto out;
+
+ /*
+ * Set the trigger indicator before calling the checker function.
+ * Setting if after may have side effects that lead to detecting
+ * a different event, like "user pressed Ctrl-C" is detected instead
+ * of the statement timeout having triggered.
+ */
+ ActiveTimeouts[0]->indicator = true;
+
+ /* Call the timeout checker. */
+ ActiveTimeouts[0]->timeout_func();
+
+ /*
+ * Remove the current timeout source and reschedule
+ * the next if needed. Short circuit disable_timeout(..., true)
+ * for the timeout source that just triggered.
+ */
+ reschedule = ActiveTimeouts[0]->resched_next;
+ remove_timeout_index(0);
+
+ if (reschedule)
+ schedule_alarm(now);
+ else
+ disable_all_timeouts(true);
+ }
+
+out:
+ errno = save_errno;
+}
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 9a5438f..fde536a 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -64,6 +64,7 @@
#include "storage/proc.h"
#include "storage/procsignal.h"
#include "storage/sinval.h"
+#include "storage/timeout.h"
#include "tcop/fastpath.h"
#include "tcop/pquery.h"
#include "tcop/tcopprot.h"
@@ -2396,9 +2397,9 @@ start_xact_command(void)
/* Set statement timeout running, if any */
/* NB: this mustn't be enabled until we are within an xact */
if (StatementTimeout > 0)
- enable_sig_alarm(StatementTimeout, true);
+ enable_timeout(STATEMENT_TIMEOUT, StatementTimeout);
else
- cancel_from_timeout = false;
+ disable_timeout(STATEMENT_TIMEOUT, false);
xact_started = true;
}
@@ -2410,7 +2411,7 @@ finish_xact_command(void)
if (xact_started)
{
/* Cancel any active statement timeout before committing */
- disable_sig_alarm(true);
+ disable_all_timeouts(false);
/* Now commit the command */
ereport(DEBUG3,
@@ -2891,7 +2892,7 @@ ProcessInterrupts(void)
(errcode(ERRCODE_QUERY_CANCELED),
errmsg("canceling authentication due to timeout")));
}
- if (cancel_from_timeout)
+ if (get_timeout_indicator(STATEMENT_TIMEOUT))
{
ImmediateInterruptOK = false; /* not idle anymore */
DisableNotifyInterrupt();
@@ -3600,6 +3601,16 @@ PostgresMain(int argc, char *argv[], const char *username)
WalSndSignals();
else
{
+ /*
+ * Register timeout sources needed by backend operation. Note
+ * that InitializeTimeout was already called by BackendInitialize.
+ */
+ RegisterTimeout(DEADLOCK_TIMEOUT, true,
+ CheckDeadLock, GetCurrentTimestamp);
+ RegisterTimeout(STATEMENT_TIMEOUT, false,
+ StatementTimeoutHandler,
+ GetCurrentStatementStartTimestamp);
+
pqsignal(SIGHUP, SigHupHandler); /* set flag to read config
* file */
pqsignal(SIGINT, StatementCancelHandler); /* cancel current query */
@@ -3802,10 +3813,10 @@ PostgresMain(int argc, char *argv[], const char *username)
/*
* Forget any pending QueryCancel request, since we're returning to
- * the idle loop anyway, and cancel the statement timer if running.
+ * the idle loop anyway, and cancel the timer if running.
*/
QueryCancelPending = false;
- disable_sig_alarm(true);
+ disable_all_timeouts(false);
QueryCancelPending = false; /* again in case timeout occurred */
/*
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 4d4a895..36e61cd 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -41,12 +41,12 @@
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lmgr.h"
-#include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/proc.h"
#include "storage/sinvaladt.h"
#include "storage/smgr.h"
+#include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
#include "utils/fmgroids.h"
@@ -205,8 +205,7 @@ PerformAuthentication(Port *port)
* during authentication. Since we're inside a transaction and might do
* database access, we have to use the statement_timeout infrastructure.
*/
- if (!enable_sig_alarm(AuthenticationTimeout * 1000, true))
- elog(FATAL, "could not set timer for authorization timeout");
+ enable_timeout(STATEMENT_TIMEOUT, AuthenticationTimeout * 1000);
/*
* Now perform authentication exchange.
@@ -216,8 +215,7 @@ PerformAuthentication(Port *port)
/*
* Done with authentication. Disable the timeout, and log if needed.
*/
- if (!disable_sig_alarm(true))
- elog(FATAL, "could not disable timer for authorization timeout");
+ disable_timeout(STATEMENT_TIMEOUT, false);
if (Log_connections)
{
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 31f7099..4db90b8 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -222,9 +222,6 @@ extern int DeadlockTimeout;
extern int StatementTimeout;
extern bool log_lock_waits;
-extern volatile bool cancel_from_timeout;
-
-
/*
* Function Prototypes
*/
@@ -252,13 +249,10 @@ extern void LockErrorCleanup(void);
extern void ProcWaitForSignal(void);
extern void ProcSendSignal(int pid);
-extern bool enable_sig_alarm(int delayms, bool is_statement_timeout);
-extern bool disable_sig_alarm(bool is_statement_timeout);
-extern void handle_sig_alarm(SIGNAL_ARGS);
-
-extern bool enable_standby_sig_alarm(TimestampTz now,
- TimestampTz fin_time, bool deadlock_only);
-extern bool disable_standby_sig_alarm(void);
-extern void handle_standby_sig_alarm(SIGNAL_ARGS);
+/* Timeout handlers */
+extern void CheckDeadLock(void);
+extern void StatementTimeoutHandler(void);
+extern void StandbyDeadLockHandler(void);
+extern void StandbyTimeoutHandler(void);
#endif /* PROC_H */
diff --git a/src/include/storage/timeout.h b/src/include/storage/timeout.h
new file mode 100644
index 0000000..d430e20
--- /dev/null
+++ b/src/include/storage/timeout.h
@@ -0,0 +1,50 @@
+/*-------------------------------------------------------------------------
+ *
+ * timeout.h
+ * SIGALRM timeout API
+ *
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/storage/timeout.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef TIMEOUT_H
+#define TIMEOUT_H
+
+#include "datatype/timestamp.h"
+
+typedef enum TimeoutName
+{
+ AUTHENTICATION_TIMEOUT,
+ DEADLOCK_TIMEOUT,
+ STATEMENT_TIMEOUT,
+ STANDBY_DEADLOCK_TIMEOUT,
+ STANDBY_TIMEOUT,
+ /* First user timeout source */
+ USER_TIMEOUT,
+ /* Maximum number of timeout sources */
+ TIMEOUT_MAX = 16
+} TimeoutName;
+
+/* timeout setup */
+typedef void (*timeout_func)(void);
+typedef TimestampTz (*timeout_start)(void);
+
+extern void InitializeTimeouts(void);
+extern TimeoutName RegisterTimeout(TimeoutName tn, bool resched_next,
+ timeout_func func, timeout_start start);
+
+/* timeout operation */
+extern void enable_timeout(TimeoutName tn, int delayms);
+extern void disable_timeout(TimeoutName tn, bool keep_indicator);
+extern void disable_all_timeouts(bool keep_indicators);
+
+/* accessors */
+extern bool get_timeout_indicator(TimeoutName tn);
+extern TimestampTz get_timeout_start(TimeoutName tn);
+extern void handle_sig_alarm(SIGNAL_ARGS);
+
+#endif /* TIMEOUT_H */
I don't understand why PGSemaphoreTimedLock() is not broken. I mean
surely you need a bool return to let the caller know whether the
acquisition succeeded or failed? AFAICS you are relying on
get_timeout_indicator() but this seems to me the wrong thing to do ...
(not to mention how ugly it is to percolate through two levels of
abstraction)
--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support
2012-07-03 23:31 keltezéssel, Alvaro Herrera írta:
Excerpts from Boszormenyi Zoltan's message of vie jun 29 14:30:28 -0400 2012:
Does anyone have a little time to look at the latest timeout framework
with the registration interface and the 2nd patch too? I am at work
until Friday next week, after that I will be on vacation for two weeks.
Just in case there is anything that needs tweaking to make it more
acceptable.I cleaned up this a bit more and now I think it's ready to commit --
as soon as somebody tests that the standby bits still work.
You just broke initdb with this cleanup. :-)
---8<------8<------8<------8<------8<------8<------8<---
$ cat src/test/regress/log/initdb.log
Running in noclean mode. Mistakes will not be cleaned up.
The files belonging to this database system will be owned by user "zozo".
This user must also own the server process.
The database cluster will be initialized with locales
COLLATE: hu_HU.utf8
CTYPE: hu_HU.utf8
MESSAGES: C
MONETARY: hu_HU.utf8
NUMERIC: hu_HU.utf8
TIME: hu_HU.utf8
The default database encoding has accordingly been set to "UTF8".
The default text search configuration will be set to "hungarian".
creating directory
/home/zozo/lock-timeout/9.1/1/postgresql.14/src/test/regress/./tmp_check/data ... ok
creating subdirectories ... ok
selecting default max_connections ... 100
selecting default shared_buffers ... 32MB
creating configuration files ... ok
creating template1 database in
/home/zozo/lock-timeout/9.1/1/postgresql.14/src/test/regress/./tmp_check/data/base/1 ... ok
initializing pg_authid ... TRAP: FailedAssertion("!(base_timeouts_initialized)", File:
"timeout.c", Line: 217)
sh: line 1: 29872 Aborted (core dumped)
"/home/zozo/lock-timeout/9.1/1/postgresql.14/src/test/regress/tmp_check/install/home/zozo/pgc92dev-locktimeout/bin/postgres"
--single -F -O -c search_path=pg_catalog -c exit_on_error=true template1 > /dev/null
child process exited with exit code 134
initdb: data directory
"/home/zozo/lock-timeout/9.1/1/postgresql.14/src/test/regress/./tmp_check/data" not
removed at user's request
---8<------8<------8<------8<------8<------8<------8<---
initdb starts postgres --single, that doesn't do BackendInitialize(),
only PostgresMain(). So, you need InitializeTimeouts() before
the RegisterTimeout() calls in PostgresMain and the elog(PANIC)
must not be in InitializeTimeouts() if called twice.
--
----------------------------------
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/
2012-07-04 12:09 keltezéssel, Boszormenyi Zoltan írta:
2012-07-03 23:31 keltezéssel, Alvaro Herrera írta:
Excerpts from Boszormenyi Zoltan's message of vie jun 29 14:30:28 -0400 2012:
Does anyone have a little time to look at the latest timeout framework
with the registration interface and the 2nd patch too? I am at work
until Friday next week, after that I will be on vacation for two weeks.
Just in case there is anything that needs tweaking to make it more
acceptable.I cleaned up this a bit more and now I think it's ready to commit --
as soon as somebody tests that the standby bits still work.You just broke initdb with this cleanup. :-)
---8<------8<------8<------8<------8<------8<------8<---
$ cat src/test/regress/log/initdb.log
Running in noclean mode. Mistakes will not be cleaned up.
The files belonging to this database system will be owned by user "zozo".
This user must also own the server process.The database cluster will be initialized with locales
COLLATE: hu_HU.utf8
CTYPE: hu_HU.utf8
MESSAGES: C
MONETARY: hu_HU.utf8
NUMERIC: hu_HU.utf8
TIME: hu_HU.utf8
The default database encoding has accordingly been set to "UTF8".
The default text search configuration will be set to "hungarian".creating directory
/home/zozo/lock-timeout/9.1/1/postgresql.14/src/test/regress/./tmp_check/data ... ok
creating subdirectories ... ok
selecting default max_connections ... 100
selecting default shared_buffers ... 32MB
creating configuration files ... ok
creating template1 database in
/home/zozo/lock-timeout/9.1/1/postgresql.14/src/test/regress/./tmp_check/data/base/1 ... ok
initializing pg_authid ... TRAP: FailedAssertion("!(base_timeouts_initialized)", File:
"timeout.c", Line: 217)
sh: line 1: 29872 Aborted (core dumped)
"/home/zozo/lock-timeout/9.1/1/postgresql.14/src/test/regress/tmp_check/install/home/zozo/pgc92dev-locktimeout/bin/postgres"
--single -F -O -c search_path=pg_catalog -c exit_on_error=true template1 > /dev/null
child process exited with exit code 134
initdb: data directory
"/home/zozo/lock-timeout/9.1/1/postgresql.14/src/test/regress/./tmp_check/data" not
removed at user's request
---8<------8<------8<------8<------8<------8<------8<---initdb starts postgres --single, that doesn't do BackendInitialize(),
only PostgresMain(). So, you need InitializeTimeouts() before
the RegisterTimeout() calls in PostgresMain and the elog(PANIC)
must not be in InitializeTimeouts() if called twice.
Attached is the fix for this problem. PostgresMain() has a new
argument: bool single_user. This way, InitializeTimeouts() can
keep its elog(PANIC) if called twice and "postgres --single"
doesn't fail its Assert() in RegisterTimeout().
Comments?
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/
Attachments:
1-timeout-framework-v14-fix.patchtext/x-patch; name=1-timeout-framework-v14-fix.patchDownload
diff -durpN postgresql.14.orig/src/backend/main/main.c postgresql.14/src/backend/main/main.c
--- postgresql.14.orig/src/backend/main/main.c 2012-06-26 09:10:21.272759354 +0200
+++ postgresql.14/src/backend/main/main.c 2012-07-04 12:21:58.869037014 +0200
@@ -192,7 +192,7 @@ main(int argc, char *argv[])
else if (argc > 1 && strcmp(argv[1], "--describe-config") == 0)
GucInfoMain(); /* does not return */
else if (argc > 1 && strcmp(argv[1], "--single") == 0)
- PostgresMain(argc, argv, get_current_username(progname)); /* does not return */
+ PostgresMain(argc, argv, get_current_username(progname), true); /* does not return */
else
PostmasterMain(argc, argv); /* does not return */
abort(); /* should not get here */
diff -durpN postgresql.14.orig/src/backend/postmaster/postmaster.c postgresql.14/src/backend/postmaster/postmaster.c
--- postgresql.14.orig/src/backend/postmaster/postmaster.c 2012-07-04 12:25:09.247183727 +0200
+++ postgresql.14/src/backend/postmaster/postmaster.c 2012-07-04 12:23:22.933543240 +0200
@@ -3626,7 +3626,7 @@ BackendRun(Port *port)
*/
MemoryContextSwitchTo(TopMemoryContext);
- PostgresMain(ac, av, port->user_name);
+ PostgresMain(ac, av, port->user_name, false);
}
diff -durpN postgresql.14.orig/src/backend/tcop/postgres.c postgresql.14/src/backend/tcop/postgres.c
--- postgresql.14.orig/src/backend/tcop/postgres.c 2012-07-04 12:25:09.255183775 +0200
+++ postgresql.14/src/backend/tcop/postgres.c 2012-07-04 12:24:17.685873058 +0200
@@ -3509,7 +3509,7 @@ process_postgres_switches(int argc, char
* ----------------------------------------------------------------
*/
void
-PostgresMain(int argc, char *argv[], const char *username)
+PostgresMain(int argc, char *argv[], const char *username, bool single_user)
{
const char *dbname;
int firstchar;
@@ -3603,8 +3603,11 @@ PostgresMain(int argc, char *argv[], con
{
/*
* Register timeout sources needed by backend operation. Note
- * that InitializeTimeout was already called by BackendInitialize.
+ * that InitializeTimeout was already called by BackendInitialize
+ * unless we are in --single mode.
*/
+ if (single_user)
+ InitializeTimeouts();
RegisterTimeout(DEADLOCK_TIMEOUT, true,
CheckDeadLock, GetCurrentTimestamp);
RegisterTimeout(STATEMENT_TIMEOUT, false,
diff -durpN postgresql.14.orig/src/include/tcop/tcopprot.h postgresql.14/src/include/tcop/tcopprot.h
--- postgresql.14.orig/src/include/tcop/tcopprot.h 2012-06-26 09:10:21.316759721 +0200
+++ postgresql.14/src/include/tcop/tcopprot.h 2012-07-04 12:21:42.732939868 +0200
@@ -70,7 +70,7 @@ extern void prepare_for_client_read(void
extern void client_read_ended(void);
extern const char *process_postgres_switches(int argc, char *argv[],
GucContext ctx);
-extern void PostgresMain(int argc, char *argv[], const char *username) __attribute__((noreturn));
+extern void PostgresMain(int argc, char *argv[], const char *username, bool single_user) __attribute__((noreturn));
extern long get_stack_depth_rlimit(void);
extern void ResetUsage(void);
extern void ShowUsage(const char *title);
Binary files postgresql.14.orig/src/test/regress/core.29872 and postgresql.14/src/test/regress/core.29872 differ
2012-07-03 23:38 keltezéssel, Alvaro Herrera írta:
I don't understand why PGSemaphoreTimedLock() is not broken. I mean
surely you need a bool return to let the caller know whether the
acquisition succeeded or failed?
Well, this is the same interface PGSemaphoreTryLock() uses.
By this reasoning, it's also broken.
AFAICS you are relying on
get_timeout_indicator() but this seems to me the wrong thing to do ...
(not to mention how ugly it is to percolate through two levels of
abstraction)
What other way do you suggest? EINTR may come from
a different signal, which may also be ignored or not. Ctrl-C
is handled and leads to elog(ERROR) but an ignored signal
technically calls a NOP handler deep inside the OS runtime
libraries but the signal *is* delivered to the backend which
in turn interrupts semop() or whatever the platform equivalent is.
I can add a flag to timeout.c that is set whenever SIGALRM
is delivered but checking that would be another "abstraction
violation" as calling get_timeout_indicator() in your opinion.
The original coding of PGSemaphoreTryLock() used
semtimedop(), sem_timedwait() and the timeout value applied
to WaitForMultipleObjectsEx(). This was quickly shot down
as using the SIGALRM signal and its behaviour to interrupt the
locking operation is be better and fits the PostgreSQL portability
features. Also, OS X at the time didn't support sem_timedwait().
I am not complaining, just recalling the different details.
How about not hardcoding get_timeout_indicator(LOCK_TIMEOUT)
into PGSemaphoreTimedLock()? Passing TimeoutName to it would
make it more generic and usable for other timeout sources.
--
----------------------------------
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/
Excerpts from Boszormenyi Zoltan's message of mié jul 04 06:32:46 -0400 2012:
2012-07-04 12:09 keltezéssel, Boszormenyi Zoltan írta:
You just broke initdb with this cleanup. :-)
Ouch.
initdb starts postgres --single, that doesn't do BackendInitialize(),
only PostgresMain(). So, you need InitializeTimeouts() before
the RegisterTimeout() calls in PostgresMain and the elog(PANIC)
must not be in InitializeTimeouts() if called twice.Attached is the fix for this problem. PostgresMain() has a new
argument: bool single_user. This way, InitializeTimeouts() can
keep its elog(PANIC) if called twice and "postgres --single"
doesn't fail its Assert() in RegisterTimeout().
Hmm. Maybe it's better to leave InitializeTimeouts to be called twice
after all. The fix seems a lot uglier than the disease it's curing.
--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support
Excerpts from Boszormenyi Zoltan's message of mié jul 04 07:03:44 -0400 2012:
2012-07-03 23:38 keltezéssel, Alvaro Herrera írta:
I don't understand why PGSemaphoreTimedLock() is not broken. I mean
surely you need a bool return to let the caller know whether the
acquisition succeeded or failed?Well, this is the same interface PGSemaphoreTryLock() uses.
By this reasoning, it's also broken.
Eh, no -- as far as I can see, PGSemaphoreTryLock returns a boolean,
which is precisely my point.
--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support
2012-07-04 17:25 keltezéssel, Alvaro Herrera írta:
Excerpts from Boszormenyi Zoltan's message of mié jul 04 07:03:44 -0400 2012:
2012-07-03 23:38 keltezéssel, Alvaro Herrera írta:
I don't understand why PGSemaphoreTimedLock() is not broken. I mean
surely you need a bool return to let the caller know whether the
acquisition succeeded or failed?Well, this is the same interface PGSemaphoreTryLock() uses.
By this reasoning, it's also broken.Eh, no -- as far as I can see, PGSemaphoreTryLock returns a boolean,
which is precisely my point.
You're right. I blame the heat for not being able to properly
read my own code.
--
----------------------------------
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/
2012-07-04 17:20 keltezéssel, Alvaro Herrera írta:
Excerpts from Boszormenyi Zoltan's message of mié jul 04 06:32:46 -0400 2012:
2012-07-04 12:09 keltezéssel, Boszormenyi Zoltan írta:
You just broke initdb with this cleanup. :-)
Ouch.
initdb starts postgres --single, that doesn't do BackendInitialize(),
only PostgresMain(). So, you need InitializeTimeouts() before
the RegisterTimeout() calls in PostgresMain and the elog(PANIC)
must not be in InitializeTimeouts() if called twice.Attached is the fix for this problem. PostgresMain() has a new
argument: bool single_user. This way, InitializeTimeouts() can
keep its elog(PANIC) if called twice and "postgres --single"
doesn't fail its Assert() in RegisterTimeout().Hmm. Maybe it's better to leave InitializeTimeouts to be called twice
after all. The fix seems a lot uglier than the disease it's curing.
Attached are the refreshed patches. InitializeTimeouts() can be called
twice and PGSemaphoreTimedLock() returns bool now. This saves
two calls to get_timeout_indicator().
--
----------------------------------
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/
Attachments:
1-timeout-framework-v15.patchtext/x-patch; name=1-timeout-framework-v15.patchDownload
diff -durpN postgresql/src/backend/postmaster/autovacuum.c postgresql.1/src/backend/postmaster/autovacuum.c
--- postgresql/src/backend/postmaster/autovacuum.c 2012-06-26 09:10:21.275759379 +0200
+++ postgresql.1/src/backend/postmaster/autovacuum.c 2012-07-04 11:01:15.331090555 +0200
@@ -89,6 +89,7 @@
#include "storage/proc.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
+#include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
@@ -484,7 +485,7 @@ AutoVacLauncherMain(int argc, char *argv
/* Forget any pending QueryCancel request */
QueryCancelPending = false;
- disable_sig_alarm(true);
+ disable_all_timeouts(false);
QueryCancelPending = false; /* again in case timeout occurred */
/* Report the error to the server log */
diff -durpN postgresql/src/backend/postmaster/postmaster.c postgresql.1/src/backend/postmaster/postmaster.c
--- postgresql/src/backend/postmaster/postmaster.c 2012-06-26 09:10:21.277759395 +0200
+++ postgresql.1/src/backend/postmaster/postmaster.c 2012-07-04 19:13:03.431407389 +0200
@@ -112,7 +112,7 @@
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
-#include "storage/proc.h"
+#include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/datetime.h"
@@ -3365,6 +3365,17 @@ report_fork_failure_to_client(Port *port
} while (rc < 0 && errno == EINTR);
}
+/*
+ * DummyTimeoutFunction
+ *
+ * Do nothing -- just to break out of a sleeping system call.
+ * The timeout indicator is set by the signal handler.
+ */
+static void
+DummyTimeoutFunction(void)
+{
+ return;
+}
/*
* BackendInitialize -- initialize an interactive (postmaster-child)
@@ -3437,6 +3448,13 @@ BackendInitialize(Port *port)
PG_SETMASK(&StartupBlockSig);
/*
+ * Initialize timeout sources and register authentication timeout early.
+ */
+ InitializeTimeouts();
+ RegisterTimeout(AUTHENTICATION_TIMEOUT, false,
+ DummyTimeoutFunction, GetCurrentTimestamp);
+
+ /*
* Get the remote host name and port for logging and status display.
*/
remote_host[0] = '\0';
@@ -3488,8 +3506,7 @@ BackendInitialize(Port *port)
* indefinitely. PreAuthDelay and any DNS interactions above don't count
* against the time limit.
*/
- if (!enable_sig_alarm(AuthenticationTimeout * 1000, false))
- elog(FATAL, "could not set timer for startup packet timeout");
+ enable_timeout(AUTHENTICATION_TIMEOUT, AuthenticationTimeout * 1000);
/*
* Receive the startup packet (which might turn out to be a cancel request
@@ -3526,8 +3543,7 @@ BackendInitialize(Port *port)
/*
* Disable the timeout, and prevent SIGTERM/SIGQUIT again.
*/
- if (!disable_sig_alarm(false))
- elog(FATAL, "could not disable timer for startup packet timeout");
+ disable_timeout(AUTHENTICATION_TIMEOUT, false);
PG_SETMASK(&BlockSig);
}
diff -durpN postgresql/src/backend/postmaster/startup.c postgresql.1/src/backend/postmaster/startup.c
--- postgresql/src/backend/postmaster/startup.c 2012-04-16 19:57:22.442915536 +0200
+++ postgresql.1/src/backend/postmaster/startup.c 2012-07-04 11:01:15.335090580 +0200
@@ -28,7 +28,9 @@
#include "storage/latch.h"
#include "storage/pmsignal.h"
#include "storage/proc.h"
+#include "storage/timeout.h"
#include "utils/guc.h"
+#include "utils/timestamp.h"
/*
@@ -184,19 +186,23 @@ StartupProcessMain(void)
#endif
/*
+ * Initialize timeouts and register timeouts needed for standby.
+ */
+ InitializeTimeouts();
+ RegisterTimeout(STANDBY_DEADLOCK_TIMEOUT, true,
+ StandbyDeadLockHandler, GetCurrentTimestamp);
+ RegisterTimeout(STANDBY_TIMEOUT, false,
+ StandbyTimeoutHandler, GetCurrentTimestamp);
+
+ /*
* Properly accept or ignore signals the postmaster might send us.
- *
- * Note: ideally we'd not enable handle_standby_sig_alarm unless actually
- * doing hot standby, but we don't know that yet. Rely on it to not do
- * anything if it shouldn't.
*/
pqsignal(SIGHUP, StartupProcSigHupHandler); /* reload config file */
pqsignal(SIGINT, SIG_IGN); /* ignore query cancel */
pqsignal(SIGTERM, StartupProcShutdownHandler); /* request shutdown */
pqsignal(SIGQUIT, startupproc_quickdie); /* hard crash time */
if (EnableHotStandby)
- pqsignal(SIGALRM, handle_standby_sig_alarm); /* ignored unless
- * InHotStandby */
+ pqsignal(SIGALRM, handle_sig_alarm);
else
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
diff -durpN postgresql/src/backend/storage/ipc/standby.c postgresql.1/src/backend/storage/ipc/standby.c
--- postgresql/src/backend/storage/ipc/standby.c 2012-06-26 09:10:21.280759421 +0200
+++ postgresql.1/src/backend/storage/ipc/standby.c 2012-07-04 11:01:15.336090586 +0200
@@ -27,6 +27,7 @@
#include "storage/procarray.h"
#include "storage/sinvaladt.h"
#include "storage/standby.h"
+#include "storage/timeout.h"
#include "utils/ps_status.h"
#include "utils/timestamp.h"
@@ -394,7 +395,6 @@ ResolveRecoveryConflictWithLock(Oid dbOi
void
ResolveRecoveryConflictWithBufferPin(void)
{
- bool sig_alarm_enabled = false;
TimestampTz ltime;
TimestampTz now;
@@ -409,10 +409,7 @@ ResolveRecoveryConflictWithBufferPin(voi
* We're willing to wait forever for conflicts, so set timeout for
* deadlock check (only)
*/
- if (enable_standby_sig_alarm(now, now, true))
- sig_alarm_enabled = true;
- else
- elog(FATAL, "could not set timer for process wakeup");
+ enable_timeout(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout);
}
else if (now >= ltime)
{
@@ -423,24 +420,26 @@ ResolveRecoveryConflictWithBufferPin(voi
}
else
{
+ long secs,
+ msecs;
+ int usecs;
+
/*
* Wake up at ltime, and check for deadlocks as well if we will be
* waiting longer than deadlock_timeout
*/
- if (enable_standby_sig_alarm(now, ltime, false))
- sig_alarm_enabled = true;
- else
- elog(FATAL, "could not set timer for process wakeup");
+ enable_timeout(STANDBY_DEADLOCK_TIMEOUT, DeadlockTimeout);
+
+ TimestampDifference(now, ltime, &secs, &usecs);
+ msecs = secs * 1000 + usecs / 1000;
+
+ enable_timeout(STANDBY_TIMEOUT, msecs);
}
/* Wait to be signaled by UnpinBuffer() */
ProcWaitForSignal();
- if (sig_alarm_enabled)
- {
- if (!disable_standby_sig_alarm())
- elog(FATAL, "could not disable timer for process wakeup");
- }
+ disable_all_timeouts(false);
}
void
diff -durpN postgresql/src/backend/storage/lmgr/Makefile postgresql.1/src/backend/storage/lmgr/Makefile
--- postgresql/src/backend/storage/lmgr/Makefile 2012-04-16 19:57:22.458915722 +0200
+++ postgresql.1/src/backend/storage/lmgr/Makefile 2012-07-04 11:01:15.336090586 +0200
@@ -12,7 +12,7 @@ subdir = src/backend/storage/lmgr
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
-OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o predicate.o
+OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o predicate.o timeout.o
include $(top_srcdir)/src/backend/common.mk
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-06-26 09:10:21.282759438 +0200
+++ postgresql.1/src/backend/storage/lmgr/proc.c 2012-07-04 11:01:15.338090598 +0200
@@ -48,6 +48,7 @@
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/spin.h"
+#include "storage/timeout.h"
#include "utils/timestamp.h"
@@ -77,27 +78,12 @@ PGPROC *PreparedXactProcs = NULL;
/* If we are waiting for a lock, this points to the associated LOCALLOCK */
static LOCALLOCK *lockAwaited = NULL;
-/* Mark these volatile because they can be changed by signal handler */
-static volatile bool standby_timeout_active = false;
-static volatile bool statement_timeout_active = false;
-static volatile bool deadlock_timeout_active = false;
-static volatile DeadLockState deadlock_state = DS_NOT_YET_CHECKED;
-volatile bool cancel_from_timeout = false;
-
-/* timeout_start_time is set when log_lock_waits is true */
-static TimestampTz timeout_start_time;
-
-/* statement_fin_time is valid only if statement_timeout_active is true */
-static TimestampTz statement_fin_time;
-static TimestampTz statement_fin_time2; /* valid only in recovery */
-
+/* Mark this volatile because it can be changed by signal handler */
+static volatile DeadLockState deadlock_state;
static void RemoveProcFromArray(int code, Datum arg);
static void ProcKill(int code, Datum arg);
static void AuxiliaryProcKill(int code, Datum arg);
-static bool CheckStatementTimeout(void);
-static bool CheckStandbyTimeout(void);
-
/*
* Report shared-memory space needed by InitProcGlobal.
@@ -653,7 +639,7 @@ LockErrorCleanup(void)
return;
/* Turn off the deadlock timer, if it's still running (see ProcSleep) */
- disable_sig_alarm(false);
+ disable_timeout(DEADLOCK_TIMEOUT, false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
@@ -1048,8 +1034,7 @@ ProcSleep(LOCALLOCK *locallock, LockMeth
* 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 (!enable_sig_alarm(DeadlockTimeout, false))
- elog(FATAL, "could not set timer for process wakeup");
+ enable_timeout(DEADLOCK_TIMEOUT, DeadlockTimeout);
/*
* If someone wakes us between LWLockRelease and PGSemaphoreLock,
@@ -1138,7 +1123,8 @@ ProcSleep(LOCALLOCK *locallock, LockMeth
DescribeLockTag(&buf, &locallock->tag.lock);
modename = GetLockmodeName(locallock->tag.lock.locktag_lockmethodid,
lockmode);
- TimestampDifference(timeout_start_time, GetCurrentTimestamp(),
+ TimestampDifference(get_timeout_start(DEADLOCK_TIMEOUT),
+ GetCurrentTimestamp(),
&secs, &usecs);
msecs = secs * 1000 + usecs / 1000;
usecs = usecs % 1000;
@@ -1200,8 +1186,7 @@ ProcSleep(LOCALLOCK *locallock, LockMeth
/*
* Disable the timer, if it's still running
*/
- if (!disable_sig_alarm(false))
- elog(FATAL, "could not disable timer for process wakeup");
+ disable_timeout(DEADLOCK_TIMEOUT, false);
/*
* Re-acquire the lock table's partition lock. We have to do this to hold
@@ -1344,7 +1329,7 @@ ProcLockWakeup(LockMethod lockMethodTabl
* NB: this is run inside a signal handler, so be very wary about what is done
* here or in called routines.
*/
-static void
+void
CheckDeadLock(void)
{
int i;
@@ -1499,400 +1484,38 @@ ProcSendSignal(int pid)
PGSemaphoreUnlock(&proc->sem);
}
-
-/*****************************************************************************
- * SIGALRM interrupt support
- *
- * Maybe these should be in pqsignal.c?
- *****************************************************************************/
-
-/*
- * Enable the SIGALRM interrupt to fire after the specified delay
- *
- * Delay is given in milliseconds. Caller should be sure a SIGALRM
- * signal handler is installed before this is called.
- *
- * This code properly handles nesting of deadlock timeout alarms within
- * statement timeout alarms.
- *
- * Returns TRUE if okay, FALSE on failure.
- */
-bool
-enable_sig_alarm(int delayms, bool is_statement_timeout)
-{
- TimestampTz fin_time;
- struct itimerval timeval;
-
- if (is_statement_timeout)
- {
- /*
- * Begin statement-level timeout
- *
- * Note that we compute statement_fin_time with reference to the
- * statement_timestamp, but apply the specified delay without any
- * correction; that is, we ignore whatever time has elapsed since
- * statement_timestamp was set. In the normal case only a small
- * interval will have elapsed and so this doesn't matter, but there
- * are corner cases (involving multi-statement query strings with
- * embedded COMMIT or ROLLBACK) where we might re-initialize the
- * statement timeout long after initial receipt of the message. In
- * such cases the enforcement of the statement timeout will be a bit
- * inconsistent. This annoyance is judged not worth the cost of
- * performing an additional gettimeofday() here.
- */
- Assert(!deadlock_timeout_active);
- fin_time = GetCurrentStatementStartTimestamp();
- fin_time = TimestampTzPlusMilliseconds(fin_time, delayms);
- statement_fin_time = fin_time;
- cancel_from_timeout = false;
- statement_timeout_active = true;
- }
- else if (statement_timeout_active)
- {
- /*
- * Begin deadlock timeout with statement-level timeout active
- *
- * Here, we want to interrupt at the closer of the two timeout times.
- * If fin_time >= statement_fin_time then we need not touch the
- * existing timer setting; else set up to interrupt at the deadlock
- * timeout time.
- *
- * NOTE: in this case it is possible that this routine will be
- * interrupted by the previously-set timer alarm. This is okay
- * because the signal handler will do only what it should do according
- * to the state variables. The deadlock checker may get run earlier
- * than normal, but that does no harm.
- */
- timeout_start_time = GetCurrentTimestamp();
- fin_time = TimestampTzPlusMilliseconds(timeout_start_time, delayms);
- deadlock_timeout_active = true;
- if (fin_time >= statement_fin_time)
- return true;
- }
- else
- {
- /* Begin deadlock timeout with no statement-level timeout */
- deadlock_timeout_active = true;
- /* GetCurrentTimestamp can be expensive, so only do it if we must */
- if (log_lock_waits)
- timeout_start_time = GetCurrentTimestamp();
- }
-
- /* If we reach here, okay to set the timer interrupt */
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = delayms / 1000;
- timeval.it_value.tv_usec = (delayms % 1000) * 1000;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- return true;
-}
-
-/*
- * Cancel the SIGALRM timer, either for a deadlock timeout or a statement
- * timeout. If a deadlock timeout is canceled, any active statement timeout
- * remains in force.
- *
- * Returns TRUE if okay, FALSE on failure.
- */
-bool
-disable_sig_alarm(bool is_statement_timeout)
-{
- /*
- * Always disable the interrupt if it is active; this avoids being
- * interrupted by the signal handler and thereby possibly getting
- * confused.
- *
- * We will re-enable the interrupt if necessary in CheckStatementTimeout.
- */
- if (statement_timeout_active || deadlock_timeout_active)
- {
- struct itimerval timeval;
-
- MemSet(&timeval, 0, sizeof(struct itimerval));
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- {
- statement_timeout_active = false;
- cancel_from_timeout = false;
- deadlock_timeout_active = false;
- return false;
- }
- }
-
- /* Always cancel deadlock timeout, in case this is error cleanup */
- deadlock_timeout_active = false;
-
- /* Cancel or reschedule statement timeout */
- if (is_statement_timeout)
- {
- statement_timeout_active = false;
- cancel_from_timeout = false;
- }
- else if (statement_timeout_active)
- {
- if (!CheckStatementTimeout())
- return false;
- }
- return true;
-}
-
-
/*
* Check for statement timeout. If the timeout time has come,
- * trigger a query-cancel interrupt; if not, reschedule the SIGALRM
- * interrupt to occur at the right time.
- *
- * Returns true if okay, false if failed to set the interrupt.
+ * trigger a query-cancel interrupt.
*/
-static bool
-CheckStatementTimeout(void)
+void
+StatementTimeoutHandler(void)
{
- TimestampTz now;
-
- if (!statement_timeout_active)
- return true; /* do nothing if not active */
-
- now = GetCurrentTimestamp();
-
- if (now >= statement_fin_time)
- {
- /* Time to die */
- statement_timeout_active = false;
- cancel_from_timeout = true;
#ifdef HAVE_SETSID
- /* try to signal whole process group */
- kill(-MyProcPid, SIGINT);
+ /* try to signal whole process group */
+ kill(-MyProcPid, SIGINT);
#endif
- kill(MyProcPid, SIGINT);
- }
- else
- {
- /* Not time yet, so (re)schedule the interrupt */
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
-
- /*
- * It's possible that the difference is less than a microsecond;
- * ensure we don't cancel, rather than set, the interrupt.
- */
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- }
-
- return true;
+ kill(MyProcPid, SIGINT);
}
-
/*
- * Signal handler for SIGALRM for normal user backends
- *
- * Process deadlock check and/or statement timeout check, as needed.
- * To avoid various edge cases, we must be careful to do nothing
- * when there is nothing to be done. We also need to be able to
- * reschedule the timer interrupt if called before end of statement.
+ * StandbyDeadLockHandler() will trigger if the deadlock timeout
+ * happens earlier than StandbyTimeout. If it triggers,
+ * StandbyTimeout will still be rescheduled.
*/
void
-handle_sig_alarm(SIGNAL_ARGS)
-{
- int save_errno = errno;
-
- /* SIGALRM is cause for waking anything waiting on the process latch */
- if (MyProc)
- SetLatch(&MyProc->procLatch);
-
- if (deadlock_timeout_active)
- {
- deadlock_timeout_active = false;
- CheckDeadLock();
- }
-
- if (statement_timeout_active)
- (void) CheckStatementTimeout();
-
- errno = save_errno;
-}
-
-/*
- * Signal handler for SIGALRM in Startup process
- *
- * To avoid various edge cases, we must be careful to do nothing
- * when there is nothing to be done. We also need to be able to
- * reschedule the timer interrupt if called before end of statement.
- *
- * We set either deadlock_timeout_active or statement_timeout_active
- * or both. Interrupts are enabled if standby_timeout_active.
- */
-bool
-enable_standby_sig_alarm(TimestampTz now, TimestampTz fin_time, bool deadlock_only)
-{
- TimestampTz deadlock_time = TimestampTzPlusMilliseconds(now,
- DeadlockTimeout);
-
- if (deadlock_only)
- {
- /*
- * Wake up at deadlock_time only, then wait forever
- */
- statement_fin_time = deadlock_time;
- deadlock_timeout_active = true;
- statement_timeout_active = false;
- }
- else if (fin_time > deadlock_time)
- {
- /*
- * Wake up at deadlock_time, then again at fin_time
- */
- statement_fin_time = deadlock_time;
- statement_fin_time2 = fin_time;
- deadlock_timeout_active = true;
- statement_timeout_active = true;
- }
- else
- {
- /*
- * Wake only at fin_time because its fairly soon
- */
- statement_fin_time = fin_time;
- deadlock_timeout_active = false;
- statement_timeout_active = true;
- }
-
- if (deadlock_timeout_active || statement_timeout_active)
- {
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- standby_timeout_active = true;
- }
-
- return true;
-}
-
-bool
-disable_standby_sig_alarm(void)
+StandbyDeadLockHandler(void)
{
- /*
- * Always disable the interrupt if it is active; this avoids being
- * interrupted by the signal handler and thereby possibly getting
- * confused.
- *
- * We will re-enable the interrupt if necessary in CheckStandbyTimeout.
- */
- if (standby_timeout_active)
- {
- struct itimerval timeval;
-
- MemSet(&timeval, 0, sizeof(struct itimerval));
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- {
- standby_timeout_active = false;
- return false;
- }
- }
-
- standby_timeout_active = false;
-
- return true;
+ SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
}
/*
- * CheckStandbyTimeout() runs unconditionally in the Startup process
+ * StandbyTimeoutHandler() runs unconditionally in the Startup process
* SIGALRM handler. Timers will only be set when InHotStandby.
* We simply ignore any signals unless the timer has been set.
*/
-static bool
-CheckStandbyTimeout(void)
-{
- TimestampTz now;
- bool reschedule = false;
-
- standby_timeout_active = false;
-
- now = GetCurrentTimestamp();
-
- /*
- * Reschedule the timer if its not time to wake yet, or if we have both
- * timers set and the first one has just been reached.
- */
- if (now >= statement_fin_time)
- {
- if (deadlock_timeout_active)
- {
- /*
- * We're still waiting when we reach deadlock timeout, so send out
- * a request to have other backends check themselves for deadlock.
- * Then continue waiting until statement_fin_time, if that's set.
- */
- SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
- deadlock_timeout_active = false;
-
- /*
- * Begin second waiting period if required.
- */
- if (statement_timeout_active)
- {
- reschedule = true;
- statement_fin_time = statement_fin_time2;
- }
- }
- else
- {
- /*
- * We've now reached statement_fin_time, so ask all conflicts to
- * leave, so we can press ahead with applying changes in recovery.
- */
- SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
- }
- }
- else
- reschedule = true;
-
- if (reschedule)
- {
- long secs;
- int usecs;
- struct itimerval timeval;
-
- TimestampDifference(now, statement_fin_time,
- &secs, &usecs);
- if (secs == 0 && usecs == 0)
- usecs = 1;
- MemSet(&timeval, 0, sizeof(struct itimerval));
- timeval.it_value.tv_sec = secs;
- timeval.it_value.tv_usec = usecs;
- if (setitimer(ITIMER_REAL, &timeval, NULL))
- return false;
- standby_timeout_active = true;
- }
-
- return true;
-}
-
void
-handle_standby_sig_alarm(SIGNAL_ARGS)
+StandbyTimeoutHandler(void)
{
- int save_errno = errno;
-
- if (standby_timeout_active)
- (void) CheckStandbyTimeout();
-
- errno = save_errno;
+ SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
}
diff -durpN postgresql/src/backend/storage/lmgr/timeout.c postgresql.1/src/backend/storage/lmgr/timeout.c
--- postgresql/src/backend/storage/lmgr/timeout.c 1970-01-01 01:00:00.000000000 +0100
+++ postgresql.1/src/backend/storage/lmgr/timeout.c 2012-07-04 19:15:47.087462348 +0200
@@ -0,0 +1,429 @@
+/*-------------------------------------------------------------------------
+ *
+ * timeout.c
+ * routines to manage timeout sources handled by SIGALRM
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/storage/lmgr/timeout.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <sys/time.h>
+
+#include "storage/proc.h"
+#include "storage/timeout.h"
+#include "utils/timestamp.h"
+
+
+static void InitTimeout(TimeoutName tn, TimestampTz start_time,
+ TimestampTz fin_time);
+static void DestroyTimeout(TimeoutName tn, bool keep_indicator);
+
+typedef struct timeout_params
+{
+ TimeoutName index;
+ bool resched_next;
+
+ /* volatile because it may be changed from the signal handler */
+ volatile bool indicator;
+
+ timeout_func timeout_func;
+ timeout_start timeout_start;
+
+ TimestampTz start_time;
+ TimestampTz fin_time;
+} timeout_params;
+
+/*
+ * List of possible timeout reasons in the order of enum TimeoutName.
+ * The priority of timeouts (in case two of them would trigger at the
+ * same time) is determined by this order: the earlier one in the list
+ * has higher priority. InitializeBackend() and PostgresMain() initialize
+ * this array and register timeout sources.
+ */
+static bool base_timeouts_initialized = false;
+static timeout_params base_timeouts[TIMEOUT_MAX];
+
+/*
+ * List of active timeouts ordered by their fin_time and priority.
+ */
+static int NumActiveTimeouts = 0;
+static timeout_params *ActiveTimeouts[TIMEOUT_MAX];
+
+/*****************************************************************************
+ * Internal helper functions
+ *****************************************************************************/
+
+/*
+ * Find the index of a given timeout type in the active array
+ */
+static int
+find_active_timeout(TimeoutName tn)
+{
+ int i;
+
+ for (i = 0; i < NumActiveTimeouts; i++)
+ {
+ if (ActiveTimeouts[i]->index == tn)
+ return i;
+ }
+
+ return -1;
+}
+
+#define is_timeout_active(tn) (find_active_timeout(tn) >= 0)
+
+/*
+ * Insert tn'th timeout into the list of timeouts at the given index
+ * if the previous timeout allows rescheduling the next one in the list.
+ */
+static void
+insert_timeout(TimeoutName tn, int index)
+{
+ int i;
+
+ if (index < 0 || index > NumActiveTimeouts)
+ elog(FATAL, "timeout index %d out of range 0..%d", index,
+ NumActiveTimeouts);
+
+ if (index > 0 && !ActiveTimeouts[index - 1]->resched_next)
+ return;
+
+ for (i = NumActiveTimeouts - 1; i >= index; i--)
+ ActiveTimeouts[i + 1] = ActiveTimeouts[i];
+
+ ActiveTimeouts[index] = &base_timeouts[tn];
+ NumActiveTimeouts++;
+}
+
+/*
+ * Remove the index'th element from the timeout list.
+ */
+static void
+remove_timeout_index(int index)
+{
+ int i;
+
+ if (index < 0 || index > NumActiveTimeouts)
+ elog(FATAL, "timeout index %d out of range 0..%d", index,
+ NumActiveTimeouts);
+
+ for (i = index + 1; i < NumActiveTimeouts; i++)
+ ActiveTimeouts[i - 1] = ActiveTimeouts[i];
+
+ if (NumActiveTimeouts > 0 && index < NumActiveTimeouts)
+ NumActiveTimeouts--;
+}
+
+/*
+ * (Re)schedule alarm for the next active timeout
+ */
+static void
+schedule_alarm(TimestampTz now)
+{
+ long secs;
+ int usecs;
+ struct itimerval timeval;
+
+ /* There is no active timeout, do nothing. */
+ if (NumActiveTimeouts == 0)
+ return;
+
+ TimestampDifference(now, ActiveTimeouts[0]->fin_time,
+ &secs, &usecs);
+
+ /*
+ * It's possible that the difference is less than a microsecond;
+ * ensure we don't cancel, rather than set, the interrupt.
+ */
+ if (secs == 0 && usecs == 0)
+ usecs = 1;
+
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ timeval.it_value.tv_sec = secs;
+ timeval.it_value.tv_usec = usecs;
+ if (setitimer(ITIMER_REAL, &timeval, NULL) != 0)
+ elog(FATAL, "could not enable timer");
+}
+
+/*****************************************************************************
+ * Init and Destroy functions
+ *****************************************************************************/
+
+static void
+InitTimeout(TimeoutName tn, TimestampTz start_time, TimestampTz fin_time)
+{
+ base_timeouts[tn].indicator = false;
+ base_timeouts[tn].start_time = start_time;
+ base_timeouts[tn].fin_time = fin_time;
+}
+
+static void
+DestroyTimeout(TimeoutName tn, bool keep_indicator)
+{
+ if (!keep_indicator)
+ base_timeouts[tn].indicator = false;
+ base_timeouts[tn].start_time = 0;
+ base_timeouts[tn].fin_time = 0;
+}
+
+/*****************************************************************************
+ * Public API
+ *****************************************************************************/
+
+/*
+ * Initialize timeout module.
+ *
+ * This must be called on every process that wants to use timeouts.
+ */
+void
+InitializeTimeouts(void)
+{
+ int i;
+
+ if (base_timeouts_initialized)
+ return;
+
+ for (i = 0; i < TIMEOUT_MAX; i++)
+ {
+ base_timeouts[i].index = i;
+ base_timeouts[i].resched_next = false;
+ base_timeouts[i].indicator = false;
+ base_timeouts[i].timeout_func = NULL;
+ base_timeouts[i].timeout_start = NULL;
+ base_timeouts[i].start_time = 0;
+ base_timeouts[i].fin_time = 0;
+ }
+
+ base_timeouts_initialized = true;
+}
+
+/*
+ * Register a timeout source
+ */
+TimeoutName
+RegisterTimeout(TimeoutName tn, bool resched_next,
+ timeout_func func, timeout_start start)
+{
+ Assert(base_timeouts_initialized);
+ Assert(base_timeouts[tn].timeout_func == NULL);
+
+ if (tn < 0)
+ {
+ for (tn = USER_TIMEOUT; tn < TIMEOUT_MAX; tn++)
+ if (base_timeouts[tn].timeout_func == NULL)
+ break;
+ if (tn >= TIMEOUT_MAX)
+ ereport(FATAL,
+ (errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
+ errmsg("cannot add more timeout sources")));
+ }
+
+ base_timeouts[tn].resched_next = resched_next;
+ base_timeouts[tn].timeout_func = func;
+ base_timeouts[tn].timeout_start = start;
+
+ return tn;
+}
+
+/*
+ * Enable the SIGALRM interrupt to fire after the specified delay
+ *
+ * Delay is given in milliseconds. Caller should be sure a SIGALRM
+ * signal handler is installed before this is called.
+ *
+ * This code properly handles nesting of different timeout alarms.
+ */
+void
+enable_timeout(TimeoutName tn, int delayms)
+{
+ TimestampTz start_time;
+ TimestampTz fin_time;
+ int i;
+
+ Assert(base_timeouts_initialized);
+ Assert(base_timeouts[tn].timeout_func != NULL);
+ Assert(!is_timeout_active(tn));
+
+ if (delayms <= 0)
+ return;
+
+ start_time = base_timeouts[tn].timeout_start();
+ fin_time = TimestampTzPlusMilliseconds(start_time, delayms);
+
+ /* Find out the index where to insert the new timeout. */
+ for (i = 0; i < NumActiveTimeouts; i++)
+ {
+ /*
+ * The new timeout triggers earlier than a previously added one:
+ * insert here.
+ */
+ if (fin_time < ActiveTimeouts[i]->fin_time)
+ break;
+ /*
+ * The new timeout triggers at the same time as the previously added
+ * one but has greater priority.
+ */
+ if (fin_time == ActiveTimeouts[i]->fin_time &&
+ tn < ActiveTimeouts[i]->index)
+ break;
+ }
+
+ /*
+ * Initialize the timeout parameters
+ */
+ InitTimeout(tn, start_time, fin_time);
+
+ insert_timeout(tn, i);
+
+ /*
+ * If the timeout was inserted in the first position, set the timer.
+ */
+ if (i == 0)
+ schedule_alarm(start_time);
+}
+
+/*
+ * Cancel the SIGALRM timer for the specific timeout.
+ * If a timeout is canceled, any other active timeout remains in force.
+ *
+ * It's not an error to disable a timeout that is not enabled.
+ */
+void
+disable_timeout(TimeoutName tn, bool keep_indicator)
+{
+ int i;
+
+ /*
+ * Always disable the timer if it is active; this avoids being
+ * interrupted by the signal handler and thereby possibly getting
+ * confused.
+ *
+ * We will re-enable the interrupt if necessary later in this function.
+ */
+ if (NumActiveTimeouts > 0)
+ {
+ struct itimerval timeval;
+
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ if (setitimer(ITIMER_REAL, &timeval, NULL) != 0)
+ elog(FATAL, "could not disable timer");
+ }
+
+ /* Find the timeout and remove from the list. */
+ i = find_active_timeout(tn);
+ if (i >= 0)
+ {
+ remove_timeout_index(i);
+ DestroyTimeout(tn, keep_indicator);
+ }
+
+ /*
+ * Now re-enable the timer, if necessary.
+ */
+ if (NumActiveTimeouts > 0)
+ schedule_alarm(GetCurrentTimestamp());
+}
+
+/*
+ * Disable SIGALRM and remove all timeouts from the list and
+ * reset the timeout indicators.
+ */
+void
+disable_all_timeouts(bool keep_indicators)
+{
+ struct itimerval timeval;
+ int i;
+
+ MemSet(&timeval, 0, sizeof(struct itimerval));
+ if (setitimer(ITIMER_REAL, &timeval, NULL) != 0)
+ elog(FATAL, "could not disable timer");
+
+ NumActiveTimeouts = 0;
+ for (i = 0; i < TIMEOUT_MAX; i++)
+ DestroyTimeout(i, keep_indicators);
+}
+
+/*
+ * Return the timeout indicator
+ */
+bool
+get_timeout_indicator(TimeoutName tn)
+{
+ return base_timeouts[tn].indicator;
+}
+
+/*
+ * Return the start of the timer for this timeout
+ */
+TimestampTz
+get_timeout_start(TimeoutName tn)
+{
+ return base_timeouts[tn].start_time;
+}
+
+/*
+ * Signal handler for SIGALRM
+ *
+ * Process the check for the currently active timeout source and
+ * reschedule the next as needed. To avoid various edge cases,
+ * we must be careful to do nothing when there is nothing to be done.
+ */
+void
+handle_sig_alarm(SIGNAL_ARGS)
+{
+ int save_errno = errno;
+
+ /*
+ * SIGALRM is cause for waking anything waiting on the process latch.
+ * Cope with MyProc not being there, as the startup process also uses
+ * this signal handler.
+ */
+ if (MyProc)
+ SetLatch(&MyProc->procLatch);
+
+ if (NumActiveTimeouts > 0)
+ {
+ TimestampTz now;
+ bool reschedule;
+
+ now = GetCurrentTimestamp();
+
+ /* If this timeout's time has not come yet, do nothing. */
+ if (now < ActiveTimeouts[0]->fin_time)
+ goto out;
+
+ /*
+ * Set the trigger indicator before calling the checker function.
+ * Setting if after may have side effects that lead to detecting
+ * a different event, like "user pressed Ctrl-C" is detected instead
+ * of the statement timeout having triggered.
+ */
+ ActiveTimeouts[0]->indicator = true;
+
+ /* Call the timeout checker. */
+ ActiveTimeouts[0]->timeout_func();
+
+ /*
+ * Remove the current timeout source and reschedule
+ * the next if needed. Short circuit disable_timeout(..., true)
+ * for the timeout source that just triggered.
+ */
+ reschedule = ActiveTimeouts[0]->resched_next;
+ remove_timeout_index(0);
+
+ if (reschedule)
+ schedule_alarm(now);
+ else
+ disable_all_timeouts(true);
+ }
+
+out:
+ errno = save_errno;
+}
diff -durpN postgresql/src/backend/tcop/postgres.c postgresql.1/src/backend/tcop/postgres.c
--- postgresql/src/backend/tcop/postgres.c 2012-06-26 09:10:21.283759446 +0200
+++ postgresql.1/src/backend/tcop/postgres.c 2012-07-04 19:14:55.314126731 +0200
@@ -64,6 +64,7 @@
#include "storage/proc.h"
#include "storage/procsignal.h"
#include "storage/sinval.h"
+#include "storage/timeout.h"
#include "tcop/fastpath.h"
#include "tcop/pquery.h"
#include "tcop/tcopprot.h"
@@ -2396,9 +2397,9 @@ start_xact_command(void)
/* Set statement timeout running, if any */
/* NB: this mustn't be enabled until we are within an xact */
if (StatementTimeout > 0)
- enable_sig_alarm(StatementTimeout, true);
+ enable_timeout(STATEMENT_TIMEOUT, StatementTimeout);
else
- cancel_from_timeout = false;
+ disable_timeout(STATEMENT_TIMEOUT, false);
xact_started = true;
}
@@ -2410,7 +2411,7 @@ finish_xact_command(void)
if (xact_started)
{
/* Cancel any active statement timeout before committing */
- disable_sig_alarm(true);
+ disable_all_timeouts(false);
/* Now commit the command */
ereport(DEBUG3,
@@ -2891,7 +2892,7 @@ ProcessInterrupts(void)
(errcode(ERRCODE_QUERY_CANCELED),
errmsg("canceling authentication due to timeout")));
}
- if (cancel_from_timeout)
+ if (get_timeout_indicator(STATEMENT_TIMEOUT))
{
ImmediateInterruptOK = false; /* not idle anymore */
DisableNotifyInterrupt();
@@ -3600,6 +3601,19 @@ PostgresMain(int argc, char *argv[], con
WalSndSignals();
else
{
+ /*
+ * Register timeout sources needed by backend operation. Note
+ * that InitializeTimeout was already called by BackendInitialize
+ * unless we are in single user mode. But it's not a problem
+ * to call it twice.
+ */
+ InitializeTimeouts();
+ RegisterTimeout(DEADLOCK_TIMEOUT, true,
+ CheckDeadLock, GetCurrentTimestamp);
+ RegisterTimeout(STATEMENT_TIMEOUT, false,
+ StatementTimeoutHandler,
+ GetCurrentStatementStartTimestamp);
+
pqsignal(SIGHUP, SigHupHandler); /* set flag to read config
* file */
pqsignal(SIGINT, StatementCancelHandler); /* cancel current query */
@@ -3802,10 +3816,10 @@ PostgresMain(int argc, char *argv[], con
/*
* Forget any pending QueryCancel request, since we're returning to
- * the idle loop anyway, and cancel the statement timer if running.
+ * the idle loop anyway, and cancel the timer if running.
*/
QueryCancelPending = false;
- disable_sig_alarm(true);
+ disable_all_timeouts(false);
QueryCancelPending = false; /* again in case timeout occurred */
/*
diff -durpN postgresql/src/backend/utils/init/postinit.c postgresql.1/src/backend/utils/init/postinit.c
--- postgresql/src/backend/utils/init/postinit.c 2012-06-26 09:10:21.287759479 +0200
+++ postgresql.1/src/backend/utils/init/postinit.c 2012-07-04 11:01:15.343090631 +0200
@@ -41,12 +41,12 @@
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lmgr.h"
-#include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/proc.h"
#include "storage/sinvaladt.h"
#include "storage/smgr.h"
+#include "storage/timeout.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
#include "utils/fmgroids.h"
@@ -205,8 +205,7 @@ PerformAuthentication(Port *port)
* during authentication. Since we're inside a transaction and might do
* database access, we have to use the statement_timeout infrastructure.
*/
- if (!enable_sig_alarm(AuthenticationTimeout * 1000, true))
- elog(FATAL, "could not set timer for authorization timeout");
+ enable_timeout(STATEMENT_TIMEOUT, AuthenticationTimeout * 1000);
/*
* Now perform authentication exchange.
@@ -216,8 +215,7 @@ PerformAuthentication(Port *port)
/*
* Done with authentication. Disable the timeout, and log if needed.
*/
- if (!disable_sig_alarm(true))
- elog(FATAL, "could not disable timer for authorization timeout");
+ disable_timeout(STATEMENT_TIMEOUT, false);
if (Log_connections)
{
diff -durpN postgresql/src/include/storage/proc.h postgresql.1/src/include/storage/proc.h
--- postgresql/src/include/storage/proc.h 2012-06-26 09:10:21.316759721 +0200
+++ postgresql.1/src/include/storage/proc.h 2012-07-04 11:01:15.344090637 +0200
@@ -222,9 +222,6 @@ extern int DeadlockTimeout;
extern int StatementTimeout;
extern bool log_lock_waits;
-extern volatile bool cancel_from_timeout;
-
-
/*
* Function Prototypes
*/
@@ -252,13 +249,10 @@ extern void LockErrorCleanup(void);
extern void ProcWaitForSignal(void);
extern void ProcSendSignal(int pid);
-extern bool enable_sig_alarm(int delayms, bool is_statement_timeout);
-extern bool disable_sig_alarm(bool is_statement_timeout);
-extern void handle_sig_alarm(SIGNAL_ARGS);
-
-extern bool enable_standby_sig_alarm(TimestampTz now,
- TimestampTz fin_time, bool deadlock_only);
-extern bool disable_standby_sig_alarm(void);
-extern void handle_standby_sig_alarm(SIGNAL_ARGS);
+/* Timeout handlers */
+extern void CheckDeadLock(void);
+extern void StatementTimeoutHandler(void);
+extern void StandbyDeadLockHandler(void);
+extern void StandbyTimeoutHandler(void);
#endif /* PROC_H */
diff -durpN postgresql/src/include/storage/timeout.h postgresql.1/src/include/storage/timeout.h
--- postgresql/src/include/storage/timeout.h 1970-01-01 01:00:00.000000000 +0100
+++ postgresql.1/src/include/storage/timeout.h 2012-07-04 11:01:15.344090637 +0200
@@ -0,0 +1,50 @@
+/*-------------------------------------------------------------------------
+ *
+ * timeout.h
+ * SIGALRM timeout API
+ *
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/storage/timeout.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef TIMEOUT_H
+#define TIMEOUT_H
+
+#include "datatype/timestamp.h"
+
+typedef enum TimeoutName
+{
+ AUTHENTICATION_TIMEOUT,
+ DEADLOCK_TIMEOUT,
+ STATEMENT_TIMEOUT,
+ STANDBY_DEADLOCK_TIMEOUT,
+ STANDBY_TIMEOUT,
+ /* First user timeout source */
+ USER_TIMEOUT,
+ /* Maximum number of timeout sources */
+ TIMEOUT_MAX = 16
+} TimeoutName;
+
+/* timeout setup */
+typedef void (*timeout_func)(void);
+typedef TimestampTz (*timeout_start)(void);
+
+extern void InitializeTimeouts(void);
+extern TimeoutName RegisterTimeout(TimeoutName tn, bool resched_next,
+ timeout_func func, timeout_start start);
+
+/* timeout operation */
+extern void enable_timeout(TimeoutName tn, int delayms);
+extern void disable_timeout(TimeoutName tn, bool keep_indicator);
+extern void disable_all_timeouts(bool keep_indicators);
+
+/* accessors */
+extern bool get_timeout_indicator(TimeoutName tn);
+extern TimestampTz get_timeout_start(TimeoutName tn);
+extern void handle_sig_alarm(SIGNAL_ARGS);
+
+#endif /* TIMEOUT_H */
2-lock_timeout-v15.patchtext/x-patch; name=2-lock_timeout-v15.patchDownload
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-02 19:22:42.141166647 +0200
+++ postgresql.2/doc/src/sgml/config.sgml 2012-07-04 19:22:27.698055231 +0200
@@ -4983,7 +4983,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>
@@ -4994,6 +4997,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-04 19:22:27.699055239 +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-04 19:22:27.701055253 +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/port/posix_sema.c postgresql.2/src/backend/port/posix_sema.c
--- postgresql.1/src/backend/port/posix_sema.c 2012-04-16 19:57:22.438915489 +0200
+++ postgresql.2/src/backend/port/posix_sema.c 2012-07-04 19:58:32.007070683 +0200
@@ -313,3 +313,32 @@ 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,
+ TimeoutCondition condition)
+{
+ int errStatus;
+ bool timeout;
+
+ do
+ {
+ ImmediateInterruptOK = interruptOK;
+ CHECK_FOR_INTERRUPTS();
+ errStatus = sem_wait(PG_SEM_REF(sema));
+ ImmediateInterruptOK = false;
+ timeout = condition();
+ } while (errStatus < 0 && errno == EINTR && !timeout);
+
+ if (timeout)
+ return false;
+ if (errStatus < 0)
+ elog(FATAL, "sem_wait failed: %m");
+ return true;
+}
diff -durpN postgresql.1/src/backend/port/sysv_sema.c postgresql.2/src/backend/port/sysv_sema.c
--- postgresql.1/src/backend/port/sysv_sema.c 2012-05-14 08:20:56.284830580 +0200
+++ postgresql.2/src/backend/port/sysv_sema.c 2012-07-04 19:58:52.096201131 +0200
@@ -492,3 +492,37 @@ 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,
+ TimeoutCondition condition)
+{
+ 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 = condition();
+ } 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.1/src/backend/port/win32_sema.c postgresql.2/src/backend/port/win32_sema.c
--- postgresql.1/src/backend/port/win32_sema.c 2012-06-11 06:22:48.137921483 +0200
+++ postgresql.2/src/backend/port/win32_sema.c 2012-07-04 19:59:03.247273158 +0200
@@ -209,3 +209,66 @@ 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,
+ TimeoutCondition condition)
+{
+ 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 = condition();
+ } 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.1/src/backend/postmaster/postmaster.c postgresql.2/src/backend/postmaster/postmaster.c
--- postgresql.1/src/backend/postmaster/postmaster.c 2012-07-04 19:13:03.431407389 +0200
+++ postgresql.2/src/backend/postmaster/postmaster.c 2012-07-04 19:33:56.079492755 +0200
@@ -3371,7 +3371,7 @@ report_fork_failure_to_client(Port *port
* Do nothing -- just to break out of a sleeping system call.
* The timeout indicator is set by the signal handler.
*/
-static void
+void
DummyTimeoutFunction(void)
{
return;
diff -durpN postgresql.1/src/backend/storage/lmgr/lmgr.c postgresql.2/src/backend/storage/lmgr/lmgr.c
--- postgresql.1/src/backend/storage/lmgr/lmgr.c 2012-04-16 19:57:22.459915733 +0200
+++ postgresql.2/src/backend/storage/lmgr/lmgr.c 2012-07-04 19:22:27.704055270 +0200
@@ -19,8 +19,11 @@
#include "access/transam.h"
#include "access/xact.h"
#include "catalog/catalog.h"
+#include "catalog/pg_database.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
+#include "utils/lsyscache.h"
+#include "storage/proc.h"
#include "storage/procarray.h"
#include "utils/inval.h"
@@ -78,6 +81,21 @@ LockRelationOid(Oid relid, LOCKMODE lock
res = LockAcquire(&tag, lockmode, false, false);
+ if (res == LOCKACQUIRE_NOT_AVAIL)
+ {
+ char *relname = get_rel_name(relid);
+ if (relname)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ relname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation with OID %u",
+ relid)));
+ }
+
/*
* Now that we have the lock, check for invalidation messages, so that we
* will update or flush any stale relcache entry before we try to use it.
@@ -174,6 +192,12 @@ LockRelation(Relation relation, LOCKMODE
res = LockAcquire(&tag, lockmode, false, false);
+ if (res == LOCKACQUIRE_NOT_AVAIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ RelationGetRelationName(relation))));
+
/*
* Now that we have the lock, check for invalidation messages; see notes
* in LockRelationOid.
@@ -251,7 +275,20 @@ LockRelationIdForSession(LockRelId *reli
SET_LOCKTAG_RELATION(tag, relid->dbId, relid->relId);
- (void) LockAcquire(&tag, lockmode, true, false);
+ if (LockAcquire(&tag, lockmode, true, false) == LOCKACQUIRE_NOT_AVAIL)
+ {
+ char *relname = get_rel_name(relid->relId);
+ if (relname)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ relname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation with OID %u",
+ relid->relId)));
+ }
}
/*
@@ -286,7 +323,11 @@ LockRelationForExtension(Relation relati
relation->rd_lockInfo.lockRelId.dbId,
relation->rd_lockInfo.lockRelId.relId);
- (void) LockAcquire(&tag, lockmode, false, false);
+ if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on index \"%s\"",
+ RelationGetRelationName(relation))));
}
/*
@@ -320,7 +361,11 @@ LockPage(Relation relation, BlockNumber
relation->rd_lockInfo.lockRelId.relId,
blkno);
- (void) LockAcquire(&tag, lockmode, false, false);
+ if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on page %u of relation \"%s\"",
+ blkno, RelationGetRelationName(relation))));
}
/*
@@ -376,7 +421,11 @@ LockTuple(Relation relation, ItemPointer
ItemPointerGetBlockNumber(tid),
ItemPointerGetOffsetNumber(tid));
- (void) LockAcquire(&tag, lockmode, false, false);
+ if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on row in relation \"%s\"",
+ RelationGetRelationName(relation))));
}
/*
@@ -430,7 +479,10 @@ XactLockTableInsert(TransactionId xid)
SET_LOCKTAG_TRANSACTION(tag, xid);
- (void) LockAcquire(&tag, ExclusiveLock, false, false);
+ if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on transaction with ID %u", xid)));
}
/*
@@ -474,7 +526,10 @@ XactLockTableWait(TransactionId xid)
SET_LOCKTAG_TRANSACTION(tag, xid);
- (void) LockAcquire(&tag, ShareLock, false, false);
+ if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on transaction with ID %u", xid)));
LockRelease(&tag, ShareLock, false);
@@ -535,7 +590,11 @@ LockDatabaseObject(Oid classid, Oid obji
objid,
objsubid);
- (void) LockAcquire(&tag, lockmode, false, false);
+ if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on class:object: %u:%u",
+ classid, objid)));
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
@@ -576,7 +635,11 @@ LockSharedObject(Oid classid, Oid objid,
objid,
objsubid);
- (void) LockAcquire(&tag, lockmode, false, false);
+ if (LockAcquire(&tag, lockmode, false, false) == LOCKACQUIRE_NOT_AVAIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on class:object: %u:%u",
+ classid, objid)));
/* Make sure syscaches are up-to-date with any changes we waited for */
AcceptInvalidationMessages();
@@ -618,7 +681,22 @@ LockSharedObjectForSession(Oid classid,
objid,
objsubid);
- (void) LockAcquire(&tag, lockmode, true, false);
+ if (LockAcquire(&tag, lockmode, true, false) == LOCKACQUIRE_NOT_AVAIL)
+ switch(classid)
+ {
+ case DatabaseRelationId:
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on database with ID %u",
+ objid)));
+ break;
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on class:object: %u:%u",
+ classid, objid)));
+ break;
+ }
}
/*
diff -durpN postgresql.1/src/backend/storage/lmgr/lock.c postgresql.2/src/backend/storage/lmgr/lock.c
--- postgresql.1/src/backend/storage/lmgr/lock.c 2012-06-26 09:10:21.280759421 +0200
+++ postgresql.2/src/backend/storage/lmgr/lock.c 2012-07-04 19:22:27.707055290 +0200
@@ -344,7 +344,7 @@ static PROCLOCK *SetupLockInTable(LockMe
static void GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner);
static void BeginStrongLockAcquire(LOCALLOCK *locallock, uint32 fasthashcode);
static void FinishStrongLockAcquire(void);
-static void WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner);
+static int WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner);
static void ReleaseLockIfHeld(LOCALLOCK *locallock, bool sessionLock);
static void LockReassignOwner(LOCALLOCK *locallock, ResourceOwner parent);
static bool UnGrantLock(LOCK *lock, LOCKMODE lockmode,
@@ -551,7 +551,7 @@ ProcLockHashCode(const PROCLOCKTAG *proc
* dontWait: if true, don't wait to acquire lock
*
* Returns one of:
- * LOCKACQUIRE_NOT_AVAIL lock not available, and dontWait=true
+ * LOCKACQUIRE_NOT_AVAIL lock not available, either dontWait=true or timeout
* LOCKACQUIRE_OK lock successfully acquired
* LOCKACQUIRE_ALREADY_HELD incremented count for lock already held
*
@@ -865,7 +865,7 @@ LockAcquireExtended(const LOCKTAG *lockt
locktag->locktag_type,
lockmode);
- WaitOnLock(locallock, owner);
+ status = WaitOnLock(locallock, owner);
TRACE_POSTGRESQL_LOCK_WAIT_DONE(locktag->locktag_field1,
locktag->locktag_field2,
@@ -880,28 +880,51 @@ LockAcquireExtended(const LOCKTAG *lockt
* done when the lock was granted to us --- see notes in WaitOnLock.
*/
- /*
- * Check the proclock entry status, in case something in the ipc
- * communication doesn't work correctly.
- */
- if (!(proclock->holdMask & LOCKBIT_ON(lockmode)))
+ switch (status)
{
- AbortStrongLockAcquire();
- PROCLOCK_PRINT("LockAcquire: INCONSISTENT", proclock);
- LOCK_PRINT("LockAcquire: INCONSISTENT", lock, lockmode);
- /* Should we retry ? */
- LWLockRelease(partitionLock);
- elog(ERROR, "LockAcquire failed");
+ case STATUS_OK:
+ /*
+ * Check the proclock entry status, in case something in the ipc
+ * communication doesn't work correctly.
+ */
+ if (!(proclock->holdMask & LOCKBIT_ON(lockmode)))
+ {
+ AbortStrongLockAcquire();
+ PROCLOCK_PRINT("LockAcquire: INCONSISTENT", proclock);
+ LOCK_PRINT("LockAcquire: INCONSISTENT", lock, lockmode);
+ /* Should we retry ? */
+ LWLockRelease(partitionLock);
+ elog(ERROR, "LockAcquire failed");
+ }
+ PROCLOCK_PRINT("LockAcquire: granted", proclock);
+ LOCK_PRINT("LockAcquire: granted", lock, lockmode);
+ break;
+ case STATUS_WAITING:
+ PROCLOCK_PRINT("LockAcquire: timed out", proclock);
+ LOCK_PRINT("LockAcquire: timed out", lock, lockmode);
+ break;
+ default:
+ elog(ERROR, "LockAcquire invalid status");
+ break;
}
- PROCLOCK_PRINT("LockAcquire: granted", proclock);
- LOCK_PRINT("LockAcquire: granted", lock, lockmode);
}
- /*
- * Lock state is fully up-to-date now; if we error out after this, no
- * special error cleanup is required.
- */
- FinishStrongLockAcquire();
+ if (status == STATUS_WAITING)
+ {
+ /*
+ * lock_timeout was set and WaitOnLock() indicated
+ * we timed out. Clean up manually.
+ */
+ AbortStrongLockAcquire();
+ }
+ else
+ {
+ /*
+ * Lock state is fully up-to-date now; if we error out after this, no
+ * special error cleanup is required.
+ */
+ FinishStrongLockAcquire();
+ }
LWLockRelease(partitionLock);
@@ -920,7 +943,7 @@ LockAcquireExtended(const LOCKTAG *lockt
locktag->locktag_field2);
}
- return LOCKACQUIRE_OK;
+ return (status == STATUS_OK ? LOCKACQUIRE_OK : LOCKACQUIRE_NOT_AVAIL);
}
/*
@@ -1450,14 +1473,20 @@ GrantAwaitedLock(void)
* Caller must have set MyProc->heldLocks to reflect locks already held
* on the lockable object by this process.
*
+ * Result: returns value of ProcSleep()
+ * STATUS_OK if we acquired the lock
+ * STATUS_ERROR if not (deadlock)
+ * STATUS_WAITING if not (timeout)
+ *
* The appropriate partition lock must be held at entry.
*/
-static void
+static int
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);
@@ -1499,8 +1528,13 @@ WaitOnLock(LOCALLOCK *locallock, Resourc
*/
PG_TRY();
{
- if (ProcSleep(locallock, lockMethodTable) != STATUS_OK)
+ wait_status = ProcSleep(locallock, lockMethodTable);
+ switch (wait_status)
{
+ case STATUS_OK:
+ case STATUS_WAITING:
+ break;
+ default:
/*
* We failed as a result of a deadlock, see CheckDeadLock(). Quit
* now.
@@ -1545,8 +1579,14 @@ WaitOnLock(LOCALLOCK *locallock, Resourc
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);
+
+ return wait_status;
}
/*
@@ -3910,7 +3950,11 @@ VirtualXactLock(VirtualTransactionId vxi
LWLockRelease(proc->backendLock);
/* Time to wait. */
- (void) LockAcquire(&tag, ShareLock, false, false);
+ if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on virtual transaction with ID %u",
+ vxid.localTransactionId)));
LockRelease(&tag, ShareLock, false);
return true;
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-04 11:01:15.338090598 +0200
+++ postgresql.2/src/backend/storage/lmgr/proc.c 2012-07-04 20:27:00.930150003 +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 */
@@ -638,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 and lock timeout timers,
+ * if they are still running (see ProcSleep)
+ */
disable_timeout(DEADLOCK_TIMEOUT, false);
+ disable_timeout(LOCK_TIMEOUT, false);
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
@@ -865,6 +871,14 @@ ProcQueueInit(PROC_QUEUE *queue)
queue->size = 0;
}
+/*
+ * LockTimeoutIndicator -- passed to PGSemaphoreTimedLock()
+ */
+static bool
+LockTimeoutIndicator(void)
+{
+ return get_timeout_indicator(LOCK_TIMEOUT);
+}
/*
* ProcSleep -- put a process to sleep on the specified lock
@@ -875,7 +889,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_ERROR if not (deadlock)
+ * STATUS_WAITING if not (timeout)
*
* ASSUME: that no one will fiddle with the queue until after
* we release the partition lock.
@@ -897,6 +914,7 @@ ProcSleep(LOCALLOCK *locallock, LockMeth
LOCKMASK myHeldLocks = MyProc->heldLocks;
bool early_deadlock = false;
bool allow_autovacuum_cancel = true;
+ bool timeout_detected = false;
int myWaitStatus;
PGPROC *proc;
int i;
@@ -1037,8 +1055,13 @@ ProcSleep(LOCALLOCK *locallock, LockMeth
enable_timeout(DEADLOCK_TIMEOUT, DeadlockTimeout);
/*
- * If someone wakes us between LWLockRelease and PGSemaphoreLock,
- * PGSemaphoreLock will not block. The wakeup is "saved" by the semaphore
+ * Queue the timer for lock timeout, too.
+ */
+ enable_timeout(LOCK_TIMEOUT, LockTimeout);
+
+ /*
+ * 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).
@@ -1055,7 +1078,11 @@ ProcSleep(LOCALLOCK *locallock, LockMeth
*/
do
{
- PGSemaphoreLock(&MyProc->sem, true);
+ /* Check and keep the lock timeout indicator for later checks */
+ timeout_detected = !PGSemaphoreTimedLock(&MyProc->sem, true,
+ LockTimeoutIndicator);
+ if (timeout_detected)
+ break;
/*
* waitStatus could change from STATUS_WAITING to something else
@@ -1184,9 +1211,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);
+ disable_timeout(LOCK_TIMEOUT, false);
/*
* Re-acquire the lock table's partition lock. We have to do this to hold
@@ -1196,6 +1224,15 @@ ProcSleep(LOCALLOCK *locallock, LockMeth
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;
@@ -1209,8 +1246,10 @@ ProcSleep(LOCALLOCK *locallock, LockMeth
/*
* 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 -durpN postgresql.1/src/backend/tcop/postgres.c postgresql.2/src/backend/tcop/postgres.c
--- postgresql.1/src/backend/tcop/postgres.c 2012-07-04 19:14:55.314126731 +0200
+++ postgresql.2/src/backend/tcop/postgres.c 2012-07-04 19:28:08.972258779 +0200
@@ -3497,6 +3497,29 @@ process_postgres_switches(int argc, char
return dbname;
}
+/*
+ * GetLockTimeoutStart
+ *
+ * Depending on lock_timeout_option, return either the current time
+ * ('per_lock') or the statement start time ('per_statement').
+ */
+static TimestampTz GetLockTimeoutStart(void)
+{
+ switch (LockTimeoutOption)
+ {
+ case LOCK_TIMEOUT_PER_LOCK:
+ return GetCurrentTimestamp();
+ case LOCK_TIMEOUT_PER_STMT:
+ return GetCurrentStatementStartTimestamp();
+ default:
+ elog(ERROR, "unhandled lock_timeout_option value: \"%s\"",
+ GetConfigOptionByName("lock_timeout_option", NULL));
+ break;
+ }
+ /* Silence possible warnings. */
+ return 0;
+}
+
/* ----------------------------------------------------------------
* PostgresMain
@@ -3610,6 +3633,9 @@ PostgresMain(int argc, char *argv[], con
InitializeTimeouts();
RegisterTimeout(DEADLOCK_TIMEOUT, true,
CheckDeadLock, GetCurrentTimestamp);
+ RegisterTimeout(LOCK_TIMEOUT, false,
+ DummyTimeoutFunction,
+ GetLockTimeoutStart);
RegisterTimeout(STATEMENT_TIMEOUT, false,
StatementTimeoutHandler,
GetCurrentStatementStartTimestamp);
diff -durpN postgresql.1/src/backend/utils/adt/lockfuncs.c postgresql.2/src/backend/utils/adt/lockfuncs.c
--- postgresql.1/src/backend/utils/adt/lockfuncs.c 2012-06-11 06:22:48.162921604 +0200
+++ postgresql.2/src/backend/utils/adt/lockfuncs.c 2012-07-04 19:22:27.710055311 +0200
@@ -421,7 +421,11 @@ pg_advisory_lock_int8(PG_FUNCTION_ARGS)
SET_LOCKTAG_INT64(tag, key);
- (void) LockAcquire(&tag, ExclusiveLock, true, false);
+ if (LockAcquire(&tag, ExclusiveLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain exclusive advisory lock on value %llu",
+ (long long)key)));
PG_RETURN_VOID();
}
@@ -438,7 +442,11 @@ pg_advisory_xact_lock_int8(PG_FUNCTION_A
SET_LOCKTAG_INT64(tag, key);
- (void) LockAcquire(&tag, ExclusiveLock, false, false);
+ if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain exclusive advisory lock on value %llu",
+ (long long)key)));
PG_RETURN_VOID();
}
@@ -454,7 +462,11 @@ pg_advisory_lock_shared_int8(PG_FUNCTION
SET_LOCKTAG_INT64(tag, key);
- (void) LockAcquire(&tag, ShareLock, true, false);
+ if (LockAcquire(&tag, ShareLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain shared advisory lock on value %llu",
+ (long long)key)));
PG_RETURN_VOID();
}
@@ -471,7 +483,11 @@ pg_advisory_xact_lock_shared_int8(PG_FUN
SET_LOCKTAG_INT64(tag, key);
- (void) LockAcquire(&tag, ShareLock, false, false);
+ if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain shared advisory lock on value %llu",
+ (long long)key)));
PG_RETURN_VOID();
}
@@ -604,7 +620,11 @@ pg_advisory_lock_int4(PG_FUNCTION_ARGS)
SET_LOCKTAG_INT32(tag, key1, key2);
- (void) LockAcquire(&tag, ExclusiveLock, true, false);
+ if (LockAcquire(&tag, ExclusiveLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain exclusive advisory lock on values %u:%u",
+ key1, key2)));
PG_RETURN_VOID();
}
@@ -622,7 +642,11 @@ pg_advisory_xact_lock_int4(PG_FUNCTION_A
SET_LOCKTAG_INT32(tag, key1, key2);
- (void) LockAcquire(&tag, ExclusiveLock, false, false);
+ if (LockAcquire(&tag, ExclusiveLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain exclusive advisory lock on values %u:%u",
+ key1, key2)));
PG_RETURN_VOID();
}
@@ -639,7 +663,11 @@ pg_advisory_lock_shared_int4(PG_FUNCTION
SET_LOCKTAG_INT32(tag, key1, key2);
- (void) LockAcquire(&tag, ShareLock, true, false);
+ if (LockAcquire(&tag, ShareLock, true, false) == LOCKACQUIRE_NOT_AVAIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain shared advisory lock on values %u:%u",
+ key1, key2)));
PG_RETURN_VOID();
}
@@ -657,7 +685,11 @@ pg_advisory_xact_lock_shared_int4(PG_FUN
SET_LOCKTAG_INT32(tag, key1, key2);
- (void) LockAcquire(&tag, ShareLock, false, false);
+ if (LockAcquire(&tag, ShareLock, false, false) == LOCKACQUIRE_NOT_AVAIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain shared advisory lock on values %u:%u",
+ key1, key2)));
PG_RETURN_VOID();
}
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-04 19:22:27.715055342 +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-04 19:22:27.716055349 +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/postmaster/postmaster.h postgresql.2/src/include/postmaster/postmaster.h
--- postgresql.1/src/include/postmaster/postmaster.h 2012-06-26 09:10:21.315759713 +0200
+++ postgresql.2/src/include/postmaster/postmaster.h 2012-07-04 19:32:47.765054159 +0200
@@ -51,6 +51,8 @@ extern void ClosePostmasterPorts(bool am
extern int MaxLivePostmasterChildren(void);
+extern void DummyTimeoutFunction(void);
+
#ifdef EXEC_BACKEND
extern pid_t postmaster_forkexec(int argc, char *argv[]);
extern void SubPostmasterMain(int argc, char *argv[]) __attribute__((noreturn));
diff -durpN postgresql.1/src/include/storage/pg_sema.h postgresql.2/src/include/storage/pg_sema.h
--- postgresql.1/src/include/storage/pg_sema.h 2012-04-16 19:57:22.672918205 +0200
+++ postgresql.2/src/include/storage/pg_sema.h 2012-07-04 19:38:46.001352598 +0200
@@ -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,11 @@ 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,
+ TimeoutCondition condition);
+
#endif /* PG_SEMA_H */
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-04 11:01:15.344090637 +0200
+++ postgresql.2/src/include/storage/proc.h 2012-07-04 19:22:27.717055355 +0200
@@ -220,8 +220,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/storage/timeout.h postgresql.2/src/include/storage/timeout.h
--- postgresql.1/src/include/storage/timeout.h 2012-07-04 11:01:15.344090637 +0200
+++ postgresql.2/src/include/storage/timeout.h 2012-07-04 19:22:27.718055361 +0200
@@ -20,6 +20,7 @@ typedef enum TimeoutName
{
AUTHENTICATION_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-04 19:22:27.718055361 +0200
@@ -198,6 +198,26 @@ 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: could not obtain lock on relation "pxtest3"
+LINE 1: 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;
+ERROR: could not obtain lock on relation "pxtest3"
+LINE 1: SELECT * FROM pxtest3;
+ ^
+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 +233,13 @@ 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: could not obtain lock on relation "pxtest3"
+LINE 1: SELECT * FROM pxtest3;
+ ^
+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-04 19:22:27.719055368 +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
Boszormenyi Zoltan <zb@cybertec.at> writes:
Attached are the refreshed patches. InitializeTimeouts() can be called
twice and PGSemaphoreTimedLock() returns bool now. This saves
two calls to get_timeout_indicator().
I'm starting to look at this patch now. There are a number of cosmetic
things I don't care for, the biggest one being the placement of
timeout.c under storage/lmgr/. That seems an entirely random place,
since the functionality provided has got nothing to do with storage
let alone locks. I'm inclined to think that utils/misc/ is about
the best option in the existing backend directory hierarchy. Anybody
object to that, or have a better idea?
Another thing that needs some discussion is the handling of
InitializeTimeouts. As designed, I think it's completely unsafe,
the reason being that if a process using timeouts forks off another
one, the child will inherit the parent's timeout reasons and be unable
to reset them. Right now this might not be such a big problem because
the postmaster doesn't need any timeouts, but what if it does in the
future? So I think we should drop the base_timeouts_initialized
"protection", and that means we need a pretty consistent scheme for
where to call InitializeTimeouts. But we already have the same issue
with respect to on_proc_exit callbacks, so we can just add
InitializeTimeouts calls in the same places as on_exit_reset().
Comments?
I'll work up a revised patch and post it.
regards, tom lane
Excerpts from Tom Lane's message of mié jul 11 15:47:47 -0400 2012:
Boszormenyi Zoltan <zb@cybertec.at> writes:
Attached are the refreshed patches. InitializeTimeouts() can be called
twice and PGSemaphoreTimedLock() returns bool now. This saves
two calls to get_timeout_indicator().I'm starting to look at this patch now. There are a number of cosmetic
things I don't care for, the biggest one being the placement of
timeout.c under storage/lmgr/. That seems an entirely random place,
since the functionality provided has got nothing to do with storage
let alone locks. I'm inclined to think that utils/misc/ is about
the best option in the existing backend directory hierarchy. Anybody
object to that, or have a better idea?
I agree with the proposed new location.
Another thing that needs some discussion is the handling of
InitializeTimeouts. As designed, I think it's completely unsafe,
the reason being that if a process using timeouts forks off another
one, the child will inherit the parent's timeout reasons and be unable
to reset them. Right now this might not be such a big problem because
the postmaster doesn't need any timeouts, but what if it does in the
future? So I think we should drop the base_timeouts_initialized
"protection", and that means we need a pretty consistent scheme for
where to call InitializeTimeouts. But we already have the same issue
with respect to on_proc_exit callbacks, so we can just add
InitializeTimeouts calls in the same places as on_exit_reset().
I do agree that InitializeTimeouts is not optimally placed. We
discussed this upthread.
Some of the calls of on_exit_reset() are placed in code that's about to
die. Surely we don't need InitializeTimeouts() then. Maybe we should
have another routine, say InitializeProcess (noting we already
InitProcess so maybe some name would be good), that calls both
on_exit_reset and InitializeTimeouts.
--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support
I wrote:
I'm starting to look at this patch now.
After reading this further, I think that the "sched_next" option is a
bad idea and we should get rid of it. AFAICT, what it is meant to do
is (if !sched_next) automatically do "disable_all_timeouts(true)" if
the particular timeout happens to fire. But there is no reason the
timeout's callback function couldn't do that; and doing it in the
callback is more flexible since you could have logic about whether to do
it or not, rather than freezing the decision at RegisterTimeout time.
Moreover, it does not seem to me to be a particularly good idea to
encourage timeouts to have such behavior, anyway. Each time we add
another timeout we'd have to look to see if it's still sane for each
existing timeout to use !sched_next. It would likely be better, in
most cases, for individual callbacks to explicitly disable any other
individual timeout reasons that should no longer be fired.
I am also underwhelmed by the "timeout_start" callback function concept.
In the first place, that's broken enable_timeout, which incorrectly
assumes that the value it gets must be "now" (see its schedule_alarm
call). In the second place, it seems fairly likely that callers of
get_timeout_start would likewise want the clock time at which the
timeout was enabled, not the timeout_start reference time. (If they
did want the latter, why couldn't they get it from wherever the callback
function had gotten it?) I'm inclined to propose that we drop the
timeout_start concept and instead provide two functions for scheduling
interrupts:
enable_timeout_after(TimeoutName tn, int delay_ms);
enable_timeout_at(TimeoutName tn, TimestampTz fin_time);
where you use the former if you want the standard GetCurrentTimestamp +
n msec calculation, but if you want the stop time calculated in some
other way, you calculate it yourself and use the second function.
regards, tom lane
Alvaro Herrera <alvherre@commandprompt.com> writes:
Excerpts from Tom Lane's message of mié jul 11 15:47:47 -0400 2012:
... that means we need a pretty consistent scheme for
where to call InitializeTimeouts. But we already have the same issue
with respect to on_proc_exit callbacks, so we can just add
InitializeTimeouts calls in the same places as on_exit_reset().
I do agree that InitializeTimeouts is not optimally placed. We
discussed this upthread.
Some of the calls of on_exit_reset() are placed in code that's about to
die. Surely we don't need InitializeTimeouts() then. Maybe we should
have another routine, say InitializeProcess (noting we already
InitProcess so maybe some name would be good), that calls both
on_exit_reset and InitializeTimeouts.
Yeah, I was wondering about that too, but it seems a bit ad-hoc from a
modularity standpoint. I gave some consideration to the idea of putting
these calls directly into fork_process(), but we'd have to be very sure
that there would never be a case where it was incorrect to do them after
forking.
regards, tom lane
Here is a revised version of the timeout-infrastructure patch.
I whacked it around quite a bit, notably:
* I decided that the most convenient way to handle the initialization
issue was to combine establishment of the signal handler with resetting
of the per-process variables. So handle_sig_alarm is no longer global,
and InitializeTimeouts is called at the places where we used to do
"pqsignal(SIGALRM, handle_sig_alarm);". I believe this is correct
because any subprocess that was intending to use SIGALRM must have
called that before establishing any timeouts.
* BTW, doing that exposed the fact that walsender processes were failing
to establish a SIGALRM signal handler, which is a pre-existing bug,
because they run a normal authentication transaction during InitPostgres
and hence need to be able to cope with deadlock and statement timeouts.
I will do something about back-patching a fix for that.
* I ended up putting the RegisterTimeout calls for DEADLOCK_TIMEOUT
and STATEMENT_TIMEOUT into InitPostgres, ensuring that they'd get
done in walsender and autovacuum processes. I'm not totally satisfied
with that, but for sure it didn't work to only establish them in
regular backends.
* I didn't like the "TimeoutName" nomenclature, because to me "name"
suggests that the value is a string, not just an enum. So I renamed
that to TimeoutId.
* I whacked around the logic in timeout.c a fair amount, because it
had race conditions if SIGALRM happened while enabling or disabling
a timeout. I believe the attached coding is safe, but I'm not totally
happy with it from a performance standpoint, because it will do two
setitimer calls (a disable and then a re-enable) in many cases where
the old code did only one.
I think what we ought to do is go ahead and apply this, so that we
can have the API nailed down, and then we can revisit the internals
of timeout.c to see if we can't get the performance back up.
It's clearly a much cleaner design than the old spaghetti logic in
proc.c, so I think we ought to go ahead with this independently of
whether the second patch gets accepted.
I haven't really looked at the second patch yet, but at minimum that
will need some rebasing to match the API tweaks here.
regards, tom lane
2012-07-11 21:47 keltezéssel, Tom Lane írta:
Boszormenyi Zoltan <zb@cybertec.at> writes:
Attached are the refreshed patches. InitializeTimeouts() can be called
twice and PGSemaphoreTimedLock() returns bool now. This saves
two calls to get_timeout_indicator().I'm starting to look at this patch now. There are a number of cosmetic
things I don't care for, the biggest one being the placement of
timeout.c under storage/lmgr/. That seems an entirely random place,
since the functionality provided has got nothing to do with storage
let alone locks. I'm inclined to think that utils/misc/ is about
the best option in the existing backend directory hierarchy. Anybody
object to that, or have a better idea?
Good idea, storage/lmgr/timeout.c was chosen simply because
it was born out of files living there.
Another thing that needs some discussion is the handling of
InitializeTimeouts. As designed, I think it's completely unsafe,
the reason being that if a process using timeouts forks off another
one, the child will inherit the parent's timeout reasons and be unable
to reset them. Right now this might not be such a big problem because
the postmaster doesn't need any timeouts, but what if it does in the
future? So I think we should drop the base_timeouts_initialized
"protection", and that means we need a pretty consistent scheme for
where to call InitializeTimeouts. But we already have the same issue
with respect to on_proc_exit callbacks, so we can just add
InitializeTimeouts calls in the same places as on_exit_reset().Comments?
I'll work up a revised patch and post it.
regards, tom lane
--
----------------------------------
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/
2012-07-11 22:59 keltezéssel, Tom Lane írta:
I wrote:
I'm starting to look at this patch now.
After reading this further, I think that the "sched_next" option is a
bad idea and we should get rid of it. AFAICT, what it is meant to do
is (if !sched_next) automatically do "disable_all_timeouts(true)" if
the particular timeout happens to fire. But there is no reason the
timeout's callback function couldn't do that; and doing it in the
callback is more flexible since you could have logic about whether to do
it or not, rather than freezing the decision at RegisterTimeout time.
Moreover, it does not seem to me to be a particularly good idea to
encourage timeouts to have such behavior, anyway. Each time we add
another timeout we'd have to look to see if it's still sane for each
existing timeout to use !sched_next. It would likely be better, in
most cases, for individual callbacks to explicitly disable any other
individual timeout reasons that should no longer be fired.
+1
I am also underwhelmed by the "timeout_start" callback function concept.
It was generalized out of "static TimestampTz timeout_start_time;"
in proc.c which is valid if deadlock_timeout is activated. It is used
in ProcSleep() for logging the time difference between the time when
the timeout was activated and "now" at several places in that function.
In the first place, that's broken enable_timeout, which incorrectly
assumes that the value it gets must be "now" (see its schedule_alarm
call).
You're right, it must be fixed.
In the second place, it seems fairly likely that callers of
get_timeout_start would likewise want the clock time at which the
timeout was enabled, not the timeout_start reference time. (If they
did want the latter, why couldn't they get it from wherever the callback
function had gotten it?) I'm inclined to propose that we drop the
timeout_start concept and instead provide two functions for scheduling
interrupts:enable_timeout_after(TimeoutName tn, int delay_ms);
enable_timeout_at(TimeoutName tn, TimestampTz fin_time);where you use the former if you want the standard GetCurrentTimestamp +
n msec calculation, but if you want the stop time calculated in some
other way, you calculate it yourself and use the second function.regards, tom lane
--
----------------------------------
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/
2012-07-12 19:05 keltezéssel, Tom Lane írta:
Here is a revised version of the timeout-infrastructure patch.
I whacked it around quite a bit, notably:* I decided that the most convenient way to handle the initialization
issue was to combine establishment of the signal handler with resetting
of the per-process variables. So handle_sig_alarm is no longer global,
and InitializeTimeouts is called at the places where we used to do
"pqsignal(SIGALRM, handle_sig_alarm);". I believe this is correct
because any subprocess that was intending to use SIGALRM must have
called that before establishing any timeouts.
OK.
* BTW, doing that exposed the fact that walsender processes were failing
to establish a SIGALRM signal handler, which is a pre-existing bug,
because they run a normal authentication transaction during InitPostgres
and hence need to be able to cope with deadlock and statement timeouts.
I will do something about back-patching a fix for that.
Wow, my work uncovered a pre-existing bug in PostgreSQL. :-)
* I ended up putting the RegisterTimeout calls for DEADLOCK_TIMEOUT
and STATEMENT_TIMEOUT into InitPostgres, ensuring that they'd get
done in walsender and autovacuum processes. I'm not totally satisfied
with that, but for sure it didn't work to only establish them in
regular backends.* I didn't like the "TimeoutName" nomenclature, because to me "name"
suggests that the value is a string, not just an enum. So I renamed
that to TimeoutId.
OK.
* I whacked around the logic in timeout.c a fair amount, because it
had race conditions if SIGALRM happened while enabling or disabling
a timeout. I believe the attached coding is safe, but I'm not totally
happy with it from a performance standpoint, because it will do two
setitimer calls (a disable and then a re-enable) in many cases where
the old code did only one.
Disabling deadlock timeout, a.k.a. disable_sig_alarm(false) in
the original code called setitimer() twice if statement timeout
was still in effect, it was done in CheckStatementTimeout().
Considering this, I think there is no performance problem with
the new code you came up with.
I think what we ought to do is go ahead and apply this, so that we
can have the API nailed down, and then we can revisit the internals
of timeout.c to see if we can't get the performance back up.
It's clearly a much cleaner design than the old spaghetti logic in
proc.c, so I think we ought to go ahead with this independently of
whether the second patch gets accepted.
There is one tiny bit you might have broken. You wrote previously:
I am also underwhelmed by the "timeout_start" callback function concept.
In the first place, that's broken enable_timeout, which incorrectly
assumes that the value it gets must be "now" (see its schedule_alarm
call). In the second place, it seems fairly likely that callers of
get_timeout_start would likewise want the clock time at which the
timeout was enabled, not the timeout_start reference time. (If they
did want the latter, why couldn't they get it from wherever the callback
function had gotten it?) I'm inclined to propose that we drop the
timeout_start concept and instead provide two functions for scheduling
interrupts:enable_timeout_after(TimeoutName tn, int delay_ms);
enable_timeout_at(TimeoutName tn, TimestampTz fin_time);where you use the former if you want the standard GetCurrentTimestamp +
n msec calculation, but if you want the stop time calculated in some
other way, you calculate it yourself and use the second function.
It's okay, but you haven't really followed it with STATEMENT_TIMEOUT:
-----8<----------8<----------8<----------8<----------8<-----
*** 2396,2404 ****
/* Set statement timeout running, if any */
/* NB: this mustn't be enabled until we are within an xact */
if (StatementTimeout > 0)
! enable_sig_alarm(StatementTimeout, true);
else
! cancel_from_timeout = false;
xact_started = true;
}
--- 2397,2405 ----
/* Set statement timeout running, if any */
/* NB: this mustn't be enabled until we are within an xact */
if (StatementTimeout > 0)
! enable_timeout_after(STATEMENT_TIMEOUT, StatementTimeout);
else
! disable_timeout(STATEMENT_TIMEOUT, false);
xact_started = true;
}
-----8<----------8<----------8<----------8<----------8<-----
It means that StatementTimeout losts its precision. It would trigger
in the future counting from "now" instead of counting from
GetCurrentStatementStartTimestamp(). It should be:
enable_timeout_at(STATEMENT_TIMEOUT,
TimestampTzPlusMilliseconds(GetCurrentStatementStartTimestamp(), StatementTimeout));
I haven't really looked at the second patch yet, but at minimum that
will need some rebasing to match the API tweaks here.
Yes, I will do that.
Thanks for your review and work.
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/
2012-07-13 22:32 keltezéssel, Boszormenyi Zoltan írta:
2012-07-12 19:05 keltezéssel, Tom Lane írta:
I haven't really looked at the second patch yet, but at minimum that
will need some rebasing to match the API tweaks here.Yes, I will do that.
While doing it, I discovered another bug you introduced.
enable_timeout_after(..., 0); would set an alarm instead of ignoring it.
Try SET deadlock_timeout = 0;
Same for enable_timeout_at(..., fin_time): if fin_time points to the past,
it enables a huge timeout that wouldn't possibly trigger for short
transactions but it's a bug nevertheless.
Thanks for your review and work.
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/
Boszormenyi Zoltan <zb@cybertec.at> writes:
It means that StatementTimeout losts its precision. It would trigger
in the future counting from "now" instead of counting from
GetCurrentStatementStartTimestamp().
That is, in fact, better not worse. Note the comment in the existing
code:
* Begin statement-level timeout
*
* Note that we compute statement_fin_time with reference to the
* statement_timestamp, but apply the specified delay without any
* correction; that is, we ignore whatever time has elapsed since
* statement_timestamp was set. In the normal case only a small
* interval will have elapsed and so this doesn't matter, but there
* are corner cases (involving multi-statement query strings with
* embedded COMMIT or ROLLBACK) where we might re-initialize the
* statement timeout long after initial receipt of the message. In
* such cases the enforcement of the statement timeout will be a bit
* inconsistent. This annoyance is judged not worth the cost of
* performing an additional gettimeofday() here.
That is, measuring from GetCurrentStatementStartTimestamp is a hack to
save one gettimeofday call, at the cost of making the timeout less
accurate, sometimes significantly so. In the new model there isn't any
good way to duplicate this kluge (in particular, there's no point in
using enable_timeout_at, because that will still make a gettimeofday
call). That doesn't bother me too much. I'd rather try to buy back
whatever performance was lost by seeing if we can reduce the number of
setitimer calls.
regards, tom lane
Boszormenyi Zoltan <zb@cybertec.at> writes:
While doing it, I discovered another bug you introduced.
enable_timeout_after(..., 0); would set an alarm instead of ignoring it.
Try SET deadlock_timeout = 0;
Hm. I don't think it's a bug for enable_timeout_after(..., 0) to cause
a timeout ... but we'll have to change the calling code. Thanks for
catching that.
Same for enable_timeout_at(..., fin_time): if fin_time points to the past,
it enables a huge timeout
No, it should cause an immediate interrupt, or at least after 1
microsecond. Look at TimestampDifference.
regards, tom lane
2012-07-13 23:51 keltezéssel, Tom Lane írta:
Boszormenyi Zoltan <zb@cybertec.at> writes:
While doing it, I discovered another bug you introduced.
enable_timeout_after(..., 0); would set an alarm instead of ignoring it.
Try SET deadlock_timeout = 0;Hm. I don't think it's a bug for enable_timeout_after(..., 0) to cause
a timeout ... but we'll have to change the calling code. Thanks for
catching that.
You're welcome. This caused a segfault in my second patch,
the code didn't expect enable_timeout_after(..., 0);
to set up a timer.
So, the calling code should check for the value and not call
enable_timeout_*() when it shouldn't, right? It's making the code
more obvious for the casual reader, I agree it's better that way.
Will you post a new version with callers checking their *Timeout settings
or commit it with this change? I can then post a new second patch.
Regarding the lock_timeout functionality: the patch can be reduced to
about half of its current size and it would be a lot less intrusive if the
LockAcquire() callers don't need to report the individual object types
and names or OIDs. Do you prefer the verbose ereport()s or a
generic one about "lock timeout triggered" in ProcSleep()?
Same for enable_timeout_at(..., fin_time): if fin_time points to the past,
it enables a huge timeoutNo, it should cause an immediate interrupt, or at least after 1
microsecond. Look at TimestampDifference.
Okay.
--
----------------------------------
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/
Excerpts from Boszormenyi Zoltan's message of vie jul 13 18:11:27 -0400 2012:
Regarding the lock_timeout functionality: the patch can be reduced to
about half of its current size and it would be a lot less intrusive if the
LockAcquire() callers don't need to report the individual object types
and names or OIDs. Do you prefer the verbose ereport()s or a
generic one about "lock timeout triggered" in ProcSleep()?
For what it's worth, I would appreciate it if you would post the lock
timeout patch for the upcoming commitfest. This one is already almost a
month long now. That way we can close this CF item soon and concentrate
on the remaining patches. This one has received its fair share of
committer attention already, ISTM.
--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support
Boszormenyi Zoltan <zb@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. The places that are unconditionally doing
"enable_timeout_after(..., DeadlockTimeout);" are perfectly fine. The
only place that might need an if-test has already got one:
if (StatementTimeout > 0)
! enable_timeout_after(STATEMENT_TIMEOUT, StatementTimeout);
else
! disable_timeout(STATEMENT_TIMEOUT, false);
regards, tom lane
Alvaro Herrera <alvherre@commandprompt.com> writes:
For what it's worth, I would appreciate it if you would post the lock
timeout patch for the upcoming commitfest.
+1. I think it's reasonable to get the infrastructure patch in now,
but we are running out of time in this commitfest (and there's still
a lot of patches that haven't been reviewed at all).
regards, tom lane
2012-07-14 00:36 keltezéssel, Tom Lane írta:
Alvaro Herrera <alvherre@commandprompt.com> writes:
For what it's worth, I would appreciate it if you would post the lock
timeout patch for the upcoming commitfest.+1. I think it's reasonable to get the infrastructure patch in now,
but we are running out of time in this commitfest (and there's still
a lot of patches that haven't been reviewed at all).regards, tom lane
OK, I will do that.
Thanks for the review.
--
----------------------------------
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/
Excerpts from Tom Lane's message of vie jul 13 18:23:31 -0400 2012:
Boszormenyi Zoltan <zb@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.
--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support
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 <zb@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/
Attachments:
1-timeout-framework-v19.patchtext/x-patch; name=1-timeout-framework-v19.patchDownload
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 */
2-lock_timeout-v19.patchtext/x-patch; name=2-lock_timeout-v19.patchDownload
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
Hi,
new version with a lot more cleanup is attached.
2012-07-22 22:03 keltezéssel, Boszormenyi Zoltan írta:
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.
This "mini-framework" is now called "lock manager timeouts" and
both deadlock timeout and the new lock timeout belong to it.
The little piece of standalone code managing these are in
storage/lmgr/lmgrtimeout.c.
There is no PGSemaphoreTimedLock() any more. Instead,
PGSemaphoreLock() gained a new function argument for
checking timeouts. This has three advantages:
- There is only one PGSemaphoreLock() implementation and bug fixes
like ada8fa08fc6cf5f199b6df935b4d0a730aaa4fec don't need to
touch several places.
- There is no layering violation between pg_sema.c and proc.c.
- The extra function can check other type of conditions from different
callers, should the need arise.
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/
Attachments:
1-timeout-framework-v21.patchtext/x-patch; name=1-timeout-framework-v21.patchDownload
diff -durpN postgresql/src/backend/port/ipc_test.c postgresql.1/src/backend/port/ipc_test.c
--- postgresql/src/backend/port/ipc_test.c 2012-04-16 19:57:22.437915477 +0200
+++ postgresql.1/src/backend/port/ipc_test.c 2012-08-21 15:53:50.059329927 +0200
@@ -240,7 +240,7 @@ main(int argc, char **argv)
printf("Testing Lock ... ");
fflush(stdout);
- PGSemaphoreLock(&storage->sem, false);
+ PGSemaphoreLock(&storage->sem, false, NULL);
printf("OK\n");
@@ -262,8 +262,8 @@ main(int argc, char **argv)
PGSemaphoreUnlock(&storage->sem);
PGSemaphoreUnlock(&storage->sem);
- PGSemaphoreLock(&storage->sem, false);
- PGSemaphoreLock(&storage->sem, false);
+ PGSemaphoreLock(&storage->sem, false, NULL);
+ PGSemaphoreLock(&storage->sem, false, NULL);
printf("OK\n");
@@ -311,7 +311,7 @@ main(int argc, char **argv)
printf("Waiting for child (should wait 3 sec here) ... ");
fflush(stdout);
- PGSemaphoreLock(&storage->sem, false);
+ PGSemaphoreLock(&storage->sem, false, NULL);
printf("OK\n");
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-08-21 15:49:26.215579665 +0200
@@ -236,9 +236,11 @@ PGSemaphoreReset(PGSemaphore sema)
* Lock a semaphore (decrement count), blocking if count would be < 0
*/
void
-PGSemaphoreLock(PGSemaphore sema, bool interruptOK)
+PGSemaphoreLock(PGSemaphore sema, bool interruptOK,
+ PGSemaphoreCondition condition_checker)
{
int errStatus;
+ bool condition = false;
/*
* See notes in sysv_sema.c's implementation of PGSemaphoreLock. Just as
@@ -252,8 +254,12 @@ PGSemaphoreLock(PGSemaphore sema, bool i
CHECK_FOR_INTERRUPTS();
errStatus = sem_wait(PG_SEM_REF(sema));
ImmediateInterruptOK = false;
- } while (errStatus < 0 && errno == EINTR);
+ if (condition_checker)
+ condition = condition_checker();
+ } while (errStatus < 0 && errno == EINTR && !condition);
+ if (condition)
+ return;
if (errStatus < 0)
elog(FATAL, "sem_wait failed: %m");
}
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-08-21 15:49:26.991584804 +0200
@@ -358,9 +358,11 @@ PGSemaphoreReset(PGSemaphore sema)
* Lock a semaphore (decrement count), blocking if count would be < 0
*/
void
-PGSemaphoreLock(PGSemaphore sema, bool interruptOK)
+PGSemaphoreLock(PGSemaphore sema, bool interruptOK,
+ PGSemaphoreCondition condition_checker)
{
int errStatus;
+ bool condition = false;
struct sembuf sops;
sops.sem_op = -1; /* decrement */
@@ -414,8 +416,12 @@ PGSemaphoreLock(PGSemaphore sema, bool i
CHECK_FOR_INTERRUPTS();
errStatus = semop(sema->semId, &sops, 1);
ImmediateInterruptOK = false;
- } while (errStatus < 0 && errno == EINTR);
+ if (condition_checker)
+ condition = condition_checker();
+ } while (errStatus < 0 && errno == EINTR && !condition);
+ if (condition)
+ return;
if (errStatus < 0)
elog(FATAL, "semop(id=%d) failed: %m", sema->semId);
}
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-08-21 15:49:24.921571070 +0200
@@ -116,10 +116,12 @@ PGSemaphoreReset(PGSemaphore sema)
* Serve the interrupt if interruptOK is true.
*/
void
-PGSemaphoreLock(PGSemaphore sema, bool interruptOK)
+PGSemaphoreLock(PGSemaphore sema, bool interruptOK,
+ PGSemaphoreCondition condition_checker)
{
DWORD ret;
HANDLE wh[2];
+ bool condition = false;
/*
* Note: pgwin32_signal_event should be first to ensure that it will be
@@ -158,8 +160,12 @@ PGSemaphoreLock(PGSemaphore sema, bool i
errno = EIDRM;
ImmediateInterruptOK = false;
- } while (errno == EINTR);
+ if (condition_checker)
+ condition = condition_checker();
+ } while (errno == EINTR && !condition);
+ if (condition)
+ return;
if (errno != 0)
ereport(FATAL,
(errmsg("could not lock semaphore: error code %lu", GetLastError())));
diff -durpN postgresql/src/backend/storage/lmgr/deadlock.c postgresql.1/src/backend/storage/lmgr/deadlock.c
--- postgresql/src/backend/storage/lmgr/deadlock.c 2012-07-27 10:19:36.932331508 +0200
+++ postgresql.1/src/backend/storage/lmgr/deadlock.c 2012-08-21 15:08:16.728428766 +0200
@@ -31,6 +31,7 @@
#include "storage/lmgr.h"
#include "storage/proc.h"
#include "utils/memutils.h"
+#include "utils/timeout.h"
/* One edge in the waits-for graph */
@@ -897,6 +898,9 @@ DeadLockReport(void)
StringInfoData locktagbuf;
int i;
+ if (!DeadLockTimeoutCondition())
+ return;
+
initStringInfo(&clientbuf);
initStringInfo(&logbuf);
initStringInfo(&locktagbuf);
diff -durpN postgresql/src/backend/storage/lmgr/lmgrtimeout.c postgresql.1/src/backend/storage/lmgr/lmgrtimeout.c
--- postgresql/src/backend/storage/lmgr/lmgrtimeout.c 1970-01-01 01:00:00.000000000 +0100
+++ postgresql.1/src/backend/storage/lmgr/lmgrtimeout.c 2012-08-21 13:05:19.214992078 +0200
@@ -0,0 +1,121 @@
+/*-------------------------------------------------------------------------
+ *
+ * lmgrtimeout.c
+ * routines to manage lock manager timeout sources
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/storage/lmgr/lmgrtimeout.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "storage/lmgrtimeout.h"
+
+/*
+ * Linked set of lock manager timeout functions
+ */
+static LmgrTimeoutFunctions *CurrentLmgrTimeoutFunctions = NULL;
+
+/*
+ * Register a set of functions for an lock manager timeout
+ */
+void
+RegisterLmgrTimeout(LmgrTimeoutFunctions *functions)
+{
+ Assert(functions != NULL);
+ Assert(functions->enable != NULL);
+ Assert(functions->disable != NULL);
+ Assert(functions->condition != NULL);
+ Assert(functions->reporter != NULL);
+
+ functions->next = CurrentLmgrTimeoutFunctions;
+ CurrentLmgrTimeoutFunctions = functions;
+}
+
+/*
+ * Enable registered lock manager timeouts
+ */
+void
+EnableLmgrTimeouts(void)
+{
+ LmgrTimeoutFunctions *ptr = CurrentLmgrTimeoutFunctions;
+
+ while (ptr)
+ {
+ ptr->enable();
+ ptr = ptr->next;
+ }
+}
+
+/*
+ * Disable registered lock manager timeouts
+ */
+void
+DisableLmgrTimeouts(void)
+{
+ LmgrTimeoutFunctions *ptr = CurrentLmgrTimeoutFunctions;
+
+ while (ptr)
+ {
+ ptr->disable();
+ ptr = ptr->next;
+ }
+}
+
+/*
+ * Call the timeout indicator functions in the order of registration
+ */
+static bool
+DoLmgrTimeoutCondition(LmgrTimeoutFunctions *ptr)
+{
+ if (ptr)
+ {
+ if (ptr->next)
+ if (DoLmgrTimeoutCondition(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
+LmgrTimeoutCondition(void)
+{
+ return DoLmgrTimeoutCondition(CurrentLmgrTimeoutFunctions);
+}
+
+/*
+ * Call the timeout error reporter functions in the order of registration
+ */
+static void
+DoReportLmgrTimeoutError(LmgrTimeoutFunctions *ptr)
+{
+ if (ptr)
+ {
+ if (ptr->next)
+ DoReportLmgrTimeoutError(ptr->next);
+
+ /* This may not return because of calling ereport() */
+ ptr->reporter();
+ }
+}
+
+/*
+ * Call the timeout error reporter if set
+ */
+void
+ReportLmgrTimeoutError(void)
+{
+ DoReportLmgrTimeoutError(CurrentLmgrTimeoutFunctions);
+}
diff -durpN postgresql/src/backend/storage/lmgr/lock.c postgresql.1/src/backend/storage/lmgr/lock.c
--- postgresql/src/backend/storage/lmgr/lock.c 2012-06-26 09:10:21.280759421 +0200
+++ postgresql.1/src/backend/storage/lmgr/lock.c 2012-08-21 16:01:33.095488150 +0200
@@ -38,6 +38,7 @@
#include "miscadmin.h"
#include "pg_trace.h"
#include "pgstat.h"
+#include "storage/lmgrtimeout.h"
#include "storage/proc.h"
#include "storage/sinvaladt.h"
#include "storage/spin.h"
@@ -1512,9 +1513,9 @@ WaitOnLock(LOCALLOCK *locallock, Resourc
/*
* Now that we aren't holding the partition lock, we can give an
- * error report including details about the detected deadlock.
+ * error report on the timeout condition that triggered.
*/
- DeadLockReport();
+ ReportLmgrTimeoutError();
/* not reached */
}
}
diff -durpN postgresql/src/backend/storage/lmgr/lwlock.c postgresql.1/src/backend/storage/lmgr/lwlock.c
--- postgresql/src/backend/storage/lmgr/lwlock.c 2012-06-27 07:41:33.603316276 +0200
+++ postgresql.1/src/backend/storage/lmgr/lwlock.c 2012-08-21 15:54:39.410667067 +0200
@@ -477,7 +477,7 @@ LWLockAcquire(LWLockId lockid, LWLockMod
for (;;)
{
/* "false" means cannot accept cancel/die interrupt here. */
- PGSemaphoreLock(&proc->sem, false);
+ PGSemaphoreLock(&proc->sem, false, NULL);
if (!proc->lwWaiting)
break;
extraWaits++;
@@ -682,7 +682,7 @@ LWLockAcquireOrWait(LWLockId lockid, LWL
for (;;)
{
/* "false" means cannot accept cancel/die interrupt here. */
- PGSemaphoreLock(&proc->sem, false);
+ PGSemaphoreLock(&proc->sem, false, NULL);
if (!proc->lwWaiting)
break;
extraWaits++;
diff -durpN postgresql/src/backend/storage/lmgr/Makefile postgresql.1/src/backend/storage/lmgr/Makefile
--- postgresql/src/backend/storage/lmgr/Makefile 2012-04-16 19:57:22.458915722 +0200
+++ postgresql.1/src/backend/storage/lmgr/Makefile 2012-08-21 13:05:00.254861812 +0200
@@ -12,7 +12,7 @@ subdir = src/backend/storage/lmgr
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
-OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o predicate.o
+OBJS = lmgr.o lmgrtimeout.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o predicate.o
include $(top_srcdir)/src/backend/common.mk
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-27 10:19:36.933331515 +0200
+++ postgresql.1/src/backend/storage/lmgr/proc.c 2012-08-21 15:56:36.948469452 +0200
@@ -43,6 +43,7 @@
#include "replication/syncrep.h"
#include "storage/ipc.h"
#include "storage/lmgr.h"
+#include "storage/lmgrtimeout.h"
#include "storage/pmsignal.h"
#include "storage/proc.h"
#include "storage/procarray.h"
@@ -640,8 +641,11 @@ 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 lock manager timers if they are still running
+ * (see ProcSleep)
+ */
+ DisableLmgrTimeouts();
/* Unlink myself from the wait queue, if on it (might not be anymore!) */
partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
@@ -1028,15 +1032,9 @@ ProcSleep(LOCALLOCK *locallock, LockMeth
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.
- *
- * By delaying the check until we've waited for a bit, we can avoid
- * running the rather expensive deadlock-check code in most cases.
+ * Set the timer for registered lock manager timeouts
*/
- enable_timeout_after(DEADLOCK_TIMEOUT, DeadlockTimeout);
+ EnableLmgrTimeouts();
/*
* If someone wakes us between LWLockRelease and PGSemaphoreLock,
@@ -1057,7 +1055,7 @@ ProcSleep(LOCALLOCK *locallock, LockMeth
*/
do
{
- PGSemaphoreLock(&MyProc->sem, true);
+ PGSemaphoreLock(&MyProc->sem, true, LmgrTimeoutCondition);
/*
* waitStatus could change from STATUS_WAITING to something else
@@ -1203,9 +1201,9 @@ 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);
+ DisableLmgrTimeouts();
/*
* Re-acquire the lock table's partition lock. We have to do this to hold
@@ -1451,6 +1449,19 @@ check_done:
/*
+ * DeadLockTimeoutCondition
+ * Indicate whether deadlock was detected
+ */
+bool
+DeadLockTimeoutCondition(void)
+{
+ if (deadlock_state == DS_NOT_YET_CHECKED)
+ return false;
+ return get_timeout_indicator(DEADLOCK_TIMEOUT) && (deadlock_state != DS_NO_DEADLOCK);
+}
+
+
+/*
* ProcWaitForSignal - wait for a signal from another backend.
*
* This can share the semaphore normally used for waiting for locks,
@@ -1464,7 +1475,7 @@ check_done:
void
ProcWaitForSignal(void)
{
- PGSemaphoreLock(&MyProc->sem, true);
+ PGSemaphoreLock(&MyProc->sem, true, NULL);
}
/*
diff -durpN postgresql/src/backend/utils/init/postinit.c postgresql.1/src/backend/utils/init/postinit.c
--- postgresql/src/backend/utils/init/postinit.c 2012-07-22 16:48:48.525857751 +0200
+++ postgresql.1/src/backend/utils/init/postinit.c 2012-08-21 16:14:31.026731931 +0200
@@ -41,6 +41,7 @@
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lmgr.h"
+#include "storage/lmgrtimeout.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/proc.h"
@@ -65,11 +66,17 @@ static void PerformAuthentication(Port *
static void CheckMyDatabase(const char *name, bool am_superuser);
static void InitCommunication(void);
static void ShutdownPostgres(int code, Datum arg);
+static void EnableDeadLockTimeout(void);
+static void DisableDeadLockTimeout(void);
static void StatementTimeoutHandler(void);
static bool ThereIsAtLeastOneRole(void);
static void process_startup_options(Port *port, bool am_superuser);
static void process_settings(Oid databaseid, Oid roleid);
+static LmgrTimeoutFunctions DeadLockTimeoutFunctions = {
+ EnableDeadLockTimeout, DisableDeadLockTimeout,
+ DeadLockTimeoutCondition, DeadLockReport
+};
/*** InitPostgres support ***/
@@ -501,6 +508,7 @@ InitPostgres(const char *in_dbname, Oid
if (!bootstrap)
{
RegisterTimeout(DEADLOCK_TIMEOUT, CheckDeadLock);
+ RegisterLmgrTimeout(&DeadLockTimeoutFunctions);
RegisterTimeout(STATEMENT_TIMEOUT, StatementTimeoutHandler);
}
@@ -982,6 +990,26 @@ ShutdownPostgres(int code, Datum arg)
LockReleaseAll(USER_LOCKMETHOD, true);
}
+static void
+EnableDeadLockTimeout(void)
+{
+ /*
+ * 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.
+ *
+ * By delaying the check until we've waited for a bit, we can avoid
+ * running the rather expensive deadlock-check code in most cases.
+ */
+ enable_timeout_after(DEADLOCK_TIMEOUT, DeadlockTimeout);
+}
+
+static void
+DisableDeadLockTimeout(void)
+{
+ disable_timeout(DEADLOCK_TIMEOUT, true);
+}
/*
* STATEMENT_TIMEOUT handler: trigger a query-cancel interrupt.
diff -durpN postgresql/src/include/storage/lmgrtimeout.h postgresql.1/src/include/storage/lmgrtimeout.h
--- postgresql/src/include/storage/lmgrtimeout.h 1970-01-01 01:00:00.000000000 +0100
+++ postgresql.1/src/include/storage/lmgrtimeout.h 2012-08-21 13:30:43.050291053 +0200
@@ -0,0 +1,36 @@
+/*-------------------------------------------------------------------------
+ *
+ * lmgrtimeout.h
+ * POSTGRES lock manager timeout definitions.
+ *
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/storage/lmgrtimeout.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef LMGRTIMEOUT_H
+#define LMGRTIMEOUT_H
+
+/* callback function signatures for lock manager timeouts */
+typedef void (*LmgrTimeoutCallback)(void);
+typedef bool (*LmgrTimeoutConditionCallback)(void);
+
+typedef struct LmgrTimeoutFunctions {
+ LmgrTimeoutCallback enable;
+ LmgrTimeoutCallback disable;
+ LmgrTimeoutConditionCallback condition;
+ LmgrTimeoutCallback reporter;
+ struct LmgrTimeoutFunctions *next;
+} LmgrTimeoutFunctions;
+
+/* register and handle lock manager timeouts */
+extern void RegisterLmgrTimeout(LmgrTimeoutFunctions *functions);
+extern void EnableLmgrTimeouts(void);
+extern void DisableLmgrTimeouts(void);
+extern bool LmgrTimeoutCondition(void);
+extern void ReportLmgrTimeoutError(void);
+
+#endif /* LMGRTIMEOUT_H */
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-08-21 15:44:06.655459333 +0200
@@ -62,6 +62,8 @@ typedef HANDLE PGSemaphoreData;
typedef PGSemaphoreData *PGSemaphore;
+typedef bool (*PGSemaphoreCondition)(void);
+
/* Module initialization (called during postmaster start or shmem reinit) */
extern void PGReserveSemaphores(int maxSemas, int port);
@@ -72,7 +74,8 @@ extern void PGSemaphoreCreate(PGSemaphor
extern void PGSemaphoreReset(PGSemaphore sema);
/* Lock a semaphore (decrement count), blocking if count would be < 0 */
-extern void PGSemaphoreLock(PGSemaphore sema, bool interruptOK);
+extern void PGSemaphoreLock(PGSemaphore sema, bool interruptOK,
+ PGSemaphoreCondition condition);
/* Unlock a semaphore (increment count) */
extern void PGSemaphoreUnlock(PGSemaphore sema);
diff -durpN postgresql/src/include/storage/proc.h postgresql.1/src/include/storage/proc.h
--- postgresql/src/include/storage/proc.h 2012-07-22 16:48:48.548857900 +0200
+++ postgresql.1/src/include/storage/proc.h 2012-08-21 15:10:38.641347523 +0200
@@ -244,6 +244,7 @@ extern int ProcSleep(LOCALLOCK *localloc
extern PGPROC *ProcWakeup(PGPROC *proc, int waitStatus);
extern void ProcLockWakeup(LockMethod lockMethodTable, LOCK *lock);
extern void CheckDeadLock(void);
+extern bool DeadLockTimeoutCondition(void);
extern bool IsWaitingForLock(void);
extern void LockErrorCleanup(void);
2-lock_timeout-v21.patchtext/x-patch; name=2-lock_timeout-v21.patchDownload
diff -durpN postgresql.1/doc/src/sgml/config.sgml postgresql.2/doc/src/sgml/config.sgml
--- postgresql.1/doc/src/sgml/config.sgml 2012-08-12 19:05:58.684948018 +0200
+++ postgresql.2/doc/src/sgml/config.sgml 2012-08-21 14:13:19.818572888 +0200
@@ -4965,7 +4965,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>
@@ -4976,6 +4979,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-08-21 14:13:19.819572894 +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-08-08 06:26:48.832119566 +0200
+++ postgresql.2/doc/src/sgml/ref/select.sgml 2012-08-21 14:13:19.821572908 +0200
@@ -1241,6 +1241,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-08-21 15:56:36.948469452 +0200
+++ postgresql.2/src/backend/storage/lmgr/proc.c 2012-08-21 16:08:02.153133960 +0200
@@ -56,6 +56,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-08-21 16:14:31.026731931 +0200
+++ postgresql.2/src/backend/utils/init/postinit.c 2012-08-21 16:14:05.715565301 +0200
@@ -57,6 +57,7 @@
#include "utils/snapmgr.h"
#include "utils/syscache.h"
#include "utils/timeout.h"
+#include "utils/timestamp.h"
#include "utils/tqual.h"
@@ -68,6 +69,11 @@ static void InitCommunication(void);
static void ShutdownPostgres(int code, Datum arg);
static void EnableDeadLockTimeout(void);
static void DisableDeadLockTimeout(void);
+static void LockTimeoutHandler(void);
+static void EnableLockTimeout(void);
+static void DisableLockTimeout(void);
+static bool LockTimeoutCondition(void);
+static void LockTimeoutError(void);
static void StatementTimeoutHandler(void);
static bool ThereIsAtLeastOneRole(void);
static void process_startup_options(Port *port, bool am_superuser);
@@ -78,6 +84,11 @@ static LmgrTimeoutFunctions DeadLockTime
DeadLockTimeoutCondition, DeadLockReport
};
+static LmgrTimeoutFunctions LockTimeoutFunctions = {
+ EnableLockTimeout, DisableLockTimeout,
+ LockTimeoutCondition, LockTimeoutError
+};
+
/*** InitPostgres support ***/
@@ -509,6 +520,8 @@ InitPostgres(const char *in_dbname, Oid
{
RegisterTimeout(DEADLOCK_TIMEOUT, CheckDeadLock);
RegisterLmgrTimeout(&DeadLockTimeoutFunctions);
+ RegisterTimeout(LOCK_TIMEOUT, LockTimeoutHandler);
+ RegisterLmgrTimeout(&LockTimeoutFunctions);
RegisterTimeout(STATEMENT_TIMEOUT, StatementTimeoutHandler);
}
@@ -1012,6 +1025,84 @@ DisableDeadLockTimeout(void)
}
/*
+ * 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.
+ *
+ * Also, we must remove ourselves from the SHM wait queue just like
+ * DeadLockCheck() does so ProcSleep() can return and the error can
+ * be reported.
+ */
+static void
+LockTimeoutHandler(void)
+{
+ Assert(MyProc->waitLock != NULL);
+
+ RemoveFromWaitQueue(MyProc, LockTagHashCode(&(MyProc->waitLock->tag)));
+
+ 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, true);
+}
+
+/*
+ * 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.
*/
static void
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-08-16 10:42:50.226582077 +0200
+++ postgresql.2/src/backend/utils/misc/guc.c 2012-08-21 14:13:19.824572927 +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
@@ -3146,6 +3168,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-08-16 10:42:50.227582083 +0200
+++ postgresql.2/src/backend/utils/misc/postgresql.conf.sample 2012-08-21 14:13:19.825572933 +0200
@@ -529,6 +529,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-08-21 15:10:38.641347523 +0200
+++ postgresql.2/src/include/storage/proc.h 2012-08-21 15:00:48.765532912 +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 16:48:48.549857907 +0200
+++ postgresql.2/src/include/utils/timeout.h 2012-08-21 14:13:19.825572933 +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-08-21 14:13:19.825572933 +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-08-21 14:13:19.826572940 +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
Boszormenyi Zoltan <zb@cybertec.at> writes:
new version with a lot more cleanup is attached.
I looked at this patch, and frankly I'm rather dismayed. It's a mess.
To start at the bottom level, the changes to PGSemaphoreLock broke it,
and seem probably unnecessary anyway. As coded, calling the "condition
checker" breaks handling of error returns from semop(), unless the checker
is careful to preserve errno, which LmgrTimeoutCondition isn't (and really
shouldn't need to be anyway). More, if the checker does return true,
it causes PGSemaphoreLock to utterly violate its contract: it returns to
the caller without having acquired the semaphore, and without even telling
the caller so. Worse, if we *did* acquire the semaphore, we might still
exit via this path, since the placement of the condition check call
ignores the comment a few lines up:
* Once we acquire the lock, we do NOT check for an interrupt before
* returning. The caller needs to be able to record ownership of the lock
* before any interrupt can be accepted.
We could possibly fix all this with a redesigned API contract for
PGSemaphoreLock, but frankly I do not see a good reason to be tinkering
with it at all. We never needed to get it involved with deadlock check
handling, and I don't see why that needs to change for lock timeouts.
One very good reason why monkeying with PGSemaphoreLock is wrong is that
on some platforms a SIGALRM interrupt won't interrupt the semop() call,
and thus control would never reach the "checker" anyway. If we're going
to throw an error, it must be thrown from the interrupt handler.
The whole lmgrtimeout module seems to me to be far more mechanism than is
warranted, for too little added functionality. In the first place, there
is nothing on the horizon suggesting that we need to let any plug-in code
get control here, and if anything the delicacy of what's going on leads me
to not wish to expose such a possibility. In the second place, it isn't
adding any actually useful functionality, it's just agglomerating some
checks. The minimum thing I would want it to do is avoid calling
timeout.c multiple times, which is what would happen right now (leading
to four extra syscalls per lock acquisition, which is enough new overhead
to constitute a strong objection to committing this patch at all).
On the whole I think we could forget lmgrtimeout and just hardwire the
lock timeout and deadlock check cases. But in any case we're going to
need support in timeout.c for enabling/disabling multiple timeouts at
once without extra setitimer calls.
I'm also not thrilled about the way in which the existing deadlock
checking code has been hacked up. As an example, you added this to
DeadLockReport():
+ if (!DeadLockTimeoutCondition())
+ return;
which again causes it to violate its contract, namely to report a
deadlock, in the most fundamental way -- existing callers aren't
expecting it to return *at all*. Surely we can decouple the deadlock
and lock timeout cases better than that; or at least if we can't it's
a delusion to propose anything like lmgrtimeout in the first place.
There's considerable lack of attention to updating comments, too.
For instance in WaitOnLock you only bothered to update the comment
immediately adjacent to the changed code, and not the two comment
blocks above that, which both have specific references to deadlocks
being the reason for failure.
Also, the "per statement" mode for lock timeout doesn't seem to be
any such thing, because it's implemented like this:
+ case LOCK_TIMEOUT_PER_STMT:
+ enable_timeout_at(LOCK_TIMEOUT,
+ TimestampTzPlusMilliseconds(
+ GetCurrentStatementStartTimestamp(),
+ LockTimeout));
+ break;
That doesn't provide anything like "you can spend at most N milliseconds
waiting for locks during a statement". What it is is "if you happen to be
waiting for a lock N milliseconds after the statement starts, or if you
attempt to acquire any lock more than N milliseconds after the statement
starts, you lose instantly". I don't think that definition actually adds
any useful functionality compared to setting statement_timeout to N
milliseconds, and it's certainly wrongly documented. To do what the
documentation implies would require tracking and adding up the time spent
waiting for locks during a statement. Which might be a good thing to do,
especially if the required gettimeofday() calls could be shared with what
timeout.c probably has to do anyway at start and stop of a lock wait.
But this code doesn't do it.
Lastly, I'm not sure where is the best place to be adding the control
logic for this, but I'm pretty sure postinit.c is not it. It oughta be
somewhere under storage/lmgr/, no?
regards, tom lane
2012-09-22 20:49 keltezéssel, Tom Lane írta:
Boszormenyi Zoltan <zb@cybertec.at> writes:
new version with a lot more cleanup is attached.
I looked at this patch, and frankly I'm rather dismayed. It's a mess.
Thank you for the kind words. :-)
To start at the bottom level, the changes to PGSemaphoreLock broke it,
and seem probably unnecessary anyway. As coded, calling the "condition
checker" breaks handling of error returns from semop(), unless the checker
is careful to preserve errno, which LmgrTimeoutCondition isn't (and really
shouldn't need to be anyway).
I would call it an oversight, nothing more.
More, if the checker does return true,
it causes PGSemaphoreLock to utterly violate its contract: it returns to
the caller without having acquired the semaphore, and without even telling
the caller so. Worse, if we *did* acquire the semaphore, we might still
exit via this path, since the placement of the condition check call
ignores the comment a few lines up:* Once we acquire the lock, we do NOT check for an interrupt before
* returning. The caller needs to be able to record ownership of the lock
* before any interrupt can be accepted.We could possibly fix all this with a redesigned API contract for
PGSemaphoreLock, but frankly I do not see a good reason to be tinkering
with it at all. We never needed to get it involved with deadlock check
handling, and I don't see why that needs to change for lock timeouts.One very good reason why monkeying with PGSemaphoreLock is wrong is that
on some platforms a SIGALRM interrupt won't interrupt the semop() call,
and thus control would never reach the "checker" anyway. If we're going
to throw an error, it must be thrown from the interrupt handler.
Then please, explain to me, how on Earth can the current deadlock_timeout
can report the error? Sure, I can see the PG_TRY() ... PG_END_TRY() block in
lock.c but as far as I can see, nothing in the CheckDeadLock() -> DeadLockCheck()
-> DeadLockCheckRecurse() path diverts the code to return to a different
address from the signal handler, i.e. there is no elog(ERROR) or ereport(ERROR)
even in the DS_HARD_DEADLOCK case, so nothing calls siglongjmp(). So logically
the code shouldn't end up in the PG_CATCH() branch.
semop() will only get an errno = EINTR when returning from the signal handler
and would loop again. Then what makes it return beside being able to lock the
semaphore? The conditions in ProcSleep() that e.g. print the lock stats work somehow.
The whole lmgrtimeout module seems to me to be far more mechanism than is
warranted, for too little added functionality. In the first place, there
is nothing on the horizon suggesting that we need to let any plug-in code
get control here, and if anything the delicacy of what's going on leads me
to not wish to expose such a possibility. In the second place, it isn't
adding any actually useful functionality, it's just agglomerating some
checks. The minimum thing I would want it to do is avoid calling
timeout.c multiple times, which is what would happen right now (leading
to four extra syscalls per lock acquisition, which is enough new overhead
to constitute a strong objection to committing this patch at all).On the whole I think we could forget lmgrtimeout and just hardwire the
lock timeout and deadlock check cases. But in any case we're going to
need support in timeout.c for enabling/disabling multiple timeouts at
once without extra setitimer calls.
OK, so you prefer the previous hardcoding PGSemaphoreTimedLock()
that makes every LockAcquire() check its return code and the detailed
error message about failed to lock the given object?
I will add new functions to timeout.c to remove many timeout sources
at once to decrease the amount of syscalls needed.
I'm also not thrilled about the way in which the existing deadlock
checking code has been hacked up. As an example, you added this to
DeadLockReport():+ if (!DeadLockTimeoutCondition())
+ return;which again causes it to violate its contract, namely to report a
deadlock, in the most fundamental way -- existing callers aren't
expecting it to return *at all*.
Existing caller*s*? There is only one caller. The reasoning behind this
change was that if the code reaches ReportLmgrTimeoutError() in lock.c
then at least one timeout triggered and one *Report function in the chain
will do ereport(ERROR). *THAT* would trigger te siglongjmp() ending
up in PG_CATCH(). The added lines in DeadLockReport() ensures that
the deadlock error is not reported if it didn't trigger.
Surely we can decouple the deadlock
and lock timeout cases better than that; or at least if we can't it's
a delusion to propose anything like lmgrtimeout in the first place.There's considerable lack of attention to updating comments, too.
For instance in WaitOnLock you only bothered to update the comment
immediately adjacent to the changed code, and not the two comment
blocks above that, which both have specific references to deadlocks
being the reason for failure.Also, the "per statement" mode for lock timeout doesn't seem to be
any such thing, because it's implemented like this:+ case LOCK_TIMEOUT_PER_STMT: + enable_timeout_at(LOCK_TIMEOUT, + TimestampTzPlusMilliseconds( + GetCurrentStatementStartTimestamp(), + LockTimeout)); + break;That doesn't provide anything like "you can spend at most N milliseconds
waiting for locks during a statement". What it is is "if you happen to be
waiting for a lock N milliseconds after the statement starts, or if you
attempt to acquire any lock more than N milliseconds after the statement
starts, you lose instantly". I don't think that definition actually adds
any useful functionality compared to setting statement_timeout to N
milliseconds, and it's certainly wrongly documented.
OK, I will add the code to "lose instantly" if the code happens to be executed
after the specified deadline, this is another oversight on my side. Where can
I get brown paper bags in large amounts? :-)
But I certainly don't agree with you that the "per statement" variant of the
lock timeout is nothing more than statement timeout. What happens if the
statement doesn't spend its timeout i.e. in the lockless SELECT case or anything
that has to lock but it can get all the locks under the specified time?
statement_timeout can trigger the error while the server is already returning
the data to the client and this is not useful. Thanks to the single-row processing
mode introduced in 9.2, the client can at least get partial data and an error
before the next row.
I have certainly seen use cases that said: I only allow the statement X time
to wait on locks but otherwise give me all data.
To do what the
documentation implies would require tracking and adding up the time spent
waiting for locks during a statement. Which might be a good thing to do,
especially if the required gettimeofday() calls could be shared with what
timeout.c probably has to do anyway at start and stop of a lock wait.
I will look into it.
But this code doesn't do it.
Lastly, I'm not sure where is the best place to be adding the control
logic for this, but I'm pretty sure postinit.c is not it. It oughta be
somewhere under storage/lmgr/, no?
Indeed.
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/
Hi,
2012-09-22 20:49 keltezéssel, Tom Lane írta:
Boszormenyi Zoltan <zb@cybertec.at> writes:
new version with a lot more cleanup is attached.
I looked at this patch, and frankly I'm rather dismayed. It's a mess.
I hope you won't find this one a mess. I tried to address all your complaints.
[rather long diatribe on modifying PGSemaphoreLock improperly]
I have returned to the previous version that used PGSemaphoreTimedLock
and this time I save and restore errno around calling the timeout condition
checker.
The whole lmgrtimeout module seems to me to be far more mechanism than is
warranted, for too little added functionality. [...]
lmgrtimeout is no more, back to hardcoding things.
The minimum thing I would want it to do is avoid calling
timeout.c multiple times, which is what would happen right now (leading
to four extra syscalls per lock acquisition, which is enough new overhead
to constitute a strong objection to committing this patch at all).
I have added enable_multiple_timeouts() and disable_multiple_timeouts()
that minimize the number setitimer() calls.
There's considerable lack of attention to updating comments, too.
For instance in WaitOnLock you only bothered to update the comment
immediately adjacent to the changed code, and not the two comment
blocks above that, which both have specific references to deadlocks
being the reason for failure.
I modified the comment in question. I hope the wording is right.
Also, the "per statement" mode for lock timeout doesn't seem to be
any such thing, because it's implemented like this:+ case LOCK_TIMEOUT_PER_STMT: + enable_timeout_at(LOCK_TIMEOUT, + TimestampTzPlusMilliseconds( + GetCurrentStatementStartTimestamp(), + LockTimeout)); + break;That doesn't provide anything like "you can spend at most N milliseconds
waiting for locks during a statement". What it is is "if you happen to be
waiting for a lock N milliseconds after the statement starts, or if you
attempt to acquire any lock more than N milliseconds after the statement
starts, you lose instantly". I don't think that definition actually adds
any useful functionality compared to setting statement_timeout to N
milliseconds, and it's certainly wrongly documented. To do what the
documentation implies would require tracking and adding up the time spent
waiting for locks during a statement. Which might be a good thing to do,
especially if the required gettimeofday() calls could be shared with what
timeout.c probably has to do anyway at start and stop of a lock wait.
But this code doesn't do it.
The code now properly accumulates the time spent in waiting for
LOCK_TIMEOUT. This means that if STATEMENT_TIMEOUT and
LOCK_TIMEOUT are set to the same value, STATEMENT_TIMEOUT
will trigger because it considers the time as one contiguous unit,
LOCK_TIMEOUT only accounts the time spent in waiting, not the time
spent with useful work. This means that LOCK_TIMEOUT doesn't
need any special code in its handler function, it's a NOP. The
relation between timeouts is only handled by the timeout.c module.
Lastly, I'm not sure where is the best place to be adding the control
logic for this, but I'm pretty sure postinit.c is not it. It oughta be
somewhere under storage/lmgr/, no?
The above change means that there is no control logic outside of
storage/lmgr now.
I temporarily abandoned the idea of detailed error reporting on
the object type and name/ID. WaitOnLock() reports "canceling statement
due to lock timeout" and LockAcquire() kept its previous semantics.
This can be quickly revived in case of demand, it would be another ~15K patch.
I hope you can find another time slot in this CF to review this one.
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/
Attachments:
Hi,
this is the latest one, fixing a bug in the accounting
of per-statement lock timeout handling and tweaking
some comments.
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/
Attachments:
Boszormenyi Zoltan escribió:
Hi,
this is the latest one, fixing a bug in the accounting
of per-statement lock timeout handling and tweaking
some comments.
Tom, are you able to give this patch some more time on this commitfest?
(If not, I think it would be fair to boot it to CF3; this is final in a
series, there's nothing that depends on it, and there's been good
movement on it; there's plenty of time before the devel cycle closes.)
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
Boszormenyi Zoltan escribi�:
this is the latest one, fixing a bug in the accounting
of per-statement lock timeout handling and tweaking
some comments.
Tom, are you able to give this patch some more time on this commitfest?
I'm still hoping to get to it, but I've been spending a lot of time on
bug fixing rather than patch review lately :-(. If you're hoping to
close out the current CF soon, maybe we should just slip it to the next
one.
regards, tom lane
2012-10-18 20:08 keltezéssel, Tom Lane írta:
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
Boszormenyi Zoltan escribió:
this is the latest one, fixing a bug in the accounting
of per-statement lock timeout handling and tweaking
some comments.Tom, are you able to give this patch some more time on this commitfest?
I'm still hoping to get to it, but I've been spending a lot of time on
bug fixing rather than patch review lately :-(. If you're hoping to
close out the current CF soon, maybe we should just slip it to the next
one.
Fine by me. Thanks.
--
----------------------------------
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/
On 2012-10-18 22:40:15 +0200, Boszormenyi Zoltan wrote:
2012-10-18 20:08 keltezéssel, Tom Lane írta:
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
Boszormenyi Zoltan escribió:
this is the latest one, fixing a bug in the accounting
of per-statement lock timeout handling and tweaking
some comments.Tom, are you able to give this patch some more time on this commitfest?
I'm still hoping to get to it, but I've been spending a lot of time on
bug fixing rather than patch review lately :-(. If you're hoping to
close out the current CF soon, maybe we should just slip it to the next
one.Fine by me. Thanks.
According to this the current state of the patch should be "Ready for
Committer" not "Needs Review" is that right? I changed the state for
now...
Greetings,
Andres Freund
--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
2012-12-08 15:30 keltezéssel, Andres Freund írta:
On 2012-10-18 22:40:15 +0200, Boszormenyi Zoltan wrote:
2012-10-18 20:08 keltezéssel, Tom Lane írta:
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
Boszormenyi Zoltan escribió:
this is the latest one, fixing a bug in the accounting
of per-statement lock timeout handling and tweaking
some comments.Tom, are you able to give this patch some more time on this commitfest?
I'm still hoping to get to it, but I've been spending a lot of time on
bug fixing rather than patch review lately :-(. If you're hoping to
close out the current CF soon, maybe we should just slip it to the next
one.Fine by me. Thanks.
According to this the current state of the patch should be "Ready for
Committer" not "Needs Review" is that right? I changed the state for
now...
Thanks. I just tried the patch on current GIT HEAD and
gives some offset warnings but no rejects. Also, it compiles
without warnings and still works as it should.
Should I post a new patch that applies cleanly?
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/
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Boszormenyi Zoltan <zb@cybertec.at> writes:
Thanks. I just tried the patch on current GIT HEAD and
gives some offset warnings but no rejects. Also, it compiles
without warnings and still works as it should.
Should I post a new patch that applies cleanly?
Offsets are not a problem --- if you tried to keep them exact you'd just
be making a lot of unnecessary updates. If there were fuzz warnings I'd
be more concerned.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers