diff --git a/AUTHORS b/AUTHORS index e4079b340..2c27f3b04 100644 --- a/AUTHORS +++ b/AUTHORS @@ -210,6 +210,7 @@ Kai Tietz Kaz Kojima Kazu Hirata Kazuhiro Inaoka +Keith Seitz Kenjiro Taura Kenneth Schalk Kevin Kenny diff --git a/CMakeLists.txt b/CMakeLists.txt index 97c69c484..40c04163b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -203,6 +203,7 @@ ENDIF(CMAKE_USE_WIN32_THREADS_INIT) OPTION(enable_gcj_support "Support for gcj" NO) IF(enable_gcj_support) ADD_DEFINITIONS("-DGC_GCJ_SUPPORT") + ADD_DEFINITIONS("-DGC_ENABLE_SUSPEND_THREAD") ENDIF(enable_gcj_support) diff --git a/configure.ac b/configure.ac index 3260e7dab..4ca7b0ab6 100644 --- a/configure.ac +++ b/configure.ac @@ -682,6 +682,8 @@ AC_ARG_ENABLE(gcj-support, [Disable support for gcj.])]) if test x"$enable_gcj_support" != xno; then AC_DEFINE(GC_GCJ_SUPPORT, 1, [Define to include support for gcj.]) + AC_DEFINE([GC_ENABLE_SUSPEND_THREAD], 1, + [Define to turn on GC_suspend_thread support.]) fi dnl Interaction with other programs that might use signals. diff --git a/doc/README.macros b/doc/README.macros index c3d27437c..90ef5128a 100644 --- a/doc/README.macros +++ b/doc/README.macros @@ -403,6 +403,9 @@ PARALLEL_MARK Allows the marker to run in multiple threads. Recommended GC_ALWAYS_MULTITHREADED Force multi-threaded mode at GC initialization. (Turns GC_allow_register_threads into a no-op routine.) +GC_ENABLE_SUSPEND_THREAD (Linux only) Turn on thread suspend/resume API +support. + GC_WINMAIN_REDIRECT (Win32 only) Redirect (rename) an application WinMain to GC_WinMain; implement the "real" WinMain which starts a new thread to call GC_WinMain after initializing the GC. Useful for WinCE. diff --git a/include/gc_pthread_redirects.h b/include/gc_pthread_redirects.h index 0d571e913..e069c578b 100644 --- a/include/gc_pthread_redirects.h +++ b/include/gc_pthread_redirects.h @@ -33,6 +33,10 @@ #ifndef GC_PTHREAD_REDIRECTS_ONLY # include +# ifndef GC_SUSPEND_THREAD_ID +# define GC_SUSPEND_THREAD_ID pthread_t +# endif + # ifndef GC_NO_DLOPEN # include GC_API void *GC_dlopen(const char * /* path */, int /* mode */); diff --git a/include/javaxfc.h b/include/javaxfc.h index 1583a2415..2a77ea6ce 100644 --- a/include/javaxfc.h +++ b/include/javaxfc.h @@ -40,6 +40,21 @@ */ GC_API void GC_CALL GC_finalize_all(void); +#ifdef GC_THREADS + /* External thread suspension support. No thread suspension count */ + /* (so a thread which has been suspended numerous times will be */ + /* resumed with the very first call to GC_resume_thread). */ + /* Acquire the allocation lock. Thread should be registered in GC */ + /* (otherwise no-op, GC_is_thread_suspended returns false). */ + /* Unimplemented on some platforms. Not recommended for general use. */ +# ifndef GC_SUSPEND_THREAD_ID +# define GC_SUSPEND_THREAD_ID void* +# endif + GC_API void GC_CALL GC_suspend_thread(GC_SUSPEND_THREAD_ID); + GC_API void GC_CALL GC_resume_thread(GC_SUSPEND_THREAD_ID); + GC_API int GC_CALL GC_is_thread_suspended(GC_SUSPEND_THREAD_ID); +#endif /* GC_THREADS */ + #ifdef __cplusplus } /* end of extern "C" */ #endif diff --git a/include/private/pthread_support.h b/include/private/pthread_support.h index 294aeedb9..f835fa3c9 100644 --- a/include/private/pthread_support.h +++ b/include/private/pthread_support.h @@ -62,9 +62,7 @@ typedef struct GC_Thread_Rep { /* it unregisters itself, since it */ /* may not return a GC pointer. */ # define MAIN_THREAD 4 /* True for the original thread only. */ -# define SUSPENDED_EXT 8 /* Thread was suspended externally */ - /* (this is not used by the unmodified */ - /* GC itself at present). */ +# define SUSPENDED_EXT 8 /* Thread was suspended externally. */ # define DISABLED_GC 0x10 /* Collections are disabled while the */ /* thread is exiting. */ diff --git a/pthread_stop_world.c b/pthread_stop_world.c index 63c93c0f2..1ee9ee8f9 100644 --- a/pthread_stop_world.c +++ b/pthread_stop_world.c @@ -50,6 +50,10 @@ /* It's safe to call original pthread_sigmask() here. */ #undef pthread_sigmask +#ifdef GC_ENABLE_SUSPEND_THREAD + static void *GC_CALLBACK suspend_self_inner(void *client_data); +#endif + #ifdef DEBUG_THREADS # ifndef NSIG # if defined(MAXSIG) @@ -260,6 +264,27 @@ STATIC void GC_suspend_handler_inner(ptr_t dummy GC_ATTR_UNUSED, /* of a thread which holds the allocation lock in order */ /* to stop the world. Thus concurrent modification of the */ /* data structure is impossible. */ + +# ifdef GC_ENABLE_SUSPEND_THREAD + if ((me -> flags & SUSPENDED_EXT) != 0) { +# ifdef SPARC + me -> stop_info.stack_ptr = GC_save_regs_in_stack(); +# else + me -> stop_info.stack_ptr = GC_approx_sp(); +# ifdef IA64 + me -> backing_store_ptr = GC_save_regs_in_stack(); +# endif +# endif + sem_post(&GC_suspend_ack_sem); + suspend_self_inner(me); +# ifdef DEBUG_THREADS + GC_log_printf("Continuing %p on GC_resume_thread\n", (void *)self); +# endif + RESTORE_CANCEL(cancel_state); + return; + } +# endif + if (me -> stop_info.last_stop_count == my_stop_count) { /* Duplicate signal. OK if we are retrying. */ if (!GC_retry_signals) { @@ -339,6 +364,121 @@ STATIC void GC_restart_handler(int sig) # endif } +# ifdef USE_TKILL_ON_ANDROID + extern int tkill(pid_t tid, int sig); /* from sys/linux-unistd.h */ + + static int android_thread_kill(pid_t tid, int sig) + { + int ret; + int old_errno = errno; + + ret = tkill(tid, sig); + if (ret < 0) { + ret = errno; + errno = old_errno; + } + return ret; + } + +# define THREAD_SYSTEM_ID(t) (t)->kernel_id +# define RAISE_SIGNAL(t, sig) android_thread_kill(THREAD_SYSTEM_ID(t), sig) +# else +# define THREAD_SYSTEM_ID(t) (t)->id +# define RAISE_SIGNAL(t, sig) pthread_kill(THREAD_SYSTEM_ID(t), sig) +# endif /* !USE_TKILL_ON_ANDROID */ + +# ifdef GC_ENABLE_SUSPEND_THREAD +# ifndef GC_TIME_LIMIT +# define GC_TIME_LIMIT 50 +# endif + + STATIC void GC_brief_async_signal_safe_sleep(void) + { + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 1000 * GC_TIME_LIMIT / 2; + select(0, 0, 0, 0, &tv); + } + + static void *GC_CALLBACK suspend_self_inner(void *client_data) { + GC_thread me = (GC_thread)client_data; + + while ((me -> flags & SUSPENDED_EXT) != 0) { + /* TODO: Use sigsuspend() instead. */ + GC_brief_async_signal_safe_sleep(); + } + return NULL; + } + + GC_API void GC_CALL GC_suspend_thread(GC_SUSPEND_THREAD_ID thread) { + GC_thread t; + DCL_LOCK_STATE; + + LOCK(); + t = GC_lookup_thread((pthread_t)thread); + if (t == NULL || (t -> flags & SUSPENDED_EXT) != 0) { + UNLOCK(); + return; + } + + t -> flags |= SUSPENDED_EXT; + if ((pthread_t)thread == pthread_self()) { + UNLOCK(); + /* It is safe as "t" cannot become invalid here (no race with */ + /* GC_unregister_my_thread). */ + (void)GC_do_blocking(suspend_self_inner, t); + return; + } + + /* TODO: Support GC_retry_signals */ + switch (RAISE_SIGNAL(t, GC_sig_suspend)) { + case ESRCH: + /* Not really there anymore (terminated but not joined yet). */ + /* No need to wait but leave the suspension flag on. */ + GC_ASSERT((t -> flags & FINISHED) != 0); + UNLOCK(); + return; + case 0: + break; + default: + ABORT("pthread_kill failed"); + } + + /* Wait for the thread to complete threads table lookup and */ + /* stack_ptr assignment. */ + GC_ASSERT(GC_thr_initialized); + while (sem_wait(&GC_suspend_ack_sem) != 0) { + if (errno != EINTR) + ABORT("sem_wait for handler failed (suspend_self)"); + } + UNLOCK(); + } + + GC_API void GC_CALL GC_resume_thread(GC_SUSPEND_THREAD_ID thread) { + GC_thread t; + DCL_LOCK_STATE; + + LOCK(); + t = GC_lookup_thread((pthread_t)thread); + if (t != NULL) + t -> flags &= ~SUSPENDED_EXT; + UNLOCK(); + } + + GC_API int GC_CALL GC_is_thread_suspended(GC_SUSPEND_THREAD_ID thread) { + GC_thread t; + int flags = 0; + DCL_LOCK_STATE; + + LOCK(); + t = GC_lookup_thread((pthread_t)thread); + if (t != NULL) + flags = t -> flags; + UNLOCK(); + return (flags & SUSPENDED_EXT) != 0; + } +# endif /* GC_ENABLE_SUSPEND_THREAD */ + #endif /* !GC_OPENBSD_UTHREADS && !NACL */ #ifdef IA64 @@ -449,24 +589,6 @@ GC_INNER void GC_push_all_stacks(void) int GC_stopping_pid = 0; #endif -#ifdef USE_TKILL_ON_ANDROID - extern int tkill(pid_t tid, int sig); /* from sys/linux-unistd.h */ - - static int android_thread_kill(pid_t tid, int sig) - { - int ret; - int old_errno = errno; - - ret = tkill(tid, sig); - if (ret < 0) { - ret = errno; - errno = old_errno; - } - - return ret; - } -#endif /* USE_TKILL_ON_ANDROID */ - /* We hold the allocation lock. Suspend all threads that might */ /* still be running. Return the number of suspend signals that */ /* were sent. */ @@ -475,11 +597,6 @@ STATIC int GC_suspend_all(void) int n_live_threads = 0; int i; # ifndef NACL -# ifndef USE_TKILL_ON_ANDROID - pthread_t thread_id; -# else - pid_t thread_id; -# endif GC_thread p; # ifndef GC_OPENBSD_UTHREADS int result; @@ -493,7 +610,7 @@ STATIC int GC_suspend_all(void) for (i = 0; i < THREAD_TABLE_SZ; i++) { for (p = GC_threads[i]; p != 0; p = p -> next) { if (!THREAD_EQUAL(p -> id, self)) { - if (p -> flags & FINISHED) continue; + if ((p -> flags & (FINISHED | SUSPENDED_EXT)) != 0) continue; if (p -> thread_blocked) /* Will wait */ continue; # ifndef GC_OPENBSD_UTHREADS if (p -> stop_info.last_stop_count == GC_stop_count) continue; @@ -516,13 +633,7 @@ STATIC int GC_suspend_all(void) (void *)p->id); } # else -# ifndef USE_TKILL_ON_ANDROID - thread_id = p -> id; - result = pthread_kill(thread_id, GC_sig_suspend); -# else - thread_id = p -> kernel_id; - result = android_thread_kill(thread_id, GC_sig_suspend); -# endif + result = RAISE_SIGNAL(p, GC_sig_suspend); switch(result) { case ESRCH: /* Not really there anymore. Possible? */ @@ -531,8 +642,8 @@ STATIC int GC_suspend_all(void) case 0: if (GC_on_thread_event) GC_on_thread_event(GC_EVENT_THREAD_SUSPENDED, - (void *)(word)thread_id); - /* Note: thread_id might be truncated. */ + (void *)(word)THREAD_SYSTEM_ID(p)); + /* Note: thread id might be truncated. */ break; default: ABORT_ARG1("pthread_kill failed at suspend", @@ -846,11 +957,6 @@ GC_INNER void GC_start_world(void) register int n_live_threads = 0; register int result; # endif -# ifndef USE_TKILL_ON_ANDROID - pthread_t thread_id; -# else - pid_t thread_id; -# endif # ifdef GC_NETBSD_THREADS_WORKAROUND int code; # endif @@ -865,7 +971,7 @@ GC_INNER void GC_start_world(void) for (i = 0; i < THREAD_TABLE_SZ; i++) { for (p = GC_threads[i]; p != 0; p = p -> next) { if (!THREAD_EQUAL(p -> id, self)) { - if (p -> flags & FINISHED) continue; + if ((p -> flags & (FINISHED | SUSPENDED_EXT)) != 0) continue; if (p -> thread_blocked) continue; # ifndef GC_OPENBSD_UTHREADS n_live_threads++; @@ -880,13 +986,7 @@ GC_INNER void GC_start_world(void) if (GC_on_thread_event) GC_on_thread_event(GC_EVENT_THREAD_UNSUSPENDED, (void *)p->id); # else -# ifndef USE_TKILL_ON_ANDROID - thread_id = p -> id; - result = pthread_kill(thread_id, GC_sig_thr_restart); -# else - thread_id = p -> kernel_id; - result = android_thread_kill(thread_id, GC_sig_thr_restart); -# endif + result = RAISE_SIGNAL(p, GC_sig_thr_restart); switch(result) { case ESRCH: /* Not really there anymore. Possible? */ @@ -895,7 +995,7 @@ GC_INNER void GC_start_world(void) case 0: if (GC_on_thread_event) GC_on_thread_event(GC_EVENT_THREAD_UNSUSPENDED, - (void *)(word)thread_id); + (void *)(word)THREAD_SYSTEM_ID(p)); break; default: ABORT_ARG1("pthread_kill failed at resume", diff --git a/pthread_support.c b/pthread_support.c index 8f379d2dc..c9fb99f3a 100644 --- a/pthread_support.c +++ b/pthread_support.c @@ -1637,6 +1637,7 @@ GC_API int GC_CALL GC_register_my_thread(const struct GC_stack_base *sb) } else if ((me -> flags & FINISHED) != 0) { /* This code is executed when a thread is registered from the */ /* client thread key destructor. */ + GC_ASSERT((me -> flags & SUSPENDED_EXT) == 0); GC_record_stack_base(me, sb); me -> flags &= ~FINISHED; /* but not DETACHED */ # ifdef GC_EXPLICIT_SIGNALS_UNBLOCK diff --git a/tests/test.c b/tests/test.c index 8bc555869..9a588b27f 100644 --- a/tests/test.c +++ b/tests/test.c @@ -510,10 +510,18 @@ void check_marks_int_list(sexpr x) check_ints(reverse(reverse(ints(1, TINY_REVERSE_UPPER_VALUE))), 1, TINY_REVERSE_UPPER_VALUE); } +# if defined(GC_ENABLE_SUSPEND_THREAD) + /* Force collection from a thread. */ + GC_gcollect(); +# endif return 0; } # if defined(GC_PTHREADS) +# if defined(GC_ENABLE_SUSPEND_THREAD) +# include "javaxfc.h" +# endif + void fork_a_thread(void) { pthread_t t; @@ -522,6 +530,27 @@ void check_marks_int_list(sexpr x) GC_printf("Small thread creation failed %d\n", code); FAIL; } +# if defined(GC_ENABLE_SUSPEND_THREAD) && !defined(GC_DARWIN_THREADS) \ + && !defined(GC_OPENBSD_UTHREADS) && !defined(GC_WIN32_THREADS) \ + && !defined(NACL) + if (GC_is_thread_suspended(t)) { + GC_printf("Running thread should be not suspended\n"); + FAIL; + } + /* Thread could be running or already terminated (but not joined). */ + GC_suspend_thread(t); + if (!GC_is_thread_suspended(t)) { + GC_printf("Thread expected to be suspended\n"); + FAIL; + } + GC_suspend_thread(t); /* should be no-op */ + GC_resume_thread(t); + if (GC_is_thread_suspended(t)) { + GC_printf("Resumed thread should be not suspended\n"); + FAIL; + } + GC_resume_thread(t); /* should be no-op */ +# endif if ((code = pthread_join(t, 0)) != 0) { GC_printf("Small thread join failed %d\n", code); FAIL;