Пример #1
0
/*
 * 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();
}
Пример #2
0
/*
 * 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();
}
Пример #3
0
/*
 * 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();
}