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 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 the 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-17 19:36:22.742266146 +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-19 12:44:15.232222764 +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-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