]> granicus.if.org Git - gc/commitdiff
Fix concurrent bitmap update in GC_dirty
authorIvan Maidanski <ivmai@mail.ru>
Sat, 22 Sep 2018 12:59:42 +0000 (15:59 +0300)
committerIvan Maidanski <ivmai@mail.ru>
Tue, 2 Oct 2018 08:12:25 +0000 (11:12 +0300)
(back-port of commit 26e9e59e6 from 'release-7_6')

Issue #235 (bdwgc).

* darwin_stop_world.c [!GC_NO_THREADS_DISCOVERY]
(GC_suspend_thread_list): Call GC_acquire_dirty_lock and
GC_release_dirty_lock around thread_suspend().
* darwin_stop_world.c (GC_stop_world): Likewise.
* include/private/gc_priv.h [THREADS] (GC_acquire_dirty_lock,
GC_release_dirty_lock): Define new macro; move comment from
win32_threads.c.
* include/private/gc_priv.h [THREADS && (MANUAL_VDB || MPROTECT_VDB)]
(GC_fault_handler_lock): Declare global variable (even if not
MPROTECT_VDB).
* os_dep.c [MANUAL_VDB] (async_set_pht_entry_from_index): Define as
for MPROTECT_VDB case; reformat comments.
* os_dep.c [MANUAL_VDB && AO_HAVE_test_and_set_acquire]
(GC_fault_handler_lock): Likewise.
* os_dep.c [THREADS && AO_HAVE_test_and_set_acquire]
(async_set_pht_entry_from_index): Use GC_acquire_dirty_lock and
GC_release_dirty_lock; remove wrong comment.
* pthread_stop_world.c [GC_ENABLE_SUSPEND_THREAD && MANUAL_VDB]
(GC_suspend_thread): Call GC_acquire_dirty_lock and
GC_release_dirty_lock around a block of pthread_kill() and sem_wait().
* pthread_stop_world.c [GC_OPENBSD_UTHREADS] (GC_suspend_all): Call
GC_acquire_dirty_lock and GC_release_dirty_lock around
pthread_suspend_np().
* pthread_stop_world.c [NACL && MANUAL_VDB] (GC_suspend_all):
Call GC_acquire_dirty_lock() and GC_release_dirty_lock() around the
code which ensures parking of threads.
* pthread_stop_world.c [!GC_OPENBSD_UTHREADS] (GC_suspend_all): Add
comment.
* pthread_stop_world.c [!GC_OPENBSD_UTHREADS && !NACL && MANUAL_VDB]
(GC_stop_world): Call GC_acquire_dirty_lock and
GC_release_dirty_lock around a block of GC_suspend_all() and
suspend_restart_barrier(); add comment.
* pthread_support.c [CAN_HANDLE_FORK] (fork_prepare_proc): Call
GC_acquire_dirty_lock().
* pthread_support.c [CAN_HANDLE_FORK] (fork_parent_proc,
fork_child_proc): Call GC_release_dirty_lock().
* win32_threads.c (GC_suspend): Use GC_acquire_dirty_lock and
GC_release_dirty_lock (regardless of MPROTECT_VDB).

darwin_stop_world.c
include/private/gc_priv.h
os_dep.c
pthread_stop_world.c
pthread_support.c
win32_threads.c

