diff options
Diffstat (limited to 'libguile/finalizers.c')
-rw-r--r-- | libguile/finalizers.c | 228 |
1 files changed, 223 insertions, 5 deletions
diff --git a/libguile/finalizers.c b/libguile/finalizers.c index 6abc70091..82f292cd2 100644 --- a/libguile/finalizers.c +++ b/libguile/finalizers.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2012, 2014 Free Software Foundation, Inc. +/* Copyright (C) 2012, 2013, 2014 Free Software Foundation, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License @@ -23,6 +23,11 @@ # include <config.h> #endif +#include <unistd.h> +#include <fcntl.h> + +#include <full-write.h> + #include "libguile/bdw-gc.h" #include "libguile/_scm.h" #include "libguile/finalizers.h" @@ -163,17 +168,196 @@ queue_finalizer_async (void) -#ifndef HAVE_GC_SET_FINALIZER_NOTIFIER +#if SCM_USE_PTHREAD_THREADS + +static int finalization_pipe[2]; +static scm_i_pthread_mutex_t finalization_thread_lock = + SCM_I_PTHREAD_MUTEX_INITIALIZER; +static pthread_t finalization_thread; +static int finalization_thread_is_running = 0; + static void -GC_set_finalizer_notifier (void (*notifier) (void)) +notify_finalizers_to_run (void) { - GC_finalizer_notifier = notifier; + char byte = 0; + full_write (finalization_pipe[1], &byte, 1); } + +static void +notify_about_to_fork (void) +{ + char byte = 1; + full_write (finalization_pipe[1], &byte, 1); +} + +struct finalization_pipe_data +{ + char byte; + ssize_t n; + int err; +}; + +static void* +read_finalization_pipe_data (void *data) +{ + struct finalization_pipe_data *fdata = data; + + fdata->n = read (finalization_pipe[0], &fdata->byte, 1); + fdata->err = errno; + + return NULL; +} + +static void* +finalization_thread_proc (void *unused) +{ + while (1) + { + struct finalization_pipe_data data; + + scm_without_guile (read_finalization_pipe_data, &data); + + if (data.n <= 0 && data.err != EINTR) + { + perror ("error in finalization thread"); + return NULL; + } + + switch (data.byte) + { + case 0: + scm_run_finalizers (); + break; + case 1: + return NULL; + default: + abort (); + } + } +} + +static void* +run_finalization_thread (void *arg) +{ + return scm_with_guile (finalization_thread_proc, arg); +} + +static void +start_finalization_thread (void) +{ + scm_i_pthread_mutex_lock (&finalization_thread_lock); + if (!finalization_thread_is_running) + { + /* Use the raw pthread API and scm_with_guile, because we don't want + to block on any lock that scm_spawn_thread might want to take, + and we don't want to inherit the dynamic state (fluids) of the + caller. */ + if (pthread_create (&finalization_thread, NULL, + run_finalization_thread, NULL)) + perror ("error creating finalization thread"); + else + finalization_thread_is_running = 1; + } + scm_i_pthread_mutex_unlock (&finalization_thread_lock); +} + +static void +stop_finalization_thread (void) +{ + scm_i_pthread_mutex_lock (&finalization_thread_lock); + if (finalization_thread_is_running) + { + notify_about_to_fork (); + if (pthread_join (finalization_thread, NULL)) + perror ("joining finalization thread"); + finalization_thread_is_running = 0; + } + scm_i_pthread_mutex_unlock (&finalization_thread_lock); +} + +static void +spawn_finalizer_thread (void) +{ + GC_set_finalizer_notifier (notify_finalizers_to_run); + start_finalization_thread (); +} + +#endif /* SCM_USE_PTHREAD_THREADS */ + + + + +void +scm_i_finalizer_pre_fork (void) +{ +#if SCM_USE_PTHREAD_THREADS + if (automatic_finalization_p) + { + stop_finalization_thread (); + GC_set_finalizer_notifier (spawn_finalizer_thread); + } #endif +} +static void* +weak_pointer_ref (void *weak_pointer) +{ + return *(void **) weak_pointer; +} + +static void +weak_gc_finalizer (void *ptr, void *data) +{ + void **weak = ptr; + void *val; + void (*callback) (SCM) = weak[1]; + + val = GC_call_with_alloc_lock (weak_pointer_ref, &weak[0]); + + if (!val) + return; + + callback (SCM_PACK_POINTER (val)); + + scm_i_set_finalizer (ptr, weak_gc_finalizer, data); +} + +/* CALLBACK will be called on OBJ, as long as OBJ is accessible. It + will be called from a finalizer, which may be from an async or from + another thread. + + As an implementation detail, the way this works is that we allocate + a fresh pointer-less object holding two words. We know that this + object should get collected the next time GC is run, so we attach a + finalizer to it so that we get a callback after GC happens. + + The first word of the object holds a weak reference to OBJ, and the + second holds the callback pointer. When the callback is called, we + check if the weak reference on OBJ still holds. If it doesn't hold, + then OBJ is no longer accessible, and we're done. Otherwise we call + the callback and re-register a finalizer for our two-word GC object, + effectively resuscitating the object so that we will get a callback + on the next GC. + + We could use the scm_after_gc_hook, but using a finalizer has the + advantage of potentially running in another thread, decreasing pause + time. */ +void +scm_i_register_weak_gc_callback (SCM obj, void (*callback) (SCM)) +{ + void **weak = GC_MALLOC_ATOMIC (sizeof (void*) * 2); + + weak[0] = SCM_UNPACK_POINTER (obj); + weak[1] = (void*)callback; + GC_GENERAL_REGISTER_DISAPPEARING_LINK (weak, SCM2PTR (obj)); + + scm_i_set_finalizer (weak, weak_gc_finalizer, NULL); +} + + int scm_set_automatic_finalization_enabled (int enabled_p) { @@ -188,7 +372,28 @@ scm_set_automatic_finalization_enabled (int enabled_p) return was_enabled_p; } - GC_set_finalizer_notifier (enabled_p ? queue_finalizer_async : 0); + if (enabled_p) + { +#if SCM_USE_PTHREAD_THREADS + if (pipe2 (finalization_pipe, O_CLOEXEC) != 0) + scm_syserror (NULL); + GC_set_finalizer_notifier (spawn_finalizer_thread); +#else + GC_set_finalizer_notifier (queue_finalizer_async); +#endif + } + else + { + GC_set_finalizer_notifier (0); + +#if SCM_USE_PTHREAD_THREADS + stop_finalization_thread (); + close (finalization_pipe[0]); + close (finalization_pipe[1]); + finalization_pipe[0] = -1; + finalization_pipe[1] = -1; +#endif + } automatic_finalization_p = enabled_p; @@ -221,3 +426,16 @@ scm_init_finalizers (void) if (automatic_finalization_p) GC_set_finalizer_notifier (queue_finalizer_async); } + +void +scm_init_finalizer_thread (void) +{ +#if SCM_USE_PTHREAD_THREADS + if (automatic_finalization_p) + { + if (pipe2 (finalization_pipe, O_CLOEXEC) != 0) + scm_syserror (NULL); + GC_set_finalizer_notifier (spawn_finalizer_thread); + } +#endif +} |