]> granicus.if.org Git - gc/commitdiff
Add manual POSIX fork handling support (Android)
authorIvan Maidanski <ivmai@mail.ru>
Wed, 21 Nov 2012 18:26:29 +0000 (22:26 +0400)
committerIvan Maidanski <ivmai@mail.ru>
Wed, 21 Nov 2012 18:31:30 +0000 (22:31 +0400)
* include/gc.h (GC_set_handle_fork): Update comment.
* include/gc.h (GC_atfork_prepare, GC_atfork_parent, GC_atfork_child):
New API proto.
* include/private/gc_priv.h (GC_handle_fork): Change type from GC_bool
to int (to hold a value of -1).
* misc.c (GC_handle_fork): Likewise.
* include/private/gc_priv.h (GC_handle_fork): Add comment.
* misc.c (GC_set_handle_fork): Likewise.
* include/private/gcconfig.h (CAN_HANDLE_FORK): Define also for HURD
and PLATFORM_ANDROID; do not define if HAVE_NO_FORK already defined.
* include/private/gcconfig.h (CAN_CALL_ATFORK): New macro (defined if
CAN_HANDLE_FORK but not HURD or PLATFORM_ANDROID).
* include/private/gcconfig.h (HAVE_NO_FORK): New macro (defined for
Win32, OS/2 and others).
* misc.c (GC_atfork_prepare, GC_atfork_parent, GC_atfork_child): New
API function definition (only if not CAN_HANDLE_FORK and not
HAVE_NO_FORK).
* misc.c (GC_handle_fork): Map all negative values of argument except
for -1 to a positive one stored to GC_handle_fork; call GC_init to
initialize GC_stderr before ABORT invocation (only if not
SMALL_CONFIG).
* pthread_support.c (GC_atfork_prepare, GC_atfork_parent,
GC_atfork_child): New API function definition (only if
CAN_HANDLE_FORK).
* win32_threads.c (GC_atfork_prepare, GC_atfork_parent,
GC_atfork_child): Likewise.
* pthread_support.c (GC_thr_init): No pthread_atfork call if not
CAN_CALL_ATFORK; adjust GC_handle_fork value if pthread_atfork
succeeds; do not about in case of pthread_atfork failure provided
GC_handle_fork is -1 (only if CAN_HANDLE_FORK).
* win32_threads.c (GC_thr_init): Likewise.
* tests/test.c (TEST_FORK_WITHOUT_ATFORK): Recognize new macro (do not
define NO_TEST_HANDLE_FORK in this case and set INIT_FORK_SUPPORT to
no-op).
* tests/test.c (INIT_FORK_SUPPORT): Define to GC_set_handle_fork(-1)
unless HANDLE_FORK, or NO_TEST_HANDLE_FORK or TEST_FORK_WITHOUT_ATFORK.
* tests/test.c (run_one_test): Surround fork() invocation with
GC_atfork_prepare, GC_atfork_parent, GC_atfork_child calls.
* win32_threads.c: Include unistd.h if CAN_CALL_ATFORK defined
(instead of CAN_HANDLE_FORK) to get pthread_atfork prototype.

include/gc.h
include/private/gc_priv.h
include/private/gcconfig.h
misc.c
pthread_support.c
tests/test.c
win32_threads.c

index c7c4f00b78b09091a13ec3cc491b20fe70d6836b..0a56dce19c8cf5b443478054c65ca40807465892 100644 (file)
@@ -365,12 +365,29 @@ GC_API int GC_CALL GC_get_pages_executable(void);
 
 /* Overrides the default handle-fork mode.  Non-zero value means GC     */
 /* should install proper pthread_atfork handlers.  Has effect only if   */
-/* called before GC_INIT.  Clients should invoke GC_set_handle_fork(1)  */
-/* only if going to use fork with GC functions called in the forked     */
-/* child.  (Note that such client and atfork handlers activities are    */
-/* not fully POSIX-compliant.)                                          */
+/* called before GC_INIT.  Clients should invoke GC_set_handle_fork     */
+/* with non-zero argument if going to use fork with GC functions called */
+/* in the forked child.  (Note that such client and atfork handlers     */
+/* activities are not fully POSIX-compliant.)  GC_set_handle_fork       */
+/* instructs GC_init to setup GC fork handlers using pthread_atfork,    */
+/* the latter might fail (or, even, absent on some targets) causing     */
+/* abort at GC initialization.  Starting from 7.3alpha3, problems with  */
+/* missing (or failed) pthread_atfork() could be avoided by invocation  */
+/* of GC_set_handle_fork(-1) at application start-up and surrounding    */
+/* each fork() with the relevant GC_atfork_prepare/parent/child calls.  */
 GC_API void GC_CALL GC_set_handle_fork(int);
 
+/* Routines to handle POSIX fork() manually (no-op if handled           */
+/* automatically).  GC_atfork_prepare should be called immediately      */
+/* before fork(); GC_atfork_parent should be invoked just after fork in */
+/* the branch that corresponds to parent process (i.e., fork result is  */
+/* non-zero); GC_atfork_child is to be called immediately in the child  */
+/* branch (i.e., fork result is 0). Note that GC_atfork_child() call    */
+/* should, of course, precede GC_start_mark_threads call (if any).      */
+GC_API void GC_CALL GC_atfork_prepare(void);
+GC_API void GC_CALL GC_atfork_parent(void);
+GC_API void GC_CALL GC_atfork_child(void);
+
 /* Initialize the collector.  Portable clients should call GC_INIT()    */
 /* from the main program instead.                                       */
 GC_API void GC_CALL GC_init(void);
index ce9cb56948c72a35e0b874f52c671eecfb2fe45f..2d716d4bd2e4a78a1aa6b2c77d5c0a465cda4355 100644 (file)
@@ -1961,7 +1961,20 @@ GC_EXTERN GC_bool GC_print_back_height;
 #endif
 
 #ifdef CAN_HANDLE_FORK
-  GC_EXTERN GC_bool GC_handle_fork;
+  GC_EXTERN int GC_handle_fork;
+                /* Fork-handling mode:                                  */
+                /* 0 means no fork handling requested (but client could */
+                /* anyway call fork() provided it is surrounded with    */
+                /* GC_atfork_prepare/parent/child calls);               */
+                /* -1 means GC tries to use pthread_at_fork if it is    */
+                /* available (if it succeeds then GC_handle_fork value  */
+                /* is changed to 1), client should nonetheless surround */
+                /* fork() with GC_atfork_prepare/parent/child (for the  */
+                /* case of pthread_at_fork failure or absence);         */
+                /* 1 (or other values) means client fully relies on     */
+                /* pthread_at_fork (so if it is missing or failed then  */
+                /* abort occurs in GC_init), GC_atfork_prepare and the  */
+                /* accompanying routines are no-op in such a case.      */
 #endif
 
 #ifndef GC_DISABLE_INCREMENTAL
index a35f4edbf54507685b335e56f88138c246e20c28..c21c38ca32f258692e10e54e0b1c96b9f155e765 100644 (file)
 #endif
 
 #if !defined(CAN_HANDLE_FORK) && !defined(NO_HANDLE_FORK) \
-    && ((defined(GC_PTHREADS) && !defined(HURD) && !defined(NACL) \
-         && !defined(PLATFORM_ANDROID) && !defined(GC_WIN32_PTHREADS) \
-         && !defined(USE_WINALLOC)) \
+    && !defined(HAVE_NO_FORK) \
+    && ((defined(GC_PTHREADS) && !defined(NACL) \
+         && !defined(GC_WIN32_PTHREADS) && !defined(USE_WINALLOC)) \
         || (defined(DARWIN) && defined(MPROTECT_VDB)) || defined(HANDLE_FORK))
   /* Attempts (where supported and requested) to make GC_malloc work in */
   /* a child process fork'ed from a multi-threaded parent.              */
 # define CAN_HANDLE_FORK
 #endif
 
+#if defined(CAN_HANDLE_FORK) && !defined(CAN_CALL_ATFORK) \
+    && !defined(HURD) && !defined(PLATFORM_ANDROID)
+  /* Have working pthread_atfork().     */
+# define CAN_CALL_ATFORK
+#endif
+
+#if !defined(CAN_HANDLE_FORK) && !defined(HAVE_NO_FORK) \
+    && (defined(MSWIN32) || defined(MSWINCE) || defined(DOS4GW) \
+        || defined(OS2) || defined(SYMBIAN) /* and probably others ... */)
+# define HAVE_NO_FORK
+#endif
+
 #if !defined(USE_MARK_BITS) && !defined(USE_MARK_BYTES) \
     && defined(PARALLEL_MARK)
    /* Minimize compare-and-swap usage.  */
diff --git a/misc.c b/misc.c
index 861f00d717e55837aec70883b7cf124564e6075a..7d3a1e9f866d2cd2c9b8e7ef57c91a34defe486c 100644 (file)
--- a/misc.c
+++ b/misc.c
@@ -171,24 +171,48 @@ GC_oom_func GC_oom_fn = GC_default_oom_fn;
 
 #ifdef CAN_HANDLE_FORK
 # ifdef HANDLE_FORK
-    GC_INNER GC_bool GC_handle_fork = TRUE;
+    GC_INNER int GC_handle_fork = 1;
                         /* The value is examined by GC_thr_init.        */
 # else
-    GC_INNER GC_bool GC_handle_fork = FALSE;
+    GC_INNER int GC_handle_fork = FALSE;
 # endif
-#endif /* CAN_HANDLE_FORK */
 
-/* Overrides the default handle-fork mode.  Non-zero value means GC     */
-/* should install proper pthread_atfork handlers (or abort if not       */
-/* supported).  Has effect only if called before GC_INIT.               */
+#elif !defined(HAVE_NO_FORK)
+
+  /* Same as above but with GC_CALL calling conventions.  */
+  GC_API void GC_CALL GC_atfork_prepare(void)
+  {
+#   ifdef THREADS
+      ABORT("fork() handling unsupported");
+#   endif
+  }
+
+  GC_API void GC_CALL GC_atfork_parent(void)
+  {
+    /* empty */
+  }
+
+  GC_API void GC_CALL GC_atfork_child(void)
+  {
+    /* empty */
+  }
+#endif /* !CAN_HANDLE_FORK && !HAVE_NO_FORK */
+
+/* Overrides the default automatic handle-fork mode.  Has effect only   */
+/* if called before GC_INIT.                                            */
 GC_API void GC_CALL GC_set_handle_fork(int value GC_ATTR_UNUSED)
 {
 # ifdef CAN_HANDLE_FORK
     if (!GC_is_initialized)
-      GC_handle_fork = (GC_bool)value;
+      GC_handle_fork = value >= -1 ? value : 1;
+                /* Map all negative values except for -1 to a positive one. */
 # elif defined(THREADS) || (defined(DARWIN) && defined(MPROTECT_VDB))
-    if (!GC_is_initialized && value)
-      ABORT("fork() handling disabled");
+    if (!GC_is_initialized && value) {
+#     ifndef SMALL_CONFIG
+        GC_init(); /* just to initialize GC_stderr */
+#     endif
+      ABORT("fork() handling unsupported");
+    }
 # else
     /* No at-fork handler is needed in the single-threaded mode.        */
 # endif
index 7b53fe0a477b12ec18c91267673607e29669bb0d..f270b59e00ac7dad757e1beb4adde2248226b154 100644 (file)
@@ -992,6 +992,25 @@ static void fork_child_proc(void)
     RESTORE_CANCEL(fork_cancel_state);
     UNLOCK();
 }
+
+  /* Routines for fork handling by client (no-op if pthread_atfork works). */
+  GC_API void GC_CALL GC_atfork_prepare(void)
+  {
+    if (GC_handle_fork <= 0)
+      fork_prepare_proc();
+  }
+
+  GC_API void GC_CALL GC_atfork_parent(void)
+  {
+    if (GC_handle_fork <= 0)
+      fork_parent_proc();
+  }
+
+  GC_API void GC_CALL GC_atfork_child(void)
+  {
+    if (GC_handle_fork <= 0)
+      fork_child_proc();
+  }
 #endif /* CAN_HANDLE_FORK */
 
 #ifdef INCLUDE_LINUX_THREAD_DESCR
@@ -1009,10 +1028,17 @@ GC_INNER void GC_thr_init(void)
   GC_ASSERT((word)&GC_threads % sizeof(word) == 0);
 # ifdef CAN_HANDLE_FORK
     /* Prepare for forks if requested.  */
-    if (GC_handle_fork
-          && pthread_atfork(fork_prepare_proc, fork_parent_proc,
-                            fork_child_proc) != 0)
-      ABORT("pthread_atfork failed");
+    if (GC_handle_fork) {
+#     ifdef CAN_CALL_ATFORK
+        if (pthread_atfork(fork_prepare_proc, fork_parent_proc,
+                           fork_child_proc) == 0) {
+          /* Handlers successfully registered.  */
+          GC_handle_fork = 1;
+        } else
+#     endif
+      /* else */ if (GC_handle_fork != -1)
+        ABORT("pthread_atfork failed");
+    }
 # endif
 # ifdef INCLUDE_LINUX_THREAD_DESCR
     /* Explicitly register the region including the address     */
index e2204af7924756566c6760da0613855bedda484e..189aee333b88f90ad15944c06ce3c02b9535518e 100644 (file)
 # if (!defined(THREADS) || !defined(HANDLE_FORK) \
       || (defined(DARWIN) && defined(MPROTECT_VDB) \
           && !defined(NO_INCREMENTAL) && !defined(MAKE_BACK_GRAPH))) \
-     && !defined(NO_TEST_HANDLE_FORK) && !defined(TEST_HANDLE_FORK)
+     && !defined(NO_TEST_HANDLE_FORK) && !defined(TEST_HANDLE_FORK) \
+     && !defined(TEST_FORK_WITHOUT_ATFORK)
 #   define NO_TEST_HANDLE_FORK
 # endif
 
 # ifndef NO_TEST_HANDLE_FORK
 #   include <unistd.h>
-#   define INIT_FORK_SUPPORT GC_set_handle_fork(1)
+#   ifdef HANDLE_FORK
+#     define INIT_FORK_SUPPORT GC_set_handle_fork(1)
                 /* Causes abort in GC_init on pthread_atfork failure.   */
-# else
+#   elif !defined(TEST_FORK_WITHOUT_ATFORK)
+#     define INIT_FORK_SUPPORT GC_set_handle_fork(-1)
+                /* Passing -1 implies fork() should be as well manually */
+                /* surrounded with GC_atfork_prepare/parent/child.      */
+#   endif
+# endif
+
+# ifndef INIT_FORK_SUPPORT
 #   define INIT_FORK_SUPPORT /* empty */
 # endif
 
@@ -1264,10 +1273,13 @@ void run_one_test(void)
         GC_free(GC_malloc(0));
         GC_free(GC_malloc_atomic(0));
 #   ifndef NO_TEST_HANDLE_FORK
+        GC_atfork_prepare();
         if (fork() != 0) {
+          GC_atfork_parent();
           if (print_stats)
             GC_log_printf("Forked child process (or failed)\n");
         } else {
+          GC_atfork_child();
           if (print_stats)
             GC_log_printf("Started a child process\n");
 #         ifdef THREADS
index 4e25bc7118fc00134731958c5ee8d28a902d30ed..83b2c420bfaff1a97d1f28f604963461260f42d3 100644 (file)
@@ -60,7 +60,7 @@
   STATIC void GC_thread_exit_proc(void *arg);
 
 # include <pthread.h>
-# ifdef CAN_HANDLE_FORK
+# ifdef CAN_CALL_ATFORK
 #   include <unistd.h>
 # endif
 
@@ -1085,6 +1085,25 @@ GC_API void * GC_CALL GC_call_with_gc_active(GC_fn_type fn,
       GC_remove_all_threads_but_me();
       UNLOCK();
     }
+
+  /* Routines for fork handling by client (no-op if pthread_atfork works). */
+  GC_API void GC_CALL GC_atfork_prepare(void)
+  {
+    if (GC_handle_fork <= 0)
+      fork_prepare_proc();
+  }
+
+  GC_API void GC_CALL GC_atfork_parent(void)
+  {
+    if (GC_handle_fork <= 0)
+      fork_parent_proc();
+  }
+
+  GC_API void GC_CALL GC_atfork_child(void)
+  {
+    if (GC_handle_fork <= 0)
+      fork_child_proc();
+  }
 #endif /* CAN_HANDLE_FORK */
 
 void GC_push_thread_structures(void)
@@ -2404,10 +2423,17 @@ GC_INNER void GC_thr_init(void)
 
 # ifdef CAN_HANDLE_FORK
     /* Prepare for forks if requested.  */
-    if (GC_handle_fork
-          && pthread_atfork(fork_prepare_proc, fork_parent_proc,
-                            fork_child_proc) != 0)
-      ABORT("pthread_atfork failed");
+    if (GC_handle_fork) {
+#     ifdef CAN_CALL_ATFORK
+        if (pthread_atfork(fork_prepare_proc, fork_parent_proc,
+                           fork_child_proc) == 0) {
+          /* Handlers successfully registered.  */
+          GC_handle_fork = 1;
+        } else
+#     endif
+      /* else */ if (GC_handle_fork != -1)
+        ABORT("pthread_atfork failed");
+    }
 # endif
 
   /* Add the initial thread, so we can stop it. */