index ac4b369dda67699d28e8e16c2f43d5eafdf9cd47..0a82f48ac0081531dbfb3c8ede19f3b65f8f4d26 100644 (file)
@@ -423,9 +423,11 @@ STATIC GC_bool GC_suspend_thread_list(thread_act_array_t act_list, int count,
 #   endif
     /* Unconditionally suspend the thread.  It will do no     */
     /* harm if it is already suspended by the client logic.   */
+    GC_acquire_dirty_lock();
     do {
       kern_result = thread_suspend(thread);
     } while (kern_result == KERN_ABORTED);
+    GC_release_dirty_lock();
     if (kern_result != KERN_SUCCESS) {
       /* The thread may have quit since the thread_threads() call we  */
       /* mark already suspended so it's not dealt with anymore later. */
@@ -521,10 +523,11 @@ GC_INNER void GC_stop_world(void)
       for (p = GC_threads[i]; p != NULL; p = p->next) {
         if ((p->flags & FINISHED) == 0 && !p->thread_blocked &&
              p->stop_info.mach_thread != my_thread) {
-
+          GC_acquire_dirty_lock();
           do {
             kern_result = thread_suspend(p->stop_info.mach_thread);
           } while (kern_result == KERN_ABORTED);
+          GC_release_dirty_lock();
           if (kern_result != KERN_SUCCESS)
             ABORT("thread_suspend failed");
         }
index 1cd1fb4b16461da6f09b817580b10615ecaf7102..fd246649d83ca5df848ef8485cc872d42d83e24a 100644 (file)
@@ -2239,7 +2239,17 @@ GC_EXTERN signed_word GC_bytes_found;
 
 #   endif
 # endif
-# ifdef MPROTECT_VDB
+# if !defined(MANUAL_VDB) && !defined(MPROTECT_VDB)
+#   define GC_acquire_dirty_lock() (void)0
+#   define GC_release_dirty_lock() (void)0
+# else
+    /* Acquire the spin lock we use to update dirty bits.       */
+    /* Threads should not get stopped holding it.  But we may   */
+    /* acquire and release it during GC_remove_protection call. */
+#   define GC_acquire_dirty_lock() \
+        do { /* empty */ \
+        } while (AO_test_and_set_acquire(&GC_fault_handler_lock) == AO_TS_SET)
+#   define GC_release_dirty_lock() AO_CLEAR(&GC_fault_handler_lock)
     GC_EXTERN volatile AO_TS_t GC_fault_handler_lock;
                                         /* defined in os_dep.c */
 # endif
index e475d3a9442674d30d4e6760a0adcaa556890a85..ee904fa43cb2522d5966dda179abb27c404abd27 100644 (file)
--- a/os_dep.c
+++ b/os_dep.c
@@ -2863,6 +2863,55 @@ GC_API GC_push_other_roots_proc GC_CALL GC_get_push_other_roots(void)
                                      GC_bool is_ptrfree GC_ATTR_UNUSED) {}
 #endif /* DEFAULT_VDB */
 
+#if defined(MANUAL_VDB) || defined(MPROTECT_VDB)
+# ifndef THREADS
+#   define async_set_pht_entry_from_index(db, index) \
+                        set_pht_entry_from_index(db, index)
+# elif defined(AO_HAVE_test_and_set_acquire)
+    /* We need to lock around the bitmap update (in the write fault     */
+    /* handler or GC_dirty) in order to avoid the risk of losing a bit. */
+    /* We do this with a test-and-set spin lock if possible.            */
+    GC_INNER volatile AO_TS_t GC_fault_handler_lock = AO_TS_INITIALIZER;
+
+    static void async_set_pht_entry_from_index(volatile page_hash_table db,
+                                               size_t index)
+    {
+      GC_acquire_dirty_lock();
+      set_pht_entry_from_index(db, index);
+      GC_release_dirty_lock();
+    }
+# else
+# error No test_and_set operation: Introduces a race.
+  /* THIS WOULD BE INCORRECT!                                           */
+  /* The dirty bit vector may be temporarily wrong,                     */
+  /* just before we notice the conflict and correct it. We may end up   */
+  /* looking at it while it's wrong.  But this requires contention      */
+  /* exactly when a GC is triggered, which seems far less likely to     */
+  /* fail than the old code, which had no reported failures.  Thus we   */
+  /* leave it this way while we think of something better, or support   */
+  /* GC_test_and_set on the remaining platforms.                        */
+  static int * volatile currently_updating = 0;
+  static void async_set_pht_entry_from_index(volatile page_hash_table db,
+                                             size_t index)
+  {
+    int update_dummy;
+    currently_updating = &update_dummy;
+    set_pht_entry_from_index(db, index);
+    /* If we get contention in the 10 or so instruction window here,    */
+    /* and we get stopped by a GC between the two updates, we lose!     */
+    if (currently_updating != &update_dummy) {
+        set_pht_entry_from_index_safe(db, index);
+        /* We claim that if two threads concurrently try to update the  */
+        /* dirty bit vector, the first one to execute UPDATE_START      */
+        /* will see it changed when UPDATE_END is executed.  (Note that */
+        /* &update_dummy must differ in two distinct threads.)  It      */
+        /* will then execute set_pht_entry_from_index_safe, thus        */
+        /* returning us to a safe state, though not soon enough.        */
+    }
+  }
+# endif /* THREADS && !AO_HAVE_test_and_set_acquire */
+#endif /* MANUAL_VDB || MPROTECT_VDB */
+
 #ifdef MANUAL_VDB
   /* Initialize virtual dirty bit implementation.       */
   GC_INNER GC_bool GC_dirty_init(void)
@@ -2891,9 +2940,6 @@ GC_API GC_push_other_roots_proc GC_CALL GC_get_push_other_roots(void)
     return(HDR(h) == 0 || get_pht_entry_from_index(GC_grungy_pages, index));
   }
 
-# define async_set_pht_entry_from_index(db, index) \
-                        set_pht_entry_from_index(db, index) /* for now */
-
   /* Mark the page containing p as dirty.  Logically, this dirties the  */
   /* entire object.                                                     */
   GC_INNER void GC_dirty_inner(const void *p)
@@ -3023,69 +3069,10 @@ GC_API GC_push_other_roots_proc GC_CALL GC_get_push_other_roots(void)
 #   endif
     STATIC GC_bool GC_old_segv_handler_used_si = FALSE;
 # endif /* !MSWIN32 */
-#endif /* !DARWIN */
 
-#if defined(THREADS)
-/* We need to lock around the bitmap update in the write fault handler  */
-/* in order to avoid the risk of losing a bit.  We do this with a       */
-/* test-and-set spin lock if we know how to do that.  Otherwise we      */
-/* check whether we are already in the handler and use the dumb but     */
-/* safe fallback algorithm of setting all bits in the word.             */
-/* Contention should be very rare, so we do the minimum to handle it    */
-/* correctly.                                                           */
-#ifdef AO_HAVE_test_and_set_acquire
-  GC_INNER volatile AO_TS_t GC_fault_handler_lock = AO_TS_INITIALIZER;
-  static void async_set_pht_entry_from_index(volatile page_hash_table db,
-                                             size_t index)
-  {
-    while (AO_test_and_set_acquire(&GC_fault_handler_lock) == AO_TS_SET) {
-      /* empty */
-    }
-    /* Could also revert to set_pht_entry_from_index_safe if initial    */
-    /* GC_test_and_set fails.                                           */
-    set_pht_entry_from_index(db, index);
-    AO_CLEAR(&GC_fault_handler_lock);
-  }
-#else /* !AO_HAVE_test_and_set_acquire */
-# error No test_and_set operation: Introduces a race.
-  /* THIS WOULD BE INCORRECT!                                           */
-  /* The dirty bit vector may be temporarily wrong,                     */
-  /* just before we notice the conflict and correct it. We may end up   */
-  /* looking at it while it's wrong.  But this requires contention      */
-  /* exactly when a GC is triggered, which seems far less likely to     */
-  /* fail than the old code, which had no reported failures.  Thus we   */
-  /* leave it this way while we think of something better, or support   */
-  /* GC_test_and_set on the remaining platforms.                        */
-  static int * volatile currently_updating = 0;
-  static void async_set_pht_entry_from_index(volatile page_hash_table db,
-                                             size_t index)
-  {
-    int update_dummy;
-    currently_updating = &update_dummy;
-    set_pht_entry_from_index(db, index);
-    /* If we get contention in the 10 or so instruction window here,    */
-    /* and we get stopped by a GC between the two updates, we lose!     */
-    if (currently_updating != &update_dummy) {
-        set_pht_entry_from_index_safe(db, index);
-        /* We claim that if two threads concurrently try to update the  */
-        /* dirty bit vector, the first one to execute UPDATE_START      */
-        /* will see it changed when UPDATE_END is executed.  (Note that */
-        /* &update_dummy must differ in two distinct threads.)  It      */
-        /* will then execute set_pht_entry_from_index_safe, thus        */
-        /* returning us to a safe state, though not soon enough.        */
-    }
-  }
-#endif /* !AO_HAVE_test_and_set_acquire */
-#else /* !THREADS */
-# define async_set_pht_entry_from_index(db, index) \
-                        set_pht_entry_from_index(db, index)
-#endif /* !THREADS */
-
-#ifdef CHECKSUMS
-  void GC_record_fault(struct hblk * h); /* from checksums.c */
-#endif
-
-#ifndef DARWIN
+# ifdef CHECKSUMS
+    void GC_record_fault(struct hblk * h); /* from checksums.c */
+# endif
 
 # if !defined(MSWIN32) && !defined(MSWINCE)
 #   include <errno.h>
index daa00f878c360e09696c8c9ce74dc4fa827ebedc..7a542a6ab7907660bb9f3f2bd676424b40ea4c97 100644 (file)
@@ -497,13 +497,21 @@ STATIC int GC_suspend_all(void)
 #           ifdef GC_OPENBSD_UTHREADS
               {
                 stack_t stack;
+
+                GC_acquire_dirty_lock();
                 if (pthread_suspend_np(p -> id) != 0)
                   ABORT("pthread_suspend_np failed");
+                GC_release_dirty_lock();
                 if (pthread_stackseg_np(p->id, &stack))
                   ABORT("pthread_stackseg_np failed");
                 p -> stop_info.stack_ptr = (ptr_t)stack.ss_sp - stack.ss_size;
               }
 #           else
+              /* The synchronization between GC_dirty (based on         */
+              /* test-and-set) and the signal-based thread suspension   */
+              /* is performed in GC_stop_world because                  */
+              /* GC_release_dirty_lock cannot be called before          */
+              /* acknowledging the thread is really suspended.          */
 #             ifndef PLATFORM_ANDROID
                 result = pthread_kill(p -> id, GC_sig_suspend);
 #             else
@@ -543,6 +551,9 @@ STATIC int GC_suspend_all(void)
       GC_stopping_pid = getpid();
 #   endif
 
+#   ifdef MANUAL_VDB
+      GC_acquire_dirty_lock();
+#   endif
     while (1) {
       int num_threads_parked = 0;
       struct timespec ts;
@@ -576,6 +587,9 @@ STATIC int GC_suspend_all(void)
         num_sleeps = 0;
       }
     }
