[m-rev.] for review: Avoid sem_post/sem_wait race in glibc.

Peter Wang novalazy at gmail.com
Wed Jul 5 17:05:38 AEST 2017


I'm not sure this workaround is necessary. It should be a hard bug to trigger.
glibc 2.21 is only a couple of years old but hopefully the distributions
sticking with older glibcs will have backported the fix.

---

glibc prior to 2.21 is susceptible to a race between sem_post/sem_wait
https://sourceware.org/bugzilla/show_bug.cgi?id=12674

(I have not personally encountered this bug, and I hope never to.)

library/thread.m:
    Use a pthread mutex and condition variable instead of a pthread
    semaphore to synchronise the newly spawned thread with its parent in
    ML_create_exclusive_thread.
---
 library/thread.m | 63 +++++++++++++++++++++++++++++++++++++++-----------------
 1 file changed, 44 insertions(+), 19 deletions(-)

diff --git a/library/thread.m b/library/thread.m
index 4cfec3a..8b28447 100644
--- a/library/thread.m
+++ b/library/thread.m
@@ -520,12 +520,20 @@ INIT mercury_sys_init_thread_modules
 
   typedef struct ML_ThreadWrapperArgs ML_ThreadWrapperArgs;
   struct ML_ThreadWrapperArgs {
-        MercurySem          sem;
+        MercuryLock         mutex;
+        MercuryCond         cond;
         MR_Word             goal;
         MR_ThreadLocalMuts  *thread_local_mutables;
-        MR_bool             thread_started;
+        MR_Integer          thread_state;
         MR_String           thread_id;
   };
+
+  enum {
+        ML_THREAD_NOT_READY,
+        ML_THREAD_READY,
+        ML_THREAD_START_ERROR
+  };
+
 #endif /* MR_THREAD_SAFE */
 ").
 
@@ -540,11 +548,18 @@ INIT mercury_sys_init_thread_modules
 
     ML_incr_thread_barrier_count();
 
-    MR_sem_init(&args.sem, 0);
+    /*
+    ** The obvious synchronisation object to use here is a semaphore but
+    ** glibc < 2.21 had a bug which could result in sem_post reading from a
+    ** semaphore after (in another thread) sem_wait returns and destroys the
+    ** semaphore. <https://sourceware.org/bugzilla/show_bug.cgi?id=12674>
+    */
+    pthread_mutex_init(&args.mutex, MR_MUTEX_ATTR);
+    pthread_cond_init(&args.cond, MR_COND_ATTR);
     args.goal = goal;
     args.thread_local_mutables =
         MR_clone_thread_local_mutables(MR_THREAD_LOCAL_MUTABLES);
-    args.thread_started = MR_FALSE;
+    args.thread_state = ML_THREAD_NOT_READY;
     args.thread_id = NULL;
 
     pthread_attr_init(&attrs);
@@ -553,21 +568,26 @@ INIT mercury_sys_init_thread_modules
     pthread_attr_destroy(&attrs);
 
     if (thread_err == 0) {
-        int sem_err;
-
-        do {
-            sem_err = MR_SEM_WAIT(&args.sem, ""ML_create_exclusive_thread"");
-        } while (sem_err == -1 && MR_SEM_IS_EINTR(errno));
-
-        if (sem_err != 0) {
-            MR_fatal_error(
-                ""ML_create_exclusive_thread: MR_SEM_WAIT error %d"", errno);
+        MR_LOCK(&args.mutex, ""ML_create_exclusive_thread"");
+        while (args.thread_state == ML_THREAD_NOT_READY) {
+            int cond_err;
+
+            cond_err = MR_COND_WAIT(&args.cond, &args.mutex,
+                ""ML_create_exclusive_thread"");
+            /* EINTR should not be possible but it has happened before. */
+            if (cond_err != 0 && errno != EINTR) {
+                MR_fatal_error(
+                    ""ML_create_exclusive_thread: MR_COND_WAIT error %d"",
+                    errno);
+            }
         }
+        MR_UNLOCK(&args.mutex, ""ML_create_exclusive_thread"");
     }
 
-    MR_sem_destroy(&args.sem);
+    pthread_cond_destroy(&args.cond);
+    pthread_mutex_destroy(&args.mutex);
 
-    if (args.thread_started) {
+    if (args.thread_state == ML_THREAD_READY) {
         *thread_id = args.thread_id;
         return MR_TRUE;
     }
@@ -583,8 +603,10 @@ INIT mercury_sys_init_thread_modules
     MR_String               thread_id;
 
     if (MR_init_thread(MR_use_now) == MR_FALSE) {
-        args->thread_started = MR_FALSE;
-        MR_SEM_POST(&args->sem, ""ML_exclusive_thread_wrapper"");
+        MR_LOCK(&args->mutex, ""ML_exclusive_thread_wrapper"");
+        args->thread_state = ML_THREAD_START_ERROR;
+        MR_COND_SIGNAL(&args->cond, ""ML_exclusive_thread_wrapper"");
+        MR_UNLOCK(&args->mutex, ""ML_exclusive_thread_wrapper"");
         return NULL;
     }
 
@@ -605,9 +627,12 @@ INIT mercury_sys_init_thread_modules
     ** Take a copy of the goal before telling the parent we are ready.
     */
     goal = args->goal;
-    args->thread_started = MR_TRUE;
+
+    MR_LOCK(&args->mutex, ""ML_exclusive_thread_wrapper"");
+    args->thread_state = ML_THREAD_READY;
     args->thread_id = thread_id;
-    MR_SEM_POST(&args->sem, ""ML_exclusive_thread_wrapper"");
+    MR_COND_SIGNAL(&args->cond, ""ML_exclusive_thread_wrapper"");
+    MR_UNLOCK(&args->mutex, ""ML_exclusive_thread_wrapper"");
 
     ML_call_back_to_mercury_cc_multi(goal, thread_id);
 
-- 
2.9.0



More information about the reviews mailing list