nptl: rename __condvar_quiesce_and_switch_g1

This function no longer waits for threads to leave g1, so rename it to
__condvar_switch_g1

Signed-off-by: Malte Skarupke <malteskarupke@fastmail.fm>
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
This commit is contained in:
Malte Skarupke 2024-12-04 08:04:54 -05:00 committed by Carlos O'Donell
parent ee6c14ed59
commit 4b79e27a50
4 changed files with 26 additions and 30 deletions

View file

@ -60,7 +60,7 @@ ___pthread_cond_broadcast (pthread_cond_t *cond)
cond->__data.__g_size[g1] << 1); cond->__data.__g_size[g1] << 1);
cond->__data.__g_size[g1] = 0; cond->__data.__g_size[g1] = 0;
/* We need to wake G1 waiters before we quiesce G1 below. */ /* We need to wake G1 waiters before we switch G1 below. */
/* TODO Only set it if there are indeed futex waiters. We could /* TODO Only set it if there are indeed futex waiters. We could
also try to move this out of the critical section in cases when also try to move this out of the critical section in cases when
G2 is empty (and we don't need to quiesce). */ G2 is empty (and we don't need to quiesce). */
@ -69,7 +69,7 @@ ___pthread_cond_broadcast (pthread_cond_t *cond)
/* G1 is complete. Step (2) is next unless there are no waiters in G2, in /* G1 is complete. Step (2) is next unless there are no waiters in G2, in
which case we can stop. */ which case we can stop. */
if (__condvar_quiesce_and_switch_g1 (cond, wseq, &g1, private)) if (__condvar_switch_g1 (cond, wseq, &g1, private))
{ {
/* Step (3): Send signals to all waiters in the old G2 / new G1. */ /* Step (3): Send signals to all waiters in the old G2 / new G1. */
atomic_fetch_add_relaxed (cond->__data.__g_signals + g1, atomic_fetch_add_relaxed (cond->__data.__g_signals + g1,

View file

@ -189,16 +189,15 @@ __condvar_get_private (int flags)
return FUTEX_SHARED; return FUTEX_SHARED;
} }
/* This closes G1 (whose index is in G1INDEX), waits for all futex waiters to /* This closes G1 (whose index is in G1INDEX), converts G1 into a fresh G2,
leave G1, converts G1 into a fresh G2, and then switches group roles so that and then switches group roles so that the former G2 becomes the new G1
the former G2 becomes the new G1 ending at the current __wseq value when we ending at the current __wseq value when we eventually make the switch
eventually make the switch (WSEQ is just an observation of __wseq by the (WSEQ is just an observation of __wseq by the signaler).
signaler).
If G2 is empty, it will not switch groups because then it would create an If G2 is empty, it will not switch groups because then it would create an
empty G1 which would require switching groups again on the next signal. empty G1 which would require switching groups again on the next signal.
Returns false iff groups were not switched because G2 was empty. */ Returns false iff groups were not switched because G2 was empty. */
static bool __attribute__ ((unused)) static bool __attribute__ ((unused))
__condvar_quiesce_and_switch_g1 (pthread_cond_t *cond, uint64_t wseq, __condvar_switch_g1 (pthread_cond_t *cond, uint64_t wseq,
unsigned int *g1index, int private) unsigned int *g1index, int private)
{ {
unsigned int g1 = *g1index; unsigned int g1 = *g1index;
@ -214,8 +213,7 @@ __condvar_quiesce_and_switch_g1 (pthread_cond_t *cond, uint64_t wseq,
+ cond->__data.__g_size[g1 ^ 1]) == 0) + cond->__data.__g_size[g1 ^ 1]) == 0)
return false; return false;
/* Now try to close and quiesce G1. We have to consider the following kinds /* We have to consider the following kinds of waiters:
of waiters:
* Waiters from less recent groups than G1 are not affected because * Waiters from less recent groups than G1 are not affected because
nothing will change for them apart from __g1_start getting larger. nothing will change for them apart from __g1_start getting larger.
* New waiters arriving concurrently with the group switching will all go * New waiters arriving concurrently with the group switching will all go
@ -223,12 +221,12 @@ __condvar_quiesce_and_switch_g1 (pthread_cond_t *cond, uint64_t wseq,
are not affected. are not affected.
* Waiters in G1 have already received a signal and been woken. */ * Waiters in G1 have already received a signal and been woken. */
/* Update __g1_start, which finishes closing this group. The value we add /* Update __g1_start, which closes this group. The value we add will never
will never be negative because old_orig_size can only be zero when we be negative because old_orig_size can only be zero when we switch groups
switch groups the first time after a condvar was initialized, in which the first time after a condvar was initialized, in which case G1 will be
case G1 will be at index 1 and we will add a value of 1. at index 1 and we will add a value of 1. Relaxed MO is fine because the
Relaxed MO is fine because the change comes with no additional change comes with no additional constraints that others would have to
constraints that others would have to observe. */ observe. */
__condvar_add_g1_start_relaxed (cond, __condvar_add_g1_start_relaxed (cond,
(old_orig_size << 1) + (g1 == 1 ? 1 : - 1)); (old_orig_size << 1) + (g1 == 1 ? 1 : - 1));

View file

@ -69,18 +69,17 @@ ___pthread_cond_signal (pthread_cond_t *cond)
bool do_futex_wake = false; bool do_futex_wake = false;
/* If G1 is still receiving signals, we put the signal there. If not, we /* If G1 is still receiving signals, we put the signal there. If not, we
check if G2 has waiters, and if so, quiesce and switch G1 to the former check if G2 has waiters, and if so, switch G1 to the former G2; if this
G2; if this results in a new G1 with waiters (G2 might have cancellations results in a new G1 with waiters (G2 might have cancellations already,
already, see __condvar_quiesce_and_switch_g1), we put the signal in the see __condvar_switch_g1), we put the signal in the new G1. */
new G1. */
if ((cond->__data.__g_size[g1] != 0) if ((cond->__data.__g_size[g1] != 0)
|| __condvar_quiesce_and_switch_g1 (cond, wseq, &g1, private)) || __condvar_switch_g1 (cond, wseq, &g1, private))
{ {
/* Add a signal. Relaxed MO is fine because signaling does not need to /* Add a signal. Relaxed MO is fine because signaling does not need to
establish a happens-before relation (see above). We do not mask the establish a happens-before relation (see above). We do not mask the
release-MO store when initializing a group in release-MO store when initializing a group in __condvar_switch_g1
__condvar_quiesce_and_switch_g1 because we use an atomic because we use an atomic read-modify-write and thus extend that
read-modify-write and thus extend that store's release sequence. */ store's release sequence. */
atomic_fetch_add_relaxed (cond->__data.__g_signals + g1, 2); atomic_fetch_add_relaxed (cond->__data.__g_signals + g1, 2);
cond->__data.__g_size[g1]--; cond->__data.__g_size[g1]--;
/* TODO Only set it if there are indeed futex waiters. */ /* TODO Only set it if there are indeed futex waiters. */

View file

@ -354,8 +354,7 @@ __pthread_cond_wait_common (pthread_cond_t *cond, pthread_mutex_t *mutex,
because we do not need to establish any happens-before relation with because we do not need to establish any happens-before relation with
signalers (see __pthread_cond_signal); modification order alone signalers (see __pthread_cond_signal); modification order alone
establishes a total order of waiters/signals. We do need acquire MO establishes a total order of waiters/signals. We do need acquire MO
to synchronize with group reinitialization in to synchronize with group reinitialization in __condvar_switch_g1. */
__condvar_quiesce_and_switch_g1. */
uint64_t wseq = __condvar_fetch_add_wseq_acquire (cond, 2); uint64_t wseq = __condvar_fetch_add_wseq_acquire (cond, 2);
/* Find our group's index. We always go into what was G2 when we acquired /* Find our group's index. We always go into what was G2 when we acquired
our position. */ our position. */
@ -387,9 +386,9 @@ __pthread_cond_wait_common (pthread_cond_t *cond, pthread_mutex_t *mutex,
{ {
/* Now wait until a signal is available in our group or it is closed. /* Now wait until a signal is available in our group or it is closed.
Acquire MO so that if we observe (signals == lowseq) after group Acquire MO so that if we observe (signals == lowseq) after group
switching in __condvar_quiesce_and_switch_g1, we synchronize with that switching in __condvar_switch_g1, we synchronize with that store and
store and will see the prior update of __g1_start done while switching will see the prior update of __g1_start done while switching groups
groups too. */ too. */
unsigned int signals = atomic_load_acquire (cond->__data.__g_signals + g); unsigned int signals = atomic_load_acquire (cond->__data.__g_signals + g);
uint64_t g1_start = __condvar_load_g1_start_relaxed (cond); uint64_t g1_start = __condvar_load_g1_start_relaxed (cond);
unsigned int lowseq = (g1_start & 1) == g ? signals : g1_start & ~1U; unsigned int lowseq = (g1_start & 1) == g ? signals : g1_start & ~1U;