+#   ifdef MANUAL_VDB
+      GC_release_dirty_lock();
+#   endif
 # endif /* NACL */
   return n_live_threads;
 }
@@ -609,6 +623,12 @@ GC_INNER void GC_stop_world(void)
 # else
     AO_store(&GC_stop_count, GC_stop_count+1);
         /* Only concurrent reads are possible. */
+#   ifdef MANUAL_VDB
+      GC_acquire_dirty_lock();
+      /* The write fault handler cannot be called if manual VDB         */
+      /* (thus double-locking should not occur in                       */
+      /* async_set_pht_entry_from_index based on test-and-set).         */
+#   endif
     AO_store_release(&GC_world_is_stopped, TRUE);
     n_live_threads = GC_suspend_all();
 
@@ -650,6 +670,9 @@ GC_INNER void GC_stop_world(void)
           ABORT("sem_wait for handler failed");
         }
     }
+#   ifdef MANUAL_VDB
+      GC_release_dirty_lock(); /* cannot be done in GC_suspend_all */
+#   endif
 # endif
 
 # ifdef PARALLEL_MARK
index dbc1ebbfb59302db5b0a2da77d25fe8e3fc89603..42c3c40fafe73664b53a811e0c269dc2a570a413 100644 (file)
@@ -989,11 +989,13 @@ static void fork_prepare_proc(void)
         if (GC_parallel)
           GC_acquire_mark_lock();
 #     endif
