BUG/MAJOR: plock: fix major bug in pl_take_w() introduced with EBO

When EBO was brought to pl_take_w() by plock commit 60d750d ("plock: use
EBO when waiting for readers to leave in take_w() and stow()"), a mistake
was made: the mask against which the current value of the lock is tested
excludes the first reader like in stow(), but it must not because it was
just obtained via an ldadd() which means that it doesn't count itself.

The problem this causes is that if there is exactly one reader when a
writer grabs the lock, the writer will not wait for it to leave before
starting its operations.

The solution consists in checking for any reader in the IF. However the
mask passed to pl_wait_unlock_*() must still exclude the lowest bit as
it's verified after a subsequent load.

Kudos to Remi Tricot-Le Breton for reporting and bisecting this issue
with a reproducer.

No backport is needed since this was brought in 2.9-dev3 with commit
8178a5211 ("MAJOR: threads/plock: update the embedded library again").
The code is now on par with plock commit ada70fe.
This commit is contained in:
Willy Tarreau 2023-10-03 08:23:33 +02:00
parent c21ec3b735
commit 7c69c9b51f

View File

@ -553,7 +553,7 @@ static unsigned int pl_wait_new_int(const unsigned int *lock, const unsigned int
__pl_r = pl_wait_unlock_long(__lk_r, __msk_r); \
} \
/* wait for all other readers to leave */ \
if (__builtin_expect(__pl_r & (PLOCK64_RL_ANY & ~PLOCK64_RL_1), 0)) \
if (__builtin_expect(__pl_r & PLOCK64_RL_ANY, 0)) \
__pl_r = pl_wait_unlock_long(__lk_r, (PLOCK64_RL_ANY & ~PLOCK64_RL_1)) - __set_r; \
pl_barrier(); \
0; \
@ -570,7 +570,7 @@ static unsigned int pl_wait_new_int(const unsigned int *lock, const unsigned int
__pl_r = pl_wait_unlock_int(__lk_r, __msk_r); \
} \
/* wait for all other readers to leave */ \
if (__builtin_expect(__pl_r & (PLOCK32_RL_ANY & ~PLOCK32_RL_1), 0)) \
if (__builtin_expect(__pl_r & PLOCK32_RL_ANY, 0)) \
__pl_r = pl_wait_unlock_int(__lk_r, (PLOCK32_RL_ANY & ~PLOCK32_RL_1)) - __set_r; \
pl_barrier(); \
0; \