[PATCH] Update to new generic semaphore algorithm v2
Andreas Schwab
schwab@suse.de
Thu Dec 5 15:05:08 GMT 2024
On Jan 13 2015, Torvald Riegel wrote:
> +/* Slow path that blocks. */
> +static int
> +__attribute__ ((noinline))
> +__new_sem_wait_slow (struct new_sem *sem, const struct timespec *abstime)
> +{
> + int err = 0;
> +
> +#if __HAVE_64B_ATOMICS
> + /* Add a waiter. Relaxed MO is sufficient because we can rely on the
> + ordering provided by the RMW operations we use. */
> + unsigned long d = atomic_fetch_add_relaxed (&sem->data,
> + 1UL << SEM_NWAITERS_SHIFT);
> +
> + pthread_cleanup_push (__sem_wait_cleanup, sem);
> +
> + /* Wait for a token to be available. Retry until we can grab one. */
> + for (;;)
> + {
> + /* If there is no token available, sleep until there is. */
> + if ((d & SEM_VALUE_MASK) == 0)
> + {
> + err = do_futex_wait (sem, abstime);
> + /* A futex return value of 0 or EAGAIN is due to a real or spurious
> + wake-up, or due to a change in the number of tokens. We retry in
> + these cases.
> + If we timed out, forward this to the caller.
> + EINTR could be either due to being interrupted by a signal, or
> + due to a spurious wake-up. Thus, we cannot distinguish between
> + both, and are not allowed to return EINTR to the caller but have
> + to retry; this is because we may not have been interrupted by a
> + signal. However, if we assume that only signals cause a futex
> + return of EINTR, we forward EINTR to the caller.
> +
> + Retrying on EINTR is technically always allowed because to
> + reliably interrupt sem_wait with a signal, the signal handler
> + must call sem_post (which is AS-Safe). In executions where the
> + signal handler does not do that, the implementation can correctly
> + claim that sem_wait hadn't actually started to execute yet, and
> + thus the signal never actually interrupted sem_wait. We make no
> + timing guarantees, so the program can never observe that sem_wait
> + actually did start to execute. Thus, in a correct program, we
> + can expect a signal that wanted to interrupt the sem_wait to have
> + provided a token, and can just try to grab this token if
> + futex_wait returns EINTR. */
> + if (err == ETIMEDOUT ||
> + (err == EINTR && sem_assume_only_signals_cause_futex_EINTR))
> + {
> + __set_errno (err);
> + err = -1;
> + /* Stop being registered as a waiter. */
> + atomic_fetch_add_relaxed (&sem->data,
> + -(1UL << SEM_NWAITERS_SHIFT));
> + break;
> + }
> + /* Relaxed MO is sufficient; see below. */
> + d = atomic_load_relaxed (&sem->data);
> + }
> + else
> + {
> + /* Try to grab both a token and stop being a waiter. We need
> + acquire MO so this synchronizes with all token providers (i.e.,
> + the RMW operation we read from or all those before it in
> + modification order; also see sem_post). On the failure path,
> + relaxed MO is sufficient because we only eventually need the
> + up-to-date value; the futex_wait or the CAS perform the real
> + work. */
> + if (atomic_compare_exchange_weak_acquire (&sem->data,
> + &d, d - 1 - (1UL << SEM_NWAITERS_SHIFT)))
Doesn't that underflow the nwaiters field if there was a token
available on entry? In that case the value of d is the semaphore data
_before_ incrementing the nwaiters field as fetched at the start of the
function. I think the initial load should look like this:
+ unsigned long d = atomic_fetch_add_relaxed (&sem->data,
+ 1UL << SEM_NWAITERS_SHIFT) + (1UL << SEM_NWAITERS_SHIFT);
--
Andreas Schwab, SUSE Labs, schwab@suse.de
GPG Key fingerprint = 0196 BAD8 1CE9 1970 F4BE 1748 E4D4 88E3 0EEA B9D7
"And now for something completely different."
More information about the Libc-alpha
mailing list