+      GC_acquire_dirty_lock();
 }
 
 /* Called in parent after a fork() (even if the latter failed). */
 static void fork_parent_proc(void)
 {
+    GC_release_dirty_lock();
 #   if defined(PARALLEL_MARK)
       if (GC_parallel)
         GC_release_mark_lock();
@@ -1005,11 +1007,12 @@ static void fork_parent_proc(void)
 /* Called in child after a fork()       */
 static void fork_child_proc(void)
 {
-    /* Clean up the thread table, so that just our thread is left. */
+    GC_release_dirty_lock();
 #   if defined(PARALLEL_MARK)
       if (GC_parallel)
         GC_release_mark_lock();
 #   endif
+    /* Clean up the thread table, so that just our thread is left.      */
     GC_remove_all_threads_but_me();
 #   ifdef PARALLEL_MARK
       /* Turn off parallel marking in the child, since we are probably  */
index 91d1e8038623b9a084024fe9e8852ac49057d688..59c34181d520f67da52d767fda6e982b3dda83a9 100644 (file)
@@ -1157,15 +1157,7 @@ STATIC void GC_suspend(GC_thread t)
       return;
     }
 # endif
-# if defined(MPROTECT_VDB)
-    /* Acquire the spin lock we use to update dirty bits.       */
-    /* Threads shouldn't get stopped holding it.  But we may    */
-    /* acquire and release it in the UNPROTECT_THREAD call.     */
-    while (AO_test_and_set_acquire(&GC_fault_handler_lock) == AO_TS_SET) {
-      /* empty */
-    }
-# endif
-
+  GC_acquire_dirty_lock();
 # ifdef MSWINCE
     /* SuspendThread() will fail if thread is running kernel code.      */
     while (SuspendThread(THREAD_HANDLE(t)) == (DWORD)-1)
@@ -1175,9 +1167,7 @@ STATIC void GC_suspend(GC_thread t)
       ABORT("SuspendThread failed");
 # endif /* !MSWINCE */
   t -> suspended = (unsigned char)TRUE;
-# if defined(MPROTECT_VDB)
-    AO_CLEAR(&GC_fault_handler_lock);
-# endif
+  GC_release_dirty_lock();
 }
 
 #if defined(GC_ASSERTIONS) && !defined(CYGWIN32)