Subject: Re: bug#77338: feature/igc: when can we run finalizers?

From: Pip Cet <[email-masked]>
Date: Tue, 20 May 2025 16:04:08 +0000

"Pip Cet via \"Bug reports for GNU Emacs, the Swiss army knife of text editors\"" <[email-masked]> writes:

> In https://debbugs.gnu.org/cgi/bugreport.cgi?bug=77325#14, Daniel
> writes:
>
>> IGC does GC all the time --- but it's not observable because we pump
>> messages from the GC only at dedicated points and run GC hooks only in
>> response to these messages. however, notice that on the IGC branch that
>> we pump GC messages, including finalizer callbacks, on the allocation
>> path for, e.g. various pseudovectors.  That'll cause Lisp to run where
>> it wouldn't have before. Is that going to be a problem?  ISTM we can
>> either pump messages in maybe_quit() or just rely on igc_on_idle().
>
> That is indeed a bug and one which actually caused a crash for me.  It's
> related to changing the new marker structure to use finalizers rather
> than attempting to unchain markers in a finalizer rather than doing so
> in the fix method.
>
> The problem is that bignums need to be eagerly finalized or we'll run
> out of memory running the pidigits benchmark.  I'm not sure whether
> there are other objects which also allocate memory that needs to be
> freed for correctness.
>
> Another problem is that I'm not sure who uses Lisp finalizers and
> whether the slightly weaker semantics of MPS finalizers suffice for
> them: IIUC, MPS queues a finalizer when it discovers an object is no
> longer reachable except through weak references, and doesn't unqueue it
> even if a weak reference is strengthened again (maphash can do this).

Some investigation revealed that the old GC's behavior for finalizers is
even more unusual than that: a finalizer reachable only through weak
references will be run (correctly) and left in the weak hash table it
was found in (somewhat confusingly, but perhaps unavoidably).  However,
using a finalizer as a value in a weak-key hash table has the same
effect!

(setq h (make-hash-table :weakness 'key))
(puthash t (make-finalizer (lambda () (message "finalized"))) h)
(garbage-collect)

will run the finalizer, even though it never becomes unreachable.

> So a finalizer can run for an object that actually survived.  Is that
> ever acceptable?  Can we prevent it?

If the current GC's behavior is acceptable, pretty much everything MPS
can do must be, too.

Pip