bug#40194: Weak sets keep growing

Ludovic Courtès <[email protected]> Wed, 18 Feb 2026 16:14:21 +0100
Newsgroups gmane.lisp.guile.bugs
Message-ID <[email protected]>
Hi Olivier,

Olivier Dion <[email protected]> skribis:

> From 2adaf872aeb3b84be9cf8af06f958ea4b731f66c Mon Sep 17 00:00:00 2001
> From: Olivier Dion <[email protected]>
> Date: Tue, 17 Feb 2026 11:37:35 -0500
> Subject: [PATCH] Fix weak sets not shrinking after GC.
>=20
> The `scm_i_register_async_gc_callback' mechanism used finalizers to
> trigger post-GC callbacks for vacuuming weak sets and tables.  This
> approach stopped working reliably, causing weak sets to grow
> indefinitely even after GC.
>=20
> Replace the finalizer-based callback mechanism with direct integration
> into `scm_after_gc_c_hook'.  A new hook callback,
> `scm_i_notify_finalizer_thread', is registered with `scm_after_gc_c_hook'.
> When the hook runs after GC, this callback either notifies the finalizer
> thread (if running) to invoke finalizers, or runs them synchronously and
> spawns the finalizer thread if allowed.  The vacuum functions for weak
> sets and tables are called from `scm_run_finalizers'.
>=20
> When `(gc)' is called explicitly from Scheme, finalizers are run
> immediately.  An atomic flag prevents double-vacuuming when the GC hook
> fires afterward.
>=20
> Fixes <https://issues.guix.gnu.org/40194>.
>=20
> * libguile/atomics-internal.h (scm_atomic_swap_pointer): New function.
> * libguile/finalizers.c: Include weak-set.h and weak-table.h.
> (queue_finalizer_async): Remove.
> (async_gc_finalizer): Remove.
> (scm_i_register_async_gc_callback): Remove.
> (allow_finalizer_thread): New static variable.
> (scm_i_finalizer_pre_fork): Use `allow_finalizer_thread' instead of
> `GC_set_finalizer_notifier'.
> (scm_i_notify_finalizer_thread): New function.
> (scm_set_automatic_finalization_enabled): Use `scm_after_gc_c_hook'
> instead of `GC_set_finalizer_notifier'.
> (vacuum_off): New static variable.
> (scm_run_finalizers): Call `scm_i_vacuum_weak_sets' and
> `scm_i_vacuum_weak_tables' using atomic flag to prevent double-vacuuming.
> (scm_i_run_finalizers_and_turnoff_vacuums): New function.
> (scm_init_finalizers): Use `scm_after_gc_c_hook'.
> (scm_init_finalizer_thread): Simplify to just set `allow_finalizer_thread=
'.
> * libguile/finalizers.h (scm_i_register_async_gc_callback): Remove.
> (scm_i_run_finalizers_and_turnoff_vacuums): New declaration.
> * libguile/gc.c: Include finalizers.h.
> (scm_gc): Call `scm_i_run_finalizers_and_turnoff_vacuums' instead of
> `GC_invoke_finalizers'.
> (scm_init_gc): Disable `GC_set_finalizer_notifier'.
> * libguile/weak-set.c (scm_i_vacuum_weak_sets): Rename from
> `vacuum_all_weak_sets' and make non-static.
> (scm_init_weak_set): Remove `scm_i_register_async_gc_callback' call.
> * libguile/weak-set.h (scm_i_vacuum_weak_sets): New declaration.
> * libguile/weak-table.c (scm_i_vacuum_weak_tables): Rename from
> `vacuum_all_weak_tables' and make non-static.
> (scm_init_weak_table): Remove `scm_i_register_async_gc_callback' call.
> * libguile/weak-table.h (scm_i_vacuum_weak_tables): New declaration.
>=20
> Signed-off-by: Olivier Dion <[email protected]>

I totally lost track of the issue bug I=E2=80=99m glad you=E2=80=99re worki=
ng on it.

I only had a quick look but the fix and its explanation make sense to
me.  Do you have ideas though as to why and when the previous approach
=E2=80=9Cstopped working reliably=E2=80=9D as you wrote?

Do you have tests showing that this patch does indeed fix the problem?

This is a quite sensitive piece of code so I=E2=80=99m worried about possib=
le
issues we did not anticipate. :-)

Thanks,
Ludo=E2=80=99.