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

@ -507,7 +507,9 @@ noinst_HEADERS = conv-integer.i.c conv-uinteger.i.c \
atomics-internal.h \ atomics-internal.h \
cache-internal.h \ cache-internal.h \
posix-w32.h \ posix-w32.h \
private-options.h ports-internal.h private-options.h \
ports-internal.h \
weak-list.h
# vm instructions # vm instructions
noinst_HEADERS += vm-engine.c noinst_HEADERS += vm-engine.c

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 static void
weak_gc_finalizer (void *ptr, void *data) async_gc_finalizer (void *ptr, void *data)
{ {
void **weak = ptr; void **obj = ptr;
void *val; void (*callback) (void) = obj[0];
void (*callback) (SCM) = weak[1];
val = GC_call_with_alloc_lock (weak_pointer_ref, &weak[0]); callback ();
if (!val) scm_i_set_finalizer (ptr, async_gc_finalizer, data);
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 /* Arrange to call CALLBACK asynchronously after each GC. The callback
will be called from a finalizer, which may be from an async or from will be invoked from a finalizer, which may be from an async or from
another thread. another thread.
As an implementation detail, the way this works is that we allocate As an implementation detail, the way this works is that we allocate a
a fresh pointer-less object holding two words. We know that this 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 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 Once the callback runs, we re-attach a finalizer to that fresh object
second holds the callback pointer. When the callback is called, we to prepare for the next GC, and the process repeats indefinitely.
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 We could use the scm_after_gc_hook, but using a finalizer has the
advantage of potentially running in another thread, decreasing pause 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 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); obj[0] = (void*)callback;
weak[1] = (void*)callback;
GC_GENERAL_REGISTER_DISAPPEARING_LINK (weak, SCM2PTR (obj));
scm_i_set_finalizer (weak, weak_gc_finalizer, NULL); scm_i_set_finalizer (obj, async_gc_finalizer, NULL);
} }

View file

@ -36,10 +36,10 @@ SCM_INTERNAL void scm_i_add_resuscitator (void *obj, scm_t_finalizer_proc,
SCM_INTERNAL void scm_i_finalizer_pre_fork (void); SCM_INTERNAL void scm_i_finalizer_pre_fork (void);
/* CALLBACK will be called on OBJ after each garbage collection, as long /* CALLBACK will be called after each garbage collection. It will be
as OBJ is accessible. It will be called from a finalizer, which may called from a finalizer, which may be from an async or from another
be from an async or from another thread. */ thread. */
SCM_INTERNAL void scm_i_register_weak_gc_callback (SCM obj, void (*callback) (SCM)); SCM_INTERNAL void scm_i_register_async_gc_callback (void (*callback) (void));
SCM_API int scm_set_automatic_finalization_enabled (int enabled_p); SCM_API int scm_set_automatic_finalization_enabled (int enabled_p);
SCM_API int scm_run_finalizers (void); SCM_API int scm_run_finalizers (void);

73
libguile/weak-list.h Normal file
View file

@ -0,0 +1,73 @@
/* classes: h_files */
#ifndef SCM_WEAK_LIST_H
#define SCM_WEAK_LIST_H
/* Copyright (C) 2016 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
* as published by the Free Software Foundation; either version 3 of
* the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include "libguile/__scm.h"
#include "libguile/weak-vector.h"
static inline SCM
scm_i_weak_cons (SCM car, SCM cdr)
{
return scm_cons (scm_c_make_weak_vector (1, car), cdr);
}
static inline SCM
scm_i_weak_car (SCM pair)
{
return scm_c_weak_vector_ref (scm_car (pair), 0);
}
static inline void
scm_i_visit_weak_list (SCM *list_loc, void (*visit) (SCM))
{
SCM in = *list_loc, out = SCM_EOL;
while (scm_is_pair (in))
{
SCM car = scm_i_weak_car (in);
SCM cdr = scm_cdr (in);
if (!scm_is_eq (car, SCM_BOOL_F))
{
scm_set_cdr_x (in, out);
out = in;
visit (car);
}
in = cdr;
}
*list_loc = out;
}
#endif /* SCM_WEAK_LIST_H */
/*
Local Variables:
c-file-style: "gnu"
End:
*/

View file

@ -31,6 +31,7 @@
#include "libguile/bdw-gc.h" #include "libguile/bdw-gc.h"
#include "libguile/validate.h" #include "libguile/validate.h"
#include "libguile/weak-list.h"
#include "libguile/weak-set.h" #include "libguile/weak-set.h"
@ -698,6 +699,17 @@ do_vacuum_weak_set (SCM set)
scm_i_pthread_mutex_unlock (&s->lock); scm_i_pthread_mutex_unlock (&s->lock);
} }
static scm_i_pthread_mutex_t all_weak_sets_lock = SCM_I_PTHREAD_MUTEX_INITIALIZER;
static SCM all_weak_sets = SCM_EOL;
static void
vacuum_all_weak_sets (void)
{
scm_i_pthread_mutex_lock (&all_weak_sets_lock);
scm_i_visit_weak_list (&all_weak_sets, do_vacuum_weak_set);
scm_i_pthread_mutex_unlock (&all_weak_sets_lock);
}
SCM SCM
scm_c_make_weak_set (unsigned long k) scm_c_make_weak_set (unsigned long k)
{ {
@ -705,7 +717,9 @@ scm_c_make_weak_set (unsigned long k)
ret = make_weak_set (k); ret = make_weak_set (k);
scm_i_register_weak_gc_callback (ret, do_vacuum_weak_set); scm_i_pthread_mutex_lock (&all_weak_sets_lock);
all_weak_sets = scm_i_weak_cons (ret, all_weak_sets);
scm_i_pthread_mutex_unlock (&all_weak_sets_lock);
return ret; return ret;
} }
@ -883,6 +897,8 @@ void
scm_init_weak_set () scm_init_weak_set ()
{ {
#include "libguile/weak-set.x" #include "libguile/weak-set.x"
scm_i_register_async_gc_callback (vacuum_all_weak_sets);
} }
/* /*

View file

@ -33,6 +33,7 @@
#include "libguile/ports.h" #include "libguile/ports.h"
#include "libguile/validate.h" #include "libguile/validate.h"
#include "libguile/weak-list.h"
#include "libguile/weak-table.h" #include "libguile/weak-table.h"
@ -832,6 +833,17 @@ do_vacuum_weak_table (SCM table)
return; return;
} }
static scm_i_pthread_mutex_t all_weak_tables_lock = SCM_I_PTHREAD_MUTEX_INITIALIZER;
static SCM all_weak_tables = SCM_EOL;
static void
vacuum_all_weak_tables (void)
{
scm_i_pthread_mutex_lock (&all_weak_tables_lock);
scm_i_visit_weak_list (&all_weak_tables, do_vacuum_weak_table);
scm_i_pthread_mutex_unlock (&all_weak_tables_lock);
}
SCM SCM
scm_c_make_weak_table (unsigned long k, scm_t_weak_table_kind kind) scm_c_make_weak_table (unsigned long k, scm_t_weak_table_kind kind)
{ {
@ -839,7 +851,9 @@ scm_c_make_weak_table (unsigned long k, scm_t_weak_table_kind kind)
ret = make_weak_table (k, kind); ret = make_weak_table (k, kind);
scm_i_register_weak_gc_callback (ret, do_vacuum_weak_table); scm_i_pthread_mutex_lock (&all_weak_tables_lock);
all_weak_tables = scm_i_weak_cons (ret, all_weak_tables);
scm_i_pthread_mutex_unlock (&all_weak_tables_lock);
return ret; return ret;
} }
@ -1155,6 +1169,8 @@ void
scm_init_weak_table () scm_init_weak_table ()
{ {
#include "libguile/weak-table.x" #include "libguile/weak-table.x"
scm_i_register_async_gc_callback (vacuum_all_weak_tables);
} }
/* /*