1
Fork 0
mirror of https://git.savannah.gnu.org/git/guile.git synced 2025-04-30 03:40:34 +02:00

Fix finalizer resuscitation causing excessive GC

* libguile/finalizers.c (async_gc_finalizer):
  (scm_i_register_async_gc_callback): Replace "weak gc callback"
  mechanism with "async gc callback" mechanism.  Very similar but the
  new API is designed to be called a bounded number of times, to avoid
  running afoul of libgc heuristics.
* libguile/weak-list.h: New internal header.
* libguile/Makefile.am (noinst_HEADERS): Add weak-list.h.
* libguile/weak-set.c (vacuum_all_weak_sets):
  (scm_c_make_weak_set, scm_init_weak_set):
* libguile/weak-table.c (vacuum_all_weak_tables):
  (scm_c_make_weak_table, scm_init_weak_table): Arrange to vacuum all
  weak sets from a single async GC callback, and likewise for weak
  tables.

Thanks to Ludovic Courtès for tracking this bug down!
This commit is contained in:
Andy Wingo 2017-03-13 15:47:51 +01:00
parent e337432041
commit c9910c6042
6 changed files with 137 additions and 43 deletions

View file

@ -296,59 +296,46 @@ scm_i_finalizer_pre_fork (void)
static void*
weak_pointer_ref (void *weak_pointer)
{
return *(void **) weak_pointer;
}
static void
weak_gc_finalizer (void *ptr, void *data)
async_gc_finalizer (void *ptr, void *data)
{
void **weak = ptr;
void *val;
void (*callback) (SCM) = weak[1];
void **obj = ptr;
void (*callback) (void) = obj[0];
val = GC_call_with_alloc_lock (weak_pointer_ref, &weak[0]);
callback ();
if (!val)
return;
callback (SCM_PACK_POINTER (val));
scm_i_set_finalizer (ptr, weak_gc_finalizer, data);
scm_i_set_finalizer (ptr, async_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
/* Arrange to call CALLBACK asynchronously after each GC. The callback
will be invoked 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
As an implementation detail, the way this works is that we allocate a
fresh object and put the callback in the object. 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.
finalizer to it to trigger the callback.
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.
Once the callback runs, we re-attach a finalizer to that fresh object
to prepare for the next GC, and the process repeats indefinitely.
We could use the scm_after_gc_hook, but using a finalizer has the
advantage of potentially running in another thread, decreasing pause
time. */
time.
Note that libgc currently has a heuristic that adding 500 finalizable
objects will cause GC to collect rather than expand the heap,
drastically reducing performance on workloads that actually need to
expand the heap. Therefore scm_i_register_async_gc_callback is
inappropriate for using on unbounded numbers of callbacks. */
void
scm_i_register_weak_gc_callback (SCM obj, void (*callback) (SCM))
scm_i_register_async_gc_callback (void (*callback) (void))
{
void **weak = GC_MALLOC_ATOMIC (sizeof (void*) * 2);
void **obj = GC_MALLOC_ATOMIC (sizeof (void*));
weak[0] = SCM_UNPACK_POINTER (obj);
weak[1] = (void*)callback;
GC_GENERAL_REGISTER_DISAPPEARING_LINK (weak, SCM2PTR (obj));
obj[0] = (void*)callback;
scm_i_set_finalizer (weak, weak_gc_finalizer, NULL);
scm_i_set_finalizer (obj, async_gc_finalizer, NULL);
}