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 log_min_error_statement is set to ERROR or lower, the statement that timed out will also be ! logged. A value of zero (the default) turns this off. --- 4980,4989 ---- milliseconds, starting from the time the command arrives at the server from the client. If log_min_error_statement is set to 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. *************** COPY postgres_log FROM '/full/path/to/lo *** 4991,4996 **** --- 4994,5053 ---- + + lock_timeout (integer) + + lock_timeout configuration parameter + + + + 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 + statement_timeout, this timeout (and the error) may only + occur while waiting for locks. If log_min_error_statement + is set to ERROR or lower, the statement that timed out will + also be logged. A value of zero (the default) turns this off. + + + + Setting lock_timeout in + postgresql.conf is not recommended because it + affects all sessions. + + + + + + lock_timeout_option (enum) + + lock_timeout_option configuration parameter + + + + Control the behaviour of lock_timeout. Possible values are + 'per_lock' and 'per_statement'. + + + + 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 * lock_timeout time in + the worst case where N is the number of locks attempted. This is the + default. + + + + With 'per_statement' in effect, lock_timeout behaves like + 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. + + + + + vacuum_freeze_table_age (integer) 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 ] NOWAIT is specified, LOCK TABLE 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 UNLOCK TABLE command; locks are always released at transaction end.) --- 39,49 ---- NOWAIT is specified, LOCK TABLE does not wait to acquire the desired lock: if it cannot be acquired immediately, the command is aborted and an ! error is emitted. If lock_timeout 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 UNLOCK TABLE command; locks are always released at transaction end.) 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 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