Fix sem_post race (bug 14532)
Torvald Riegel
triegel@redhat.com
Thu Sep 13 19:21:00 GMT 2012
On Wed, 2012-09-12 at 15:19 +0000, Joseph S. Myers wrote:
> On Tue, 11 Sep 2012, Torvald Riegel wrote:
>
> > > atomic_full_barrier ();
> >
> > Why do we have the full barrier after the rel barrier? Is it necessary?
Attached is a draft patch that documents the synchronization used to
build the semaphore. Please comment whether that is useful for
everyone, or whether we need more details, a different format, or
whatever. Perhaps we can use the semaphore as a template / example for
how concurrent code should be documented in glibc.
I tried to keep this short enough while still pointing out the
relationships with related code pieces. I also used the C11/C++11
memory model terminology, even though glibc's sync functions are named
slightly differently.
Also, atomic operations without an _acq or _rel suffix seem to always
default to _acq. Is this a guarantee (and thus should be documented)?
I'm wondering whether it would be better to use an explicit suffix in
the long-term.
> I wouldn't say we have it after the rel barrier. Apart from the rel
> barrier being a barrier before the compare-and-exchange operation, so the
> barriers are separated by that operation, the formatting of the code
> implies that the positioning of the barrier is logically "before the code
> to wake a waiter" rather than "after the code to update the semaphore
> value", and so the code to wake a waiter is where you should look for the
> reason for this barrier.
I got confused because I didn't see a counterpart to the full fence
anywhere; so far, I assumed that futex_wait does not imply a full membar
before the read from the futex variable. But the existing code relies
on this; is this guarantee commonly known to be true on all
architectures (e.g., is a syscall always like a full membar,
everywhere)?
Torvald
-------------- next part --------------
commit 8f909376fea5832ad49bb15756a7015d91a62e48
Author: Torvald Riegel <triegel@redhat.com>
Date: Thu Sep 13 21:00:10 2012 +0200
Document semaphore synchronization.
diff --git a/nptl/sysdeps/unix/sysv/linux/sem_post.c b/nptl/sysdeps/unix/sysv/linux/sem_post.c
index 67e8cc5..27ddd6e 100644
--- a/nptl/sysdeps/unix/sysv/linux/sem_post.c
+++ b/nptl/sysdeps/unix/sysv/linux/sem_post.c
@@ -30,6 +30,13 @@ __new_sem_post (sem_t *sem)
{
struct new_sem *isem = (struct new_sem *) sem;
+ /* Use a CAS loop to increment the semaphore's value. We need release
+ memory order (MO) on the CAS so that it synchronizes with the other CAS
+ used by threads waiting on the semaphore (which has acquire MO); this
+ ensures that everything we did before posting the semaphore happens
+ before the waiters can continue. We do not need additional acquire MO
+ here because we do not need additional happens-before guarantees based
+ on other threads' writes to the semaphore's value. */
__typeof (isem->value) cur;
do
{
@@ -42,6 +49,11 @@ __new_sem_post (sem_t *sem)
}
while (atomic_compare_and_exchange_bool_rel (&isem->value, cur + 1, cur));
+ /* This seq_cst fence is necessary for the Dekker-style synchronization used
+ here. This fence and the respective fence in__new_sem_wait() are totally
+ ordered, which in turn prevents cyclic reads-from/sequenced-before
+ dependencies between the store/load pairs; here, this pair is the prior
+ write to value by the CAS and the following read of nwaiters. */
atomic_full_barrier ();
if (isem->nwaiters > 0)
{
diff --git a/nptl/sysdeps/unix/sysv/linux/sem_timedwait.c b/nptl/sysdeps/unix/sysv/linux/sem_timedwait.c
index 36e0042..f703483 100644
--- a/nptl/sysdeps/unix/sysv/linux/sem_timedwait.c
+++ b/nptl/sysdeps/unix/sysv/linux/sem_timedwait.c
@@ -51,6 +51,7 @@ sem_timedwait (sem_t *sem, const struct timespec *abstime)
struct new_sem *isem = (struct new_sem *) sem;
int err;
+ /* See __new_sem_wait() for comments. */
if (atomic_decrement_if_positive (&isem->value) > 0)
return 0;
diff --git a/nptl/sysdeps/unix/sysv/linux/sem_trywait.c b/nptl/sysdeps/unix/sysv/linux/sem_trywait.c
index 517ae44..c1a523f 100644
--- a/nptl/sysdeps/unix/sysv/linux/sem_trywait.c
+++ b/nptl/sysdeps/unix/sysv/linux/sem_trywait.c
@@ -34,6 +34,7 @@ __new_sem_trywait (sem_t *sem)
if (*futex > 0)
{
+ /* See __new_sem_wait() for comments. */
val = atomic_decrement_if_positive (futex);
if (val > 0)
return 0;
diff --git a/nptl/sysdeps/unix/sysv/linux/sem_wait.c b/nptl/sysdeps/unix/sysv/linux/sem_wait.c
index ca50f4e..2274b70 100644
--- a/nptl/sysdeps/unix/sysv/linux/sem_wait.c
+++ b/nptl/sysdeps/unix/sysv/linux/sem_wait.c
@@ -57,9 +57,17 @@ __new_sem_wait (sem_t *sem)
struct new_sem *isem = (struct new_sem *) sem;
int err;
+ /* This decrement operation has acquire memory order so that everything
+ that happens before the posting of the semaphore happens before the code
+ that we return execution to. */
if (atomic_decrement_if_positive (&isem->value) > 0)
return 0;
+ /* This increment to nwaiters is part of the Dekker-style synchronization
+ we use here (see __new_sem_post). The seq_cst fence and the read of
+ isem->value are part of the futex_wait(). Incrementing nwaiters once
+ is sufficient because only waiting threads decrease nwaiters, and only
+ once. */
atomic_increment (&isem->nwaiters);
pthread_cleanup_push (__sem_wait_cleanup, isem);
More information about the Libc-alpha
mailing list