/* * LWLockConditionalAcquire - acquire a lightweight lock in the specified mode * * If the lock is not available, return FALSE with no side-effects. * * If successful, cancel/die interrupts are held off until lock release. */ bool LWLockConditionalAcquire(LWLockId lockid, LWLockMode mode) { volatile LWLock *lock = LWLockArray + lockid; bool mustwait; PRINT_LWDEBUG("LWLockConditionalAcquire", lockid, lock); /* * Lock out cancel/die interrupts until we exit the code section * protected by the LWLock. This ensures that interrupts will not * interfere with manipulations of data structures in shared memory. */ HOLD_INTERRUPTS(); /* Acquire mutex. Time spent holding mutex should be short! */ SpinLockAcquire_NoHoldoff(&lock->mutex); /* If I can get the lock, do so quickly. */ if (mode == LW_EXCLUSIVE) { if (lock->exclusive == 0 && lock->shared == 0) { lock->exclusive++; mustwait = false; } else mustwait = true; } else { if (lock->exclusive == 0) { lock->shared++; mustwait = false; } else mustwait = true; } /* We are done updating shared state of the lock itself. */ SpinLockRelease_NoHoldoff(&lock->mutex); if (mustwait) { /* Failed to get lock, so release interrupt holdoff */ RESUME_INTERRUPTS(); LOG_LWDEBUG("LWLockConditionalAcquire", lockid, "failed"); } else { /* Add lock to list of locks held by this backend */ Assert(num_held_lwlocks < MAX_SIMUL_LWLOCKS); held_lwlocks[num_held_lwlocks++] = lockid; } return !mustwait; }
/* * LWLockRelease - release a previously acquired lock */ void LWLockRelease(LWLockId lockid) { volatile LWLock *lock = &(LWLockArray[lockid].lock); #if LWLOCK_LOCK_PARTS > 1 volatile LWLockPart *part = LWLOCK_PART(lock, lockid, MyBackendId); #endif PGPROC *head = NULL; PGPROC *proc; LWLockMode mode; int i; PRINT_LWDEBUG("LWLockRelease", lockid, lock); /* * Remove lock from list of locks held. Usually, but not always, it will * be the latest-acquired lock; so search array backwards. */ for (i = num_held_lwlocks; --i >= 0;) { if (lockid == held_lwlocks[i]) break; } if (i < 0) elog(ERROR, "lock %d is not held", (int) lockid); mode = held_lwlocks_mode[i]; num_held_lwlocks--; for (; i < num_held_lwlocks; i++) { held_lwlocks[i] = held_lwlocks[i + 1]; held_lwlocks_mode[i] = held_lwlocks_mode[i + 1]; } if (mode == LW_SHARED) { #ifdef LWLOCK_PART_SHARED_OPS_ATOMIC int shared_pre; /* Release my hold on lock */ Assert(lock->exclusive == 0); shared_pre = LWLOCK_PART_SHARED_POSTDEC_ATOMIC(lock, lockid, part, MyBackendId); Assert(shared_pre > 0); /* If the count didn't drop to zero (i.e., there are more lockers * using the same shared counter partition), we can leave waiting * up blocked exclusive locking attempts to them. Note that there * may also be shared lockers using a *different* partition, so * we're not necessarily the last share lockers, even if we continue. * Still, it's an easy optimization, so we got for it */ if (shared_pre > 1) goto lock_released; LWLOCK_PART_SHARED_FENCE(); /* A concurrent exclusive locking attempt does the following * three steps * 1) Acquire mutex * 2) Check shared counter partitions for readers. * 3a) If found add proc to wait queue, block, restart at (1) * 3b) If not found, set exclusive flag, continue with (4) * 4) Enter protected section * Assume now that we're that last share lock holder. Then, the * fence after the atomic add above ensures that no further such * concurrent exclusive locking attempts will proceed to (3a) and * thus block. There may be such attempts currently blocking or * about to block, but we can recognize those by either wait queue * being non-empty or the mutex being taken. Conversely, if we see * neither, we may assume that nobody needs to be signalled. * * Note that if two shared lockers release their lock while * an exclusive locking attempt is in progress, both may decide * they need to signal here. Taking the mutex below will sort that * out, but it's a bit unfortunate that they have to race for the * mutex here. Also, taking the mutex will force *other* shared * lockers to take the mutex also in their release path. * XXX: We may be able to improve that if we could dinstinguish * been mutexed held for the purpose of unlocking and mutexes * held for the purpose of locking. * * FIXME: This doesn't work if slock_t is a struct. */ if ((lock->mutex == 0) && (lock->head == NULL)) goto lock_released; /* At this point, we don't know if the concurrent exclusive locker * has seen on-zero in our shared counter partition in his step * (2) or not. We must thus take the mutex and re-check. */ #endif /* LWLOCK_PART_SHARED_OPS_ATOMIC */ /* Acquire mutex. Time spent holding mutex should be short! */ SpinLockAcquire(&lock->mutex); #ifdef LWLOCK_PART_SHARED_OPS_ATOMIC /* Already decremented the shared counter partition above */ #else /* Release my hold on lock */ lock->shared--; #endif } else { /* Acquire mutex. Time spent holding mutex should be short! */ SpinLockAcquire(&lock->mutex); /* Release my hold on lock */ lock->exclusive--; Assert(lock->exclusive == 0); } /* * See if I need to awaken any waiters. If I released a non-last shared * hold, there cannot be anything to do. Also, do not awaken any waiters * if someone has already awakened waiters that haven't yet acquired the * lock. */ head = lock->head; if (head != NULL) { bool is_shared; if (mode == LW_SHARED) LWLOCK_IS_SHARED(is_shared, lock, lockid); else is_shared = false; if (lock->exclusive == 0 && !is_shared && lock->releaseOK) { /* * Remove the to-be-awakened PGPROCs from the queue. If the front * waiter wants exclusive lock, awaken him only. Otherwise awaken * as many waiters as want shared access. */ proc = head; if (!proc->lwExclusive) { while (proc->lwWaitLink != NULL && !proc->lwWaitLink->lwExclusive) proc = proc->lwWaitLink; } /* proc is now the last PGPROC to be released */ lock->head = proc->lwWaitLink; proc->lwWaitLink = NULL; /* prevent additional wakeups until retryer gets to run */ lock->releaseOK = false; } else { /* lock is still held, can't awaken anything */ head = NULL; } } /* We are done updating shared state of the lock itself. */ SpinLockRelease(&lock->mutex); #ifdef LWLOCK_PART_SHARED_OPS_ATOMIC lock_released: #endif TRACE_POSTGRESQL_LWLOCK_RELEASE(lockid); /* * Awaken any waiters I removed from the queue. */ while (head != NULL) { LOG_LWDEBUG("LWLockRelease", lockid, "release waiter"); proc = head; head = proc->lwWaitLink; proc->lwWaitLink = NULL; proc->lwWaiting = false; PGSemaphoreUnlock(&proc->sem); } /* * Now okay to allow cancel/die interrupts. */ RESUME_INTERRUPTS(); }
/* * LWLockConditionalAcquire - acquire a lightweight lock in the specified mode * * If the lock is not available, return FALSE with no side-effects. * * If successful, cancel/die interrupts are held off until lock release. */ bool LWLockConditionalAcquire(LWLockId lockid, LWLockMode mode) { volatile LWLock *lock = &(LWLockArray[lockid].lock); #if LWLOCK_LOCK_PARTS > 1 volatile LWLockPart *part = LWLOCK_PART(lock, lockid, MyBackendId); #endif bool mustwait; PRINT_LWDEBUG("LWLockConditionalAcquire", lockid, lock); /* Ensure we will have room to remember the lock */ if (num_held_lwlocks >= MAX_SIMUL_LWLOCKS) elog(ERROR, "too many LWLocks taken"); /* * Lock out cancel/die interrupts until we exit the code section protected * by the LWLock. This ensures that interrupts will not interfere with * manipulations of data structures in shared memory. */ HOLD_INTERRUPTS(); if (mode == LW_SHARED) { #ifdef LWLOCK_PART_SHARED_OPS_ATOMIC /* Increment shared counter partition. If there's no contention, * this is sufficient to take the lock */ LWLOCK_PART_SHARED_POSTINC_ATOMIC(lock, lockid, part, MyBackendId); LWLOCK_PART_SHARED_FENCE(); /* A concurrent exclusive locking attempt does the following * three steps * 1) Acquire mutex * 2) Check shared counter partitions for readers. * 3a) If found add proc to wait queue, block, restart at (1) * 3b) If not found, set exclusive flag, continue with (4) * 4) Enter protected section * The fence after the atomic add above ensures that no further * such attempt can proceed to (3b) or beyond. There may be * pre-existing exclusive locking attempts at step (3b) or beyond, * but we can recognize those by either the mutex being taken, or * the exclusive flag being set. Conversely, if we see neither, we * may proceed and enter the protected section. * * FIXME: This doesn't work if slock_t is a struct or doesn't * use 0 for state "unlocked". */ if ((lock->mutex == 0) && (lock->exclusive == 0)) goto lock_acquired; /* At this point, we don't know if the concurrent exclusive locker * has proceeded to (3b) or blocked. We must take the mutex and * re-check */ #endif /* LWLOCK_PART_SHARED_OPS_ATOMIC */ /* Acquire mutex. Time spent holding mutex should be short! */ SpinLockAcquire(&lock->mutex); if (lock->exclusive == 0) { #ifdef LWLOCK_PART_SHARED_OPS_ATOMIC /* Already incremented the shared counter partition above */ #else lock->shared++; #endif mustwait = false; } else { #ifdef LWLOCK_PART_SHARED_OPS_ATOMIC /* Must undo shared counter partition increment. Note that * we *need* to do that while holding the mutex. Otherwise, * the exclusive lock could be released and attempted to be * re-acquired before we undo the increment. That attempt * would then block, even though there'd be no lock holder * left */ LWLOCK_PART_SHARED_POSTDEC_ATOMIC(lock, lockid, part, MyBackendId); #endif mustwait = true; } } else { /* Step (1). Acquire mutex. Time spent holding mutex should be * short! */ SpinLockAcquire(&lock->mutex); if (lock->exclusive == 0) { /* Step (2). Check for shared lockers. This surely happens * after (1), otherwise SpinLockAcquire() is broken. Lock * acquire semantics demand that no load must be re-ordered * from after a lock acquisition to before, for obvious * reasons. */ LWLOCK_IS_SHARED(mustwait, lock, lockid); if (!mustwait) { /* Step (3a). Set exclusive flag. This surely happens * after (2) because it depends on the result of (2), * no matter how much reordering is going on here. */ lock->exclusive++; } } else mustwait = true; } /* We are done updating shared state of the lock itself. */ SpinLockRelease(&lock->mutex); if (mustwait) { /* Failed to get lock, so release interrupt holdoff */ RESUME_INTERRUPTS(); LOG_LWDEBUG("LWLockConditionalAcquire", lockid, "failed"); TRACE_POSTGRESQL_LWLOCK_CONDACQUIRE_FAIL(lockid, mode); return false; } #ifdef LWLOCK_PART_SHARED_OPS_ATOMIC lock_acquired: #endif TRACE_POSTGRESQL_LWLOCK_CONDACQUIRE(lockid, mode); /* Add lock to list of locks held by this backend */ held_lwlocks[num_held_lwlocks] = lockid; held_lwlocks_mode[num_held_lwlocks] = mode; ++num_held_lwlocks; return true; }
/* * LWLockAcquire - acquire a lightweight lock in the specified mode * * If the lock is not available, sleep until it is. * * Side effect: cancel/die interrupts are held off until lock release. */ void LWLockAcquire(LWLockId lockid, LWLockMode mode) { volatile LWLock *lock = &(LWLockArray[lockid].lock); #if LWLOCK_LOCK_PARTS > 1 volatile LWLockPart *part = LWLOCK_PART(lock, lockid, MyBackendId); #endif PGPROC *proc = MyProc; bool retry = false; int extraWaits = 0; PRINT_LWDEBUG("LWLockAcquire", lockid, lock); #ifdef LWLOCK_STATS /* Set up local count state first time through in a given process */ if (counts_for_pid != MyProcPid) { int *LWLockCounter = (int *) ((char *) LWLockArray - 2 * sizeof(int)); int numLocks = LWLockCounter[1]; sh_acquire_counts = calloc(numLocks, sizeof(int)); ex_acquire_counts = calloc(numLocks, sizeof(int)); block_counts = calloc(numLocks, sizeof(int)); counts_for_pid = MyProcPid; on_shmem_exit(print_lwlock_stats, 0); } /* Count lock acquisition attempts */ if (mode == LW_EXCLUSIVE) ex_acquire_counts[lockid]++; else sh_acquire_counts[lockid]++; #endif /* LWLOCK_STATS */ /* * We can't wait if we haven't got a PGPROC. This should only occur * during bootstrap or shared memory initialization. Put an Assert here * to catch unsafe coding practices. */ Assert(!(proc == NULL && IsUnderPostmaster)); /* Ensure we will have room to remember the lock */ if (num_held_lwlocks >= MAX_SIMUL_LWLOCKS) elog(ERROR, "too many LWLocks taken"); /* * Lock out cancel/die interrupts until we exit the code section protected * by the LWLock. This ensures that interrupts will not interfere with * manipulations of data structures in shared memory. */ HOLD_INTERRUPTS(); /* * Loop here to try to acquire lock after each time we are signaled by * LWLockRelease. * * NOTE: it might seem better to have LWLockRelease actually grant us the * lock, rather than retrying and possibly having to go back to sleep. But * in practice that is no good because it means a process swap for every * lock acquisition when two or more processes are contending for the same * lock. Since LWLocks are normally used to protect not-very-long * sections of computation, a process needs to be able to acquire and * release the same lock many times during a single CPU time slice, even * in the presence of contention. The efficiency of being able to do that * outweighs the inefficiency of sometimes wasting a process dispatch * cycle because the lock is not free when a released waiter finally gets * to run. See pgsql-hackers archives for 29-Dec-01. */ for (;;) { bool mustwait; if (mode == LW_SHARED) { #ifdef LWLOCK_PART_SHARED_OPS_ATOMIC /* Increment shared counter partition. If there's no contention, * this is sufficient to take the lock */ LWLOCK_PART_SHARED_POSTINC_ATOMIC(lock, lockid, part, MyBackendId); LWLOCK_PART_SHARED_FENCE(); /* A concurrent exclusive locking attempt does the following * three steps * 1) Acquire mutex * 2) Check shared counter partitions for readers. * 3a) If found add proc to wait queue, block, restart at (1) * 3b) If not found, set exclusive flag, continue with (4) * 4) Enter protected section * The fence after the atomic add above ensures that no further * such attempt can proceed to (3b) or beyond. There may be * pre-existing exclusive locking attempts at step (3b) or beyond, * but we can recognize those by either the mutex being taken, or * the exclusive flag being set. Conversely, if we see neither, we * may proceed and enter the protected section. * * FIXME: This doesn't work if slock_t is a struct or doesn't * use 0 for state "unlocked". */ if ((lock->mutex == 0) && (lock->exclusive == 0)) { /* If retrying, allow LWLockRelease to release waiters again. * Usually this happens after we acquired the mutex, but if * we skip that, we still need to set releaseOK. * * Acquiring the mutex here is not really an option - if many * reader are awoken simultaneously by an exclusive unlock, * that would be a source of considerable contention. * * Fotunately, this is safe even without the mutex. First, * there actually cannot be any non-fast path unlocking * attempt in progress, because we'd then either still see * the exclusive flag set or the mutex being taken. And * even if there was, and such an attempt cleared the flag * immediately after we set it, it'd also wake up some waiter * who'd then re-set the flag. * * The only reason to do this here, and not directly * after returning from PGSemaphoreLock(), is that it seems * benefical to make SpinLockAcquire() the first thing to * touch the lock if possible, in case we acquire the spin * lock at all. That way, the cache line doesn't go through * a possible shared state, but instead directly to exclusive. * On Opterons at least, there seems to be a difference, c.f. * the comment above tas() for x86_64 in s_lock.h */ if (retry && !lock->releaseOK) lock->releaseOK = true; goto lock_acquired; } /* At this point, we don't know if the concurrent exclusive locker * has proceeded to (3b) or blocked. We must take the mutex and * re-check */ #endif /* LWLOCK_PART_SHARED_OPS_ATOMIC */ /* Acquire mutex. Time spent holding mutex should be short! */ SpinLockAcquire(&lock->mutex); if (lock->exclusive == 0) { #ifdef LWLOCK_PART_SHARED_OPS_ATOMIC /* Already incremented the shared counter partition above */ #else lock->shared++; #endif mustwait = false; } else { #ifdef LWLOCK_PART_SHARED_OPS_ATOMIC /* Must undo shared counter partition increment. Note that * we *need* to do that while holding the mutex. Otherwise, * the exclusive lock could be released and attempted to be * re-acquired before we undo the increment. That attempt * would then block, even though there'd be no lock holder * left */ LWLOCK_PART_SHARED_POSTDEC_ATOMIC(lock, lockid, part, MyBackendId); #endif mustwait = true; } } else { /* Step (1). Acquire mutex. Time spent holding mutex should be * short! */ SpinLockAcquire(&lock->mutex); if (lock->exclusive == 0) { /* Step (2). Check for shared lockers. This surely happens * after (1), otherwise SpinLockAcquire() is broken. Lock * acquire semantics demand that no load must be re-ordered * from after a lock acquisition to before, for obvious * reasons. */ LWLOCK_IS_SHARED(mustwait, lock, lockid); if (!mustwait) { /* Step (3a). Set exclusive flag. This surely happens * after (2) because it depends on the result of (2), * no matter how much reordering is going on here. */ lock->exclusive++; } } else mustwait = true; } /* If retrying, allow LWLockRelease to release waiters again. * This is also separately done in the LW_SHARED early exit case * above, and in contrast to there we don't hold the mutex there. * See the comment there for why this is safe */ if (retry) lock->releaseOK = true; if (!mustwait) break; /* got the lock */ /* * Step (3b). Add myself to wait queue. * * If we don't have a PGPROC structure, there's no way to wait. This * should never occur, since MyProc should only be null during shared * memory initialization. */ if (proc == NULL) elog(PANIC, "cannot wait without a PGPROC structure"); proc->lwWaiting = true; proc->lwExclusive = (mode == LW_EXCLUSIVE); proc->lwWaitLink = NULL; if (lock->head == NULL) lock->head = proc; else lock->tail->lwWaitLink = proc; lock->tail = proc; /* Can release the mutex now */ SpinLockRelease(&lock->mutex); /* * Wait until awakened. * * Since we share the process wait semaphore with the regular lock * manager and ProcWaitForSignal, and we may need to acquire an LWLock * while one of those is pending, it is possible that we get awakened * for a reason other than being signaled by LWLockRelease. If so, * loop back and wait again. Once we've gotten the LWLock, * re-increment the sema by the number of additional signals received, * so that the lock manager or signal manager will see the received * signal when it next waits. */ LOG_LWDEBUG("LWLockAcquire", lockid, "waiting"); #ifdef LWLOCK_STATS block_counts[lockid]++; #endif TRACE_POSTGRESQL_LWLOCK_WAIT_START(lockid, mode); for (;;) { /* "false" means cannot accept cancel/die interrupt here. */ PGSemaphoreLock(&proc->sem, false); if (!proc->lwWaiting) break; extraWaits++; } TRACE_POSTGRESQL_LWLOCK_WAIT_DONE(lockid, mode); LOG_LWDEBUG("LWLockAcquire", lockid, "awakened"); /* Now loop back and try to acquire lock again. */ retry = true; } /* We are done updating shared state of the lock itself. */ SpinLockRelease(&lock->mutex); /* Step 4. Enter protected section. This surely happens after (3), * this time because lock release semantics demand that no store * must be moved from before a lock release to after the release, * again for obvious reasons */ #ifdef LWLOCK_PART_SHARED_OPS_ATOMIC lock_acquired: #endif TRACE_POSTGRESQL_LWLOCK_ACQUIRE(lockid, mode); /* Add lock to list of locks held by this backend */ held_lwlocks[num_held_lwlocks] = lockid; held_lwlocks_mode[num_held_lwlocks] = mode; ++num_held_lwlocks; /* * Fix the process wait semaphore's count for any absorbed wakeups. */ while (extraWaits-- > 0) PGSemaphoreUnlock(&proc->sem); }
/* * LWLockRelease - release a previously acquired lock */ void LWLockRelease(LWLockId lockid) { volatile LWLock *lock = &(LWLockArray[lockid].lock); PGPROC *head; PGPROC *proc; int i; PRINT_LWDEBUG("LWLockRelease", lockid, lock); /* * Remove lock from list of locks held. Usually, but not always, it will * be the latest-acquired lock; so search array backwards. */ for (i = num_held_lwlocks; --i >= 0;) { if (lockid == held_lwlocks[i]) break; } if (i < 0) elog(ERROR, "lock %d is not held", (int) lockid); num_held_lwlocks--; for (; i < num_held_lwlocks; i++) held_lwlocks[i] = held_lwlocks[i + 1]; /* Acquire mutex. Time spent holding mutex should be short! */ SpinLockAcquire(&lock->mutex); /* Release my hold on lock */ if (lock->exclusive > 0) lock->exclusive--; else { Assert(lock->shared > 0); lock->shared--; } /* * See if I need to awaken any waiters. If I released a non-last shared * hold, there cannot be anything to do. Also, do not awaken any waiters * if someone has already awakened waiters that haven't yet acquired the * lock. */ head = lock->head; if (head != NULL) { if (lock->exclusive == 0 && lock->shared == 0 && lock->releaseOK) { /* * Remove the to-be-awakened PGPROCs from the queue. If the front * waiter wants exclusive lock, awaken him only. Otherwise awaken * as many waiters as want shared access. */ proc = head; if (!proc->lwExclusive) { while (proc->lwWaitLink != NULL && !proc->lwWaitLink->lwExclusive) proc = proc->lwWaitLink; } /* proc is now the last PGPROC to be released */ lock->head = proc->lwWaitLink; proc->lwWaitLink = NULL; /* prevent additional wakeups until retryer gets to run */ lock->releaseOK = false; } else { /* lock is still held, can't awaken anything */ head = NULL; } } /* We are done updating shared state of the lock itself. */ SpinLockRelease(&lock->mutex); TRACE_POSTGRESQL_LWLOCK_RELEASE(lockid); /* * Awaken any waiters I removed from the queue. */ while (head != NULL) { LOG_LWDEBUG("LWLockRelease", lockid, "release waiter"); proc = head; head = proc->lwWaitLink; proc->lwWaitLink = NULL; proc->lwWaiting = false; PGSemaphoreUnlock(&proc->sem); } /* * Now okay to allow cancel/die interrupts. */ RESUME_INTERRUPTS(); }
/* * LWLockConditionalAcquire - acquire a lightweight lock in the specified mode * * If the lock is not available, return FALSE with no side-effects. * * If successful, cancel/die interrupts are held off until lock release. */ bool LWLockConditionalAcquire(LWLockId lockid, LWLockMode mode) { volatile LWLock *lock = &(LWLockArray[lockid].lock); bool mustwait; PRINT_LWDEBUG("LWLockConditionalAcquire", lockid, lock); /* Ensure we will have room to remember the lock */ if (num_held_lwlocks >= MAX_SIMUL_LWLOCKS) elog(ERROR, "too many LWLocks taken"); /* * Lock out cancel/die interrupts until we exit the code section protected * by the LWLock. This ensures that interrupts will not interfere with * manipulations of data structures in shared memory. */ HOLD_INTERRUPTS(); /* Acquire mutex. Time spent holding mutex should be short! */ SpinLockAcquire(&lock->mutex); /* If I can get the lock, do so quickly. */ if (mode == LW_EXCLUSIVE) { if (lock->exclusive == 0 && lock->shared == 0) { lock->exclusive++; mustwait = false; } else mustwait = true; } else { if (lock->exclusive == 0) { lock->shared++; mustwait = false; } else mustwait = true; } /* We are done updating shared state of the lock itself. */ SpinLockRelease(&lock->mutex); if (mustwait) { /* Failed to get lock, so release interrupt holdoff */ RESUME_INTERRUPTS(); LOG_LWDEBUG("LWLockConditionalAcquire", lockid, "failed"); TRACE_POSTGRESQL_LWLOCK_CONDACQUIRE_FAIL(lockid, mode); } else { /* Add lock to list of locks held by this backend */ held_lwlocks[num_held_lwlocks++] = lockid; TRACE_POSTGRESQL_LWLOCK_CONDACQUIRE(lockid, mode); } return !mustwait; }
/* * LWLockAcquire - acquire a lightweight lock in the specified mode * * If the lock is not available, sleep until it is. * * Side effect: cancel/die interrupts are held off until lock release. */ void LWLockAcquire(LWLockId lockid, LWLockMode mode) { volatile LWLock *lock = &(LWLockArray[lockid].lock); PGPROC *proc = MyProc; bool retry = false; int extraWaits = 0; PRINT_LWDEBUG("LWLockAcquire", lockid, lock); #ifdef LWLOCK_STATS /* Set up local count state first time through in a given process */ if (counts_for_pid != MyProcPid) { int *LWLockCounter = (int *) ((char *) LWLockArray - 2 * sizeof(int)); int numLocks = LWLockCounter[1]; sh_acquire_counts = calloc(numLocks, sizeof(int)); ex_acquire_counts = calloc(numLocks, sizeof(int)); block_counts = calloc(numLocks, sizeof(int)); counts_for_pid = MyProcPid; on_shmem_exit(print_lwlock_stats, 0); } /* Count lock acquisition attempts */ if (mode == LW_EXCLUSIVE) ex_acquire_counts[lockid]++; else sh_acquire_counts[lockid]++; #endif /* LWLOCK_STATS */ /* * We can't wait if we haven't got a PGPROC. This should only occur * during bootstrap or shared memory initialization. Put an Assert here * to catch unsafe coding practices. */ Assert(!(proc == NULL && IsUnderPostmaster)); /* Ensure we will have room to remember the lock */ if (num_held_lwlocks >= MAX_SIMUL_LWLOCKS) elog(ERROR, "too many LWLocks taken"); /* * Lock out cancel/die interrupts until we exit the code section protected * by the LWLock. This ensures that interrupts will not interfere with * manipulations of data structures in shared memory. */ HOLD_INTERRUPTS(); /* * Loop here to try to acquire lock after each time we are signaled by * LWLockRelease. * * NOTE: it might seem better to have LWLockRelease actually grant us the * lock, rather than retrying and possibly having to go back to sleep. But * in practice that is no good because it means a process swap for every * lock acquisition when two or more processes are contending for the same * lock. Since LWLocks are normally used to protect not-very-long * sections of computation, a process needs to be able to acquire and * release the same lock many times during a single CPU time slice, even * in the presence of contention. The efficiency of being able to do that * outweighs the inefficiency of sometimes wasting a process dispatch * cycle because the lock is not free when a released waiter finally gets * to run. See pgsql-hackers archives for 29-Dec-01. */ for (;;) { bool mustwait; /* Acquire mutex. Time spent holding mutex should be short! */ SpinLockAcquire(&lock->mutex); /* If retrying, allow LWLockRelease to release waiters again */ if (retry) lock->releaseOK = true; /* If I can get the lock, do so quickly. */ if (mode == LW_EXCLUSIVE) { if (lock->exclusive == 0 && lock->shared == 0) { lock->exclusive++; mustwait = false; } else mustwait = true; } else { if (lock->exclusive == 0) { lock->shared++; mustwait = false; } else mustwait = true; } if (!mustwait) break; /* got the lock */ /* * Add myself to wait queue. * * If we don't have a PGPROC structure, there's no way to wait. This * should never occur, since MyProc should only be null during shared * memory initialization. */ if (proc == NULL) elog(PANIC, "cannot wait without a PGPROC structure"); proc->lwWaiting = true; proc->lwExclusive = (mode == LW_EXCLUSIVE); proc->lwWaitLink = NULL; if (lock->head == NULL) lock->head = proc; else lock->tail->lwWaitLink = proc; lock->tail = proc; /* Can release the mutex now */ SpinLockRelease(&lock->mutex); /* * Wait until awakened. * * Since we share the process wait semaphore with the regular lock * manager and ProcWaitForSignal, and we may need to acquire an LWLock * while one of those is pending, it is possible that we get awakened * for a reason other than being signaled by LWLockRelease. If so, * loop back and wait again. Once we've gotten the LWLock, * re-increment the sema by the number of additional signals received, * so that the lock manager or signal manager will see the received * signal when it next waits. */ LOG_LWDEBUG("LWLockAcquire", lockid, "waiting"); #ifdef LWLOCK_STATS block_counts[lockid]++; #endif TRACE_POSTGRESQL_LWLOCK_WAIT_START(lockid, mode); for (;;) { /* "false" means cannot accept cancel/die interrupt here. */ PGSemaphoreLock(&proc->sem, false); if (!proc->lwWaiting) break; extraWaits++; } TRACE_POSTGRESQL_LWLOCK_WAIT_DONE(lockid, mode); LOG_LWDEBUG("LWLockAcquire", lockid, "awakened"); /* Now loop back and try to acquire lock again. */ retry = true; } /* We are done updating shared state of the lock itself. */ SpinLockRelease(&lock->mutex); TRACE_POSTGRESQL_LWLOCK_ACQUIRE(lockid, mode); /* Add lock to list of locks held by this backend */ held_lwlocks[num_held_lwlocks++] = lockid; /* * Fix the process wait semaphore's count for any absorbed wakeups. */ while (extraWaits-- > 0) PGSemaphoreUnlock(&proc->sem); }
/* * LWLockWaitForVar - Wait until lock is free, or a variable is updated. * * If the lock is held and *valptr equals oldval, waits until the lock is * either freed, or the lock holder updates *valptr by calling * LWLockUpdateVar. If the lock is free on exit (immediately or after * waiting), returns true. If the lock is still held, but *valptr no longer * matches oldval, returns false and sets *newval to the current value in * *valptr. * * It's possible that the lock holder releases the lock, but another backend * acquires it again before we get a chance to observe that the lock was * momentarily released. We wouldn't need to wait for the new lock holder, * but we cannot distinguish that case, so we will have to wait. * * Note: this function ignores shared lock holders; if the lock is held * in shared mode, returns 'true'. */ bool LWLockWaitForVar(LWLock *lock, uint64 *valptr, uint64 oldval, uint64 *newval) { PGPROC *proc = MyProc; int extraWaits = 0; bool result = false; #ifdef LWLOCK_STATS lwlock_stats *lwstats; #endif PRINT_LWDEBUG("LWLockWaitForVar", lock); #ifdef LWLOCK_STATS lwstats = get_lwlock_stats_entry(lock); #endif /* LWLOCK_STATS */ /* * Quick test first to see if it the slot is free right now. * * XXX: the caller uses a spinlock before this, so we don't need a memory * barrier here as far as the current usage is concerned. But that might * not be safe in general. */ if (lock->exclusive == 0) return true; /* * Lock out cancel/die interrupts while we sleep on the lock. There is no * cleanup mechanism to remove us from the wait queue if we got * interrupted. */ HOLD_INTERRUPTS(); /* * Loop here to check the lock's status after each time we are signaled. */ for (;;) { bool mustwait; uint64 value; /* Acquire mutex. Time spent holding mutex should be short! */ #ifdef LWLOCK_STATS lwstats->spin_delay_count += SpinLockAcquire(&lock->mutex); #else SpinLockAcquire(&lock->mutex); #endif /* Is the lock now free, and if not, does the value match? */ if (lock->exclusive == 0) { result = true; mustwait = false; } else { value = *valptr; if (value != oldval) { result = false; mustwait = false; *newval = value; } else mustwait = true; } if (!mustwait) break; /* the lock was free or value didn't match */ /* * Add myself to wait queue. */ proc->lwWaiting = true; proc->lwWaitMode = LW_WAIT_UNTIL_FREE; /* waiters are added to the front of the queue */ proc->lwWaitLink = lock->head; if (lock->head == NULL) lock->tail = proc; lock->head = proc; /* * Set releaseOK, to make sure we get woken up as soon as the lock is * released. */ lock->releaseOK = true; /* Can release the mutex now */ SpinLockRelease(&lock->mutex); /* * Wait until awakened. * * Since we share the process wait semaphore with the regular lock * manager and ProcWaitForSignal, and we may need to acquire an LWLock * while one of those is pending, it is possible that we get awakened * for a reason other than being signaled by LWLockRelease. If so, * loop back and wait again. Once we've gotten the LWLock, * re-increment the sema by the number of additional signals received, * so that the lock manager or signal manager will see the received * signal when it next waits. */ LOG_LWDEBUG("LWLockWaitForVar", T_NAME(lock), T_ID(lock), "waiting"); #ifdef LWLOCK_STATS lwstats->block_count++; #endif TRACE_POSTGRESQL_LWLOCK_WAIT_START(T_NAME(lock), T_ID(lock), LW_EXCLUSIVE); for (;;) { /* "false" means cannot accept cancel/die interrupt here. */ PGSemaphoreLock(&proc->sem, false); if (!proc->lwWaiting) break; extraWaits++; } TRACE_POSTGRESQL_LWLOCK_WAIT_DONE(T_NAME(lock), T_ID(lock), LW_EXCLUSIVE); LOG_LWDEBUG("LWLockWaitForVar", T_NAME(lock), T_ID(lock), "awakened"); /* Now loop back and check the status of the lock again. */ } /* We are done updating shared state of the lock itself. */ SpinLockRelease(&lock->mutex); TRACE_POSTGRESQL_LWLOCK_ACQUIRE(T_NAME(lock), T_ID(lock), LW_EXCLUSIVE); /* * Fix the process wait semaphore's count for any absorbed wakeups. */ while (extraWaits-- > 0) PGSemaphoreUnlock(&proc->sem); /* * Now okay to allow cancel/die interrupts. */ RESUME_INTERRUPTS(); return result; }
/* * LWLockAcquireOrWait - Acquire lock, or wait until it's free * * The semantics of this function are a bit funky. If the lock is currently * free, it is acquired in the given mode, and the function returns true. If * the lock isn't immediately free, the function waits until it is released * and returns false, but does not acquire the lock. * * This is currently used for WALWriteLock: when a backend flushes the WAL, * holding WALWriteLock, it can flush the commit records of many other * backends as a side-effect. Those other backends need to wait until the * flush finishes, but don't need to acquire the lock anymore. They can just * wake up, observe that their records have already been flushed, and return. */ bool LWLockAcquireOrWait(LWLock *lock, LWLockMode mode) { PGPROC *proc = MyProc; bool mustwait; int extraWaits = 0; #ifdef LWLOCK_STATS lwlock_stats *lwstats; #endif PRINT_LWDEBUG("LWLockAcquireOrWait", lock); #ifdef LWLOCK_STATS lwstats = get_lwlock_stats_entry(lock); #endif /* Ensure we will have room to remember the lock */ if (num_held_lwlocks >= MAX_SIMUL_LWLOCKS) elog(ERROR, "too many LWLocks taken"); /* * Lock out cancel/die interrupts until we exit the code section protected * by the LWLock. This ensures that interrupts will not interfere with * manipulations of data structures in shared memory. */ HOLD_INTERRUPTS(); /* Acquire mutex. Time spent holding mutex should be short! */ SpinLockAcquire(&lock->mutex); /* If I can get the lock, do so quickly. */ if (mode == LW_EXCLUSIVE) { if (lock->exclusive == 0 && lock->shared == 0) { lock->exclusive++; mustwait = false; } else mustwait = true; } else { if (lock->exclusive == 0) { lock->shared++; mustwait = false; } else mustwait = true; } if (mustwait) { /* * Add myself to wait queue. * * If we don't have a PGPROC structure, there's no way to wait. This * should never occur, since MyProc should only be null during shared * memory initialization. */ if (proc == NULL) elog(PANIC, "cannot wait without a PGPROC structure"); proc->lwWaiting = true; proc->lwWaitMode = LW_WAIT_UNTIL_FREE; proc->lwWaitLink = NULL; if (lock->head == NULL) lock->head = proc; else lock->tail->lwWaitLink = proc; lock->tail = proc; /* Can release the mutex now */ SpinLockRelease(&lock->mutex); /* * Wait until awakened. Like in LWLockAcquire, be prepared for bogus * wakups, because we share the semaphore with ProcWaitForSignal. */ LOG_LWDEBUG("LWLockAcquireOrWait", T_NAME(lock), T_ID(lock), "waiting"); #ifdef LWLOCK_STATS lwstats->block_count++; #endif TRACE_POSTGRESQL_LWLOCK_WAIT_START(T_NAME(lock), T_ID(lock), mode); for (;;) { /* "false" means cannot accept cancel/die interrupt here. */ PGSemaphoreLock(&proc->sem, false); if (!proc->lwWaiting) break; extraWaits++; } TRACE_POSTGRESQL_LWLOCK_WAIT_DONE(T_NAME(lock), T_ID(lock), mode); LOG_LWDEBUG("LWLockAcquireOrWait", T_NAME(lock), T_ID(lock), "awakened"); } else { /* We are done updating shared state of the lock itself. */ SpinLockRelease(&lock->mutex); } /* * Fix the process wait semaphore's count for any absorbed wakeups. */ while (extraWaits-- > 0) PGSemaphoreUnlock(&proc->sem); if (mustwait) { /* Failed to get lock, so release interrupt holdoff */ RESUME_INTERRUPTS(); LOG_LWDEBUG("LWLockAcquireOrWait", T_NAME(lock), T_ID(lock), "failed"); TRACE_POSTGRESQL_LWLOCK_ACQUIRE_OR_WAIT_FAIL(T_NAME(lock), T_ID(lock), mode); } else { /* Add lock to list of locks held by this backend */ held_lwlocks[num_held_lwlocks++] = lock; TRACE_POSTGRESQL_LWLOCK_ACQUIRE_OR_WAIT(T_NAME(lock), T_ID(lock), mode); } return !mustwait; }
/* * LWLockRelease - release a previously acquired lock */ void LWLockRelease(LWLock *lock) { PGPROC *head; PGPROC *proc; int i; PRINT_LWDEBUG("LWLockRelease", lock); /* * Remove lock from list of locks held. Usually, but not always, it will * be the latest-acquired lock; so search array backwards. */ for (i = num_held_lwlocks; --i >= 0;) { if (lock == held_lwlocks[i]) break; } if (i < 0) elog(ERROR, "lock %s %d is not held", T_NAME(lock), T_ID(lock)); num_held_lwlocks--; for (; i < num_held_lwlocks; i++) held_lwlocks[i] = held_lwlocks[i + 1]; /* Acquire mutex. Time spent holding mutex should be short! */ SpinLockAcquire(&lock->mutex); /* Release my hold on lock */ if (lock->exclusive > 0) lock->exclusive--; else { Assert(lock->shared > 0); lock->shared--; } /* * See if I need to awaken any waiters. If I released a non-last shared * hold, there cannot be anything to do. Also, do not awaken any waiters * if someone has already awakened waiters that haven't yet acquired the * lock. */ head = lock->head; if (head != NULL) { if (lock->exclusive == 0 && lock->shared == 0 && lock->releaseOK) { /* * Remove the to-be-awakened PGPROCs from the queue. */ bool releaseOK = true; proc = head; /* * First wake up any backends that want to be woken up without * acquiring the lock. */ while (proc->lwWaitMode == LW_WAIT_UNTIL_FREE && proc->lwWaitLink) proc = proc->lwWaitLink; /* * If the front waiter wants exclusive lock, awaken him only. * Otherwise awaken as many waiters as want shared access. */ if (proc->lwWaitMode != LW_EXCLUSIVE) { while (proc->lwWaitLink != NULL && proc->lwWaitLink->lwWaitMode != LW_EXCLUSIVE) { if (proc->lwWaitMode != LW_WAIT_UNTIL_FREE) releaseOK = false; proc = proc->lwWaitLink; } } /* proc is now the last PGPROC to be released */ lock->head = proc->lwWaitLink; proc->lwWaitLink = NULL; /* * Prevent additional wakeups until retryer gets to run. Backends * that are just waiting for the lock to become free don't retry * automatically. */ if (proc->lwWaitMode != LW_WAIT_UNTIL_FREE) releaseOK = false; lock->releaseOK = releaseOK; } else { /* lock is still held, can't awaken anything */ head = NULL; } } /* We are done updating shared state of the lock itself. */ SpinLockRelease(&lock->mutex); TRACE_POSTGRESQL_LWLOCK_RELEASE(T_NAME(lock), T_ID(lock)); /* * Awaken any waiters I removed from the queue. */ while (head != NULL) { LOG_LWDEBUG("LWLockRelease", T_NAME(lock), T_ID(lock), "release waiter"); proc = head; head = proc->lwWaitLink; proc->lwWaitLink = NULL; /* * Guarantee that lwWaiting being unset only becomes visible once the * unlink from the link has completed. Otherwise the target backend * could be woken up for other reason and enqueue for a new lock - if * that happens before the list unlink happens, the list would end up * being corrupted. * * The barrier pairs with the SpinLockAcquire() when enqueing for * another lock. */ pg_write_barrier(); proc->lwWaiting = false; PGSemaphoreUnlock(&proc->sem); } /* * Now okay to allow cancel/die interrupts. */ RESUME_INTERRUPTS(); }
/* * LWLockRelease - release a previously acquired lock */ void LWLockRelease(LWLockId lockid) { volatile LWLock *lock = &(LWLockArray[lockid].lock); PGPROC *head; PGPROC *proc; int i; bool saveExclusive; PRINT_LWDEBUG("LWLockRelease", lockid, lock); /* * Remove lock from list of locks held. Usually, but not always, it will * be the latest-acquired lock; so search array backwards. */ for (i = num_held_lwlocks; --i >= 0;) { if (lockid == held_lwlocks[i]) break; } if (i < 0) elog(ERROR, "lock %d is not held", (int) lockid); saveExclusive = held_lwlocks_exclusive[i]; if (InterruptHoldoffCount <= 0) elog(PANIC, "upon entering lock release, the interrupt holdoff count is bad (%d) for release of lock %d (%s)", InterruptHoldoffCount, (int)lockid, (saveExclusive ? "Exclusive" : "Shared")); #ifdef LWLOCK_TRACE_MIRROREDLOCK if (lockid == MirroredLock) elog(LOG, "LWLockRelease: release for MirroredLock by PID %u in held_lwlocks[%d] %s", MyProcPid, i, (held_lwlocks_exclusive[i] ? "Exclusive" : "Shared")); #endif num_held_lwlocks--; for (; i < num_held_lwlocks; i++) { held_lwlocks_exclusive[i] = held_lwlocks_exclusive[i + 1]; held_lwlocks[i] = held_lwlocks[i + 1]; #ifdef USE_TEST_UTILS_X86 /* shift stack traces */ held_lwlocks_depth[i] = held_lwlocks_depth[i + 1]; memcpy ( held_lwlocks_addresses[i], held_lwlocks_addresses[i + 1], held_lwlocks_depth[i] * sizeof(*held_lwlocks_depth) ) ; #endif /* USE_TEST_UTILS_X86 */ } // Clear out old last entry. held_lwlocks_exclusive[num_held_lwlocks] = false; held_lwlocks[num_held_lwlocks] = 0; #ifdef USE_TEST_UTILS_X86 held_lwlocks_depth[num_held_lwlocks] = 0; #endif /* USE_TEST_UTILS_X86 */ /* Acquire mutex. Time spent holding mutex should be short! */ SpinLockAcquire(&lock->mutex); /* Release my hold on lock */ if (lock->exclusive > 0) { lock->exclusive--; lock->exclusivePid = 0; } else { Assert(lock->shared > 0); lock->shared--; } /* * See if I need to awaken any waiters. If I released a non-last shared * hold, there cannot be anything to do. Also, do not awaken any waiters * if someone has already awakened waiters that haven't yet acquired the * lock. */ head = lock->head; if (head != NULL) { if (lock->exclusive == 0 && lock->shared == 0 && lock->releaseOK) { /* * Remove the to-be-awakened PGPROCs from the queue. If the front * waiter wants exclusive lock, awaken him only. Otherwise awaken * as many waiters as want shared access. */ proc = head; if (!proc->lwExclusive) { while (proc->lwWaitLink != NULL && !proc->lwWaitLink->lwExclusive) { proc = proc->lwWaitLink; if (proc->pid != 0) { lock->releaseOK = false; } } } /* proc is now the last PGPROC to be released */ lock->head = proc->lwWaitLink; proc->lwWaitLink = NULL; /* proc->pid can be 0 if process exited while waiting for lock */ if (proc->pid != 0) { /* prevent additional wakeups until retryer gets to run */ lock->releaseOK = false; } } else { /* lock is still held, can't awaken anything */ head = NULL; } } /* We are done updating shared state of the lock itself. */ SpinLockRelease(&lock->mutex); PG_TRACE1(lwlock__release, lockid); /* * Awaken any waiters I removed from the queue. */ while (head != NULL) { #ifdef LWLOCK_TRACE_MIRROREDLOCK if (lockid == MirroredLock) elog(LOG, "LWLockRelease: release waiter for MirroredLock (this PID %u", MyProcPid); #endif LOG_LWDEBUG("LWLockRelease", lockid, "release waiter"); proc = head; head = proc->lwWaitLink; proc->lwWaitLink = NULL; pg_write_barrier(); proc->lwWaiting = false; PGSemaphoreUnlock(&proc->sem); } /* * Now okay to allow cancel/die interrupts. */ if (InterruptHoldoffCount <= 0) elog(PANIC, "upon exiting lock release, the interrupt holdoff count is bad (%d) for release of lock %d (%s)", InterruptHoldoffCount, (int)lockid, (saveExclusive ? "Exclusive" : "Shared")); RESUME_INTERRUPTS(); }
/* * LWLockConditionalAcquire - acquire a lightweight lock in the specified mode * * If the lock is not available, return FALSE with no side-effects. * * If successful, cancel/die interrupts are held off until lock release. */ bool LWLockConditionalAcquire(LWLockId lockid, LWLockMode mode) { volatile LWLock *lock = &(LWLockArray[lockid].lock); bool mustwait; PRINT_LWDEBUG("LWLockConditionalAcquire", lockid, lock); /* Ensure we will have room to remember the lock */ if (num_held_lwlocks >= MAX_SIMUL_LWLOCKS) elog(ERROR, "too many LWLocks taken"); /* * Lock out cancel/die interrupts until we exit the code section protected * by the LWLock. This ensures that interrupts will not interfere with * manipulations of data structures in shared memory. */ HOLD_INTERRUPTS(); /* Acquire mutex. Time spent holding mutex should be short! */ SpinLockAcquire(&lock->mutex); /* If I can get the lock, do so quickly. */ if (mode == LW_EXCLUSIVE) { if (lock->exclusive == 0 && lock->shared == 0) { lock->exclusive++; lock->exclusivePid = MyProcPid; mustwait = false; } else mustwait = true; } else { if (lock->exclusive == 0) { lock->shared++; mustwait = false; } else mustwait = true; } /* We are done updating shared state of the lock itself. */ SpinLockRelease(&lock->mutex); if (mustwait) { /* Failed to get lock, so release interrupt holdoff */ RESUME_INTERRUPTS(); LOG_LWDEBUG("LWLockConditionalAcquire", lockid, "failed"); PG_TRACE2(lwlock__condacquire__fail, lockid, mode); } else { #ifdef LWLOCK_TRACE_MIRROREDLOCK if (lockid == MirroredLock) elog(LOG, "LWLockConditionalAcquire: MirroredLock by PID %u in held_lwlocks[%d] %s", MyProcPid, num_held_lwlocks, (mode == LW_EXCLUSIVE ? "Exclusive" : "Shared")); #endif #ifdef USE_TEST_UTILS_X86 /* keep track of stack trace where lock got acquired */ held_lwlocks_depth[num_held_lwlocks] = gp_backtrace(held_lwlocks_addresses[num_held_lwlocks], MAX_FRAME_DEPTH); #endif /* USE_TEST_UTILS_X86 */ /* Add lock to list of locks held by this backend */ held_lwlocks_exclusive[num_held_lwlocks] = (mode == LW_EXCLUSIVE); held_lwlocks[num_held_lwlocks++] = lockid; PG_TRACE2(lwlock__condacquire, lockid, mode); } return !mustwait; }
/* * LWLockAcquire - acquire a lightweight lock in the specified mode * * If the lock is not available, sleep until it is. * * Side effect: cancel/die interrupts are held off until lock release. */ void LWLockAcquire(LWLockId lockid, LWLockMode mode) { volatile LWLock *lock = LWLockArray + lockid; PGPROC *proc = MyProc; bool retry = false; int extraWaits = 0; PRINT_LWDEBUG("LWLockAcquire", lockid, lock); /* * We can't wait if we haven't got a PGPROC. This should only occur * during bootstrap or shared memory initialization. Put an Assert * here to catch unsafe coding practices. */ Assert(!(proc == NULL && IsUnderPostmaster)); /* * Lock out cancel/die interrupts until we exit the code section * protected by the LWLock. This ensures that interrupts will not * interfere with manipulations of data structures in shared memory. */ HOLD_INTERRUPTS(); /* * Loop here to try to acquire lock after each time we are signaled by * LWLockRelease. * * NOTE: it might seem better to have LWLockRelease actually grant us the * lock, rather than retrying and possibly having to go back to sleep. * But in practice that is no good because it means a process swap for * every lock acquisition when two or more processes are contending * for the same lock. Since LWLocks are normally used to protect * not-very-long sections of computation, a process needs to be able * to acquire and release the same lock many times during a single CPU * time slice, even in the presence of contention. The efficiency of * being able to do that outweighs the inefficiency of sometimes * wasting a process dispatch cycle because the lock is not free when * a released waiter finally gets to run. See pgsql-hackers archives * for 29-Dec-01. */ for (;;) { bool mustwait; /* Acquire mutex. Time spent holding mutex should be short! */ SpinLockAcquire_NoHoldoff(&lock->mutex); /* If retrying, allow LWLockRelease to release waiters again */ if (retry) lock->releaseOK = true; /* If I can get the lock, do so quickly. */ if (mode == LW_EXCLUSIVE) { if (lock->exclusive == 0 && lock->shared == 0) { lock->exclusive++; mustwait = false; } else mustwait = true; } else { if (lock->exclusive == 0) { lock->shared++; mustwait = false; } else mustwait = true; } if (!mustwait) break; /* got the lock */ /* * Add myself to wait queue. * * If we don't have a PGPROC structure, there's no way to wait. This * should never occur, since MyProc should only be null during * shared memory initialization. */ if (proc == NULL) elog(FATAL, "cannot wait without a PGPROC structure"); proc->lwWaiting = true; proc->lwExclusive = (mode == LW_EXCLUSIVE); proc->lwWaitLink = NULL; if (lock->head == NULL) lock->head = proc; else lock->tail->lwWaitLink = proc; lock->tail = proc; /* Can release the mutex now */ SpinLockRelease_NoHoldoff(&lock->mutex); /* * Wait until awakened. * * Since we share the process wait semaphore with the regular lock * manager and ProcWaitForSignal, and we may need to acquire an * LWLock while one of those is pending, it is possible that we * get awakened for a reason other than being signaled by * LWLockRelease. If so, loop back and wait again. Once we've * gotten the LWLock, re-increment the sema by the number of * additional signals received, so that the lock manager or signal * manager will see the received signal when it next waits. */ LOG_LWDEBUG("LWLockAcquire", lockid, "waiting"); for (;;) { /* "false" means cannot accept cancel/die interrupt here. */ PGSemaphoreLock(&proc->sem, false); if (!proc->lwWaiting) break; extraWaits++; } LOG_LWDEBUG("LWLockAcquire", lockid, "awakened"); /* Now loop back and try to acquire lock again. */ retry = true; } /* We are done updating shared state of the lock itself. */ SpinLockRelease_NoHoldoff(&lock->mutex); /* Add lock to list of locks held by this backend */ Assert(num_held_lwlocks < MAX_SIMUL_LWLOCKS); held_lwlocks[num_held_lwlocks++] = lockid; /* * Fix the process wait semaphore's count for any absorbed wakeups. */ while (extraWaits-- > 0) PGSemaphoreUnlock(&proc->sem); }