1
Fork 0
mirror of https://git.savannah.gnu.org/git/guile.git synced 2025-06-24 20:30:28 +02:00

cancel-thread via asyncs, not pthread_cancel

* module/ice-9/threads.scm (cancel-tag): New variable.
  (cancel-thread): New Scheme function.
  (call-with-new-thread): Install a prompt around the thread.
* libguile/threads.h (scm_i_thread): Remove cancelled member.
* libguile/threads.c (scm_cancel_thread): Call out to Scheme.  Always
  available, and works on the current thread too.
  (scm_set_thread_cleanup_x, scm_thread_cleanup): Adapt.
  (scm_init_ice_9_threads): Capture cancel-thread var.
* doc/ref/api-scheduling.texi (Threads): Update.
* NEWS: Update.
This commit is contained in:
Andy Wingo 2016-10-27 21:22:28 +02:00
parent c957ec7ab0
commit a04739b31a
5 changed files with 44 additions and 43 deletions

3
NEWS
View file

@ -38,6 +38,9 @@ trivial unused data structure. Now that we have deprecated the old
only refer to "asyncs". only refer to "asyncs".
* Bug fixes * Bug fixes
** cancel-thread uses asynchronous interrupts, not pthread_cancel
See "Asyncs" in the manual, for more on asynchronous interrupts.
Previous changes in 2.1.x (changes since the 2.0.x series): Previous changes in 2.1.x (changes since the 2.0.x series):

View file

@ -114,17 +114,14 @@ immediate context switch to one of them. Otherwise, yield has no effect.
@deffn {Scheme Procedure} cancel-thread thread @deffn {Scheme Procedure} cancel-thread thread
@deffnx {C Function} scm_cancel_thread (thread) @deffnx {C Function} scm_cancel_thread (thread)
Asynchronously notify @var{thread} to exit. Immediately after Asynchronously interrupt @var{thread} and ask it to terminate.
receiving this notification, @var{thread} will call its cleanup handler @code{dynamic-wind} post thunks will run, but throw handlers will not.
(if one has been set) and then terminate, aborting any evaluation that If @var{thread} has already terminated or been signaled to terminate,
is in progress. this function is a no-op.
Because Guile threads are isomorphic with POSIX threads, @var{thread} Under this hood, thread cancellation uses @code{system-async-mark} and
will not receive its cancellation signal until it reaches a cancellation @code{abort-to-prompt}. @xref{Asyncs} for more on asynchronous
point. See your operating system's POSIX threading documentation for interrupts.
more information on cancellation points; note that in Guile, unlike
native POSIX threads, a thread can receive a cancellation notification
while attempting to lock a mutex.
@end deffn @end deffn
@deffn {Scheme Procedure} set-thread-cleanup! thread proc @deffn {Scheme Procedure} set-thread-cleanup! thread proc

View file

@ -438,7 +438,6 @@ guilify_self_1 (struct GC_stack_base *base)
abort (); abort ();
scm_i_pthread_mutex_init (&t.admin_mutex, NULL); scm_i_pthread_mutex_init (&t.admin_mutex, NULL);
t.canceled = 0;
t.exited = 0; t.exited = 0;
t.guile_mode = 0; t.guile_mode = 0;
@ -1012,34 +1011,14 @@ SCM_DEFINE (scm_yield, "yield", 0, 0, 0,
/* Some systems, notably Android, lack 'pthread_cancel'. Don't provide /* Some systems, notably Android, lack 'pthread_cancel'. Don't provide
'cancel-thread' on these systems. */ 'cancel-thread' on these systems. */
#if !SCM_USE_PTHREAD_THREADS || defined HAVE_PTHREAD_CANCEL static SCM cancel_thread_var;
SCM_DEFINE (scm_cancel_thread, "cancel-thread", 1, 0, 0, SCM
(SCM thread), scm_cancel_thread (SCM thread)
"Asynchronously force the target @var{thread} to terminate. @var{thread} "
"cannot be the current thread, and if @var{thread} has already terminated or "
"been signaled to terminate, this function is a no-op.")
#define FUNC_NAME s_scm_cancel_thread
{ {
scm_i_thread *t = NULL; scm_call_1 (scm_variable_ref (cancel_thread_var), thread);
SCM_VALIDATE_THREAD (1, thread);
t = SCM_I_THREAD_DATA (thread);
scm_i_scm_pthread_mutex_lock (&t->admin_mutex);
if (!t->canceled)
{
t->canceled = 1;
scm_i_pthread_mutex_unlock (&t->admin_mutex);
scm_i_pthread_cancel (t->pthread);
}
else
scm_i_pthread_mutex_unlock (&t->admin_mutex);
return SCM_UNSPECIFIED; return SCM_UNSPECIFIED;
} }
#undef FUNC_NAME
#endif
SCM_DEFINE (scm_set_thread_cleanup_x, "set-thread-cleanup!", 2, 0, 0, SCM_DEFINE (scm_set_thread_cleanup_x, "set-thread-cleanup!", 2, 0, 0,
(SCM thread, SCM proc), (SCM thread, SCM proc),
@ -1056,7 +1035,7 @@ SCM_DEFINE (scm_set_thread_cleanup_x, "set-thread-cleanup!", 2, 0, 0,
t = SCM_I_THREAD_DATA (thread); t = SCM_I_THREAD_DATA (thread);
scm_i_pthread_mutex_lock (&t->admin_mutex); scm_i_pthread_mutex_lock (&t->admin_mutex);
if (!(t->exited || t->canceled)) if (!t->exited)
t->cleanup_handler = proc; t->cleanup_handler = proc;
scm_i_pthread_mutex_unlock (&t->admin_mutex); scm_i_pthread_mutex_unlock (&t->admin_mutex);
@ -1077,7 +1056,7 @@ SCM_DEFINE (scm_thread_cleanup, "thread-cleanup", 1, 0, 0,
t = SCM_I_THREAD_DATA (thread); t = SCM_I_THREAD_DATA (thread);
scm_i_pthread_mutex_lock (&t->admin_mutex); scm_i_pthread_mutex_lock (&t->admin_mutex);
ret = (t->exited || t->canceled) ? SCM_BOOL_F : t->cleanup_handler; ret = t->exited ? SCM_BOOL_F : t->cleanup_handler;
scm_i_pthread_mutex_unlock (&t->admin_mutex); scm_i_pthread_mutex_unlock (&t->admin_mutex);
return ret; return ret;
@ -2073,6 +2052,9 @@ scm_init_ice_9_threads (void *unused)
{ {
#include "libguile/threads.x" #include "libguile/threads.x"
cancel_thread_var =
scm_module_variable (scm_current_module (),
scm_from_latin1_symbol ("cancel-thread"));
call_with_new_thread_var = call_with_new_thread_var =
scm_module_variable (scm_current_module (), scm_module_variable (scm_current_module (),
scm_from_latin1_symbol ("call-with-new-thread")); scm_from_latin1_symbol ("call-with-new-thread"));

View file

@ -68,7 +68,6 @@ typedef struct scm_i_thread {
scm_i_pthread_mutex_t *held_mutex; scm_i_pthread_mutex_t *held_mutex;
SCM result; SCM result;
int canceled;
int exited; int exited;
/* Boolean indicating whether the thread is in guile mode. */ /* Boolean indicating whether the thread is in guile mode. */

View file

@ -31,6 +31,7 @@
(define-module (ice-9 threads) (define-module (ice-9 threads)
#:use-module (ice-9 match) #:use-module (ice-9 match)
#:use-module (ice-9 control)
;; These bindings are marked as #:replace because when deprecated code ;; These bindings are marked as #:replace because when deprecated code
;; is enabled, (ice-9 deprecated) also exports these names. ;; is enabled, (ice-9 deprecated) also exports these names.
;; (Referencing one of the deprecated names prints a warning directing ;; (Referencing one of the deprecated names prints a warning directing
@ -86,6 +87,21 @@
(define cancel-tag (make-prompt-tag "cancel"))
(define (cancel-thread thread)
"Asynchronously interrupt the target @var{thread} and ask it to
terminate. @code{dynamic-wind} post thunks will run, but throw handlers
will not. If @var{thread} has already terminated or been signaled to
terminate, this function is a no-op."
(system-async-mark
(lambda ()
(catch #t
(lambda ()
(abort-to-prompt cancel-tag))
(lambda _
(error "thread cancellation failed, throwing error instead???"))))
thread))
(define* (call-with-new-thread thunk #:optional handler) (define* (call-with-new-thread thunk #:optional handler)
"Call @code{thunk} in a new thread and with a new dynamic state, "Call @code{thunk} in a new thread and with a new dynamic state,
returning a new thread object representing the thread. The procedure returning a new thread object representing the thread. The procedure
@ -106,11 +122,15 @@ Once @var{thunk} or @var{handler} returns, the return value is made the
(with-mutex mutex (with-mutex mutex
(%call-with-new-thread (%call-with-new-thread
(lambda () (lambda ()
(lock-mutex mutex) (call-with-prompt cancel-tag
(set! thread (current-thread)) (lambda ()
(signal-condition-variable cv) (lock-mutex mutex)
(unlock-mutex mutex) (set! thread (current-thread))
(thunk))) (signal-condition-variable cv)
(unlock-mutex mutex)
(thunk))
(lambda (k . args)
(apply values args)))))
(let lp () (let lp ()
(unless thread (unless thread
(wait-condition-variable cv mutex) (wait-condition-variable cv mutex)