Re: [feature/igc3] weak hash table lookup can return a finalized (PVEC_FREE) object

Pip Cet <[email protected]>
Newsgroups gmane.emacs.devel
Message-ID <[email protected]>
<[email protected]> writes:

> Hi. This is about an immediate crash upon startup with igc3 and doom emacs.

Hi!

> What follows is a LLM-produced report and attached a suggested
> fix that has been working stable for a few weeks now.

Is the fix LLM-produced as well?

> I can reproduce reliably and retrieve more debug information if needed.
> Given the report and fix size I hope this is helpful.

Certainly!

> ----------------
>
> Branch feature/igc3, commit 07b35c82b7d ("Move 'read_commit_limit' to
> sysdep.c"), x86_64-pc-linux-gnu, --with-mps.
>
> A lookup in a weak hash table can return an object that the collector
> has already finalized and splatted into a PVEC_FREE.  Lisp then sees a
> "function" that is not a function, and printing the resulting error
> aborts Emacs.

Indeed, but the actual problem is slightly more complicated: in most GC
systems, including pre-MPS Emacs, "finalization" happens when an object
is about to be destroyed: it's the last thing to happen to an object.

In MPS, however, "finalization" only means that only weak references to
an object remain; this triggers the finalization code, but there is no
guarantee that a new strong reference to the object hasn't been created
by the time the finalization code is actually called (in MPS, this
happens asynchronously, and the mechanism to do so is quite expensive).

The MPS approach has its advantages (in particular, you can veto the
actual destruction of an object in the finalizer, which is useful for
debugging).

We're currently pretending that MPS finalization is the right thing for
Emacs finalization to use, but this isn't true: IMHO, the correct fix is
to add last-thing-to-happen-to-an-object finalization to MPS. I have
code somewhere to do that, synchronously, when we actually free the
memory associated with an MPS object (instead of copying it).

However, I've never been quite happy with it, because it means that we
occasionally walk a segment once more than previously, to find objects
in it that need to be finalized. That's a lot cheaper than the current
approach, but it also happens when Emacs isn't idle, which is a bad
time.

All that said, your fix looks like it would definitely improve things,
and I rather suspect I meant to do something like it but forgot to add
the actual code.

> How I ran into it: Emacs crashed about a second into starting a Doom
> Emacs configuration, every time.  The victim was a combined method in
> cl--generic-combined-method-memoization, which is
>
>     (make-hash-table :test #'equal :weakness 'value)
>
> Two of its values are splatted on every startup (the cl-generic-
> generalizers methods for EIEIO defclass and for cl-deftype derived
> types).

Just to make sure I understand, when you say they were "splatted", you
mean that their finalizers were called, but that they weren't removed
from the hash table, correct?

> gethash returned one of them, funcall signalled
> (invalid-function #<splatted closure>), and the error handler formatted
> the error with %s, which reached print_vectorlike_unreadable and
> emacs_abort.  In the core dump the table's kv.values vector still holds
> two PVEC_FREE objects, next to a live key
> (#s(cl--generic cl-generic-generalizers ...) . METHODS).

> Proposed fix, attached as igc-weak-hash-fix.diff: skip dead entries in
> the lookup, the way DOHASH_WEAK already does.

Ah, so that's what I actually did :-)

> With this, both reproducers above are clean (gethash reports a miss) and
> the Doom configuration starts normally.  I instrumented the two skips
> before removing the counters: exactly two lookups per startup matched a
> key whose value had been splatted, reproducibly, on every start.
>
> Two further points, which I have not tried to fix:
>
> - finalize (src/igc.c) only dispatches to finalize_vector; everything
>   else falls through "default: break".  So the PVEC_FREE marker exists
>   only for pseudovectors.  A weak table whose weakly held side is a
>   cons, string or symbol goes through the same window with no marker at
>   all, and neither DOHASH_WEAK nor the check above can detect it - the
>   lookup will hand back an object that should have been evicted.

I'm not sure I follow: conses, strings, and symbols need no
(Emacs-style) finalization, do they? Can you describe this bug in more
detail?

> - It may be worth asking whether the entry can be removed at the same
>   time as the splat, rather than a cycle later, which would close the
>   window instead of papering over it.

If we cannot live with the limitations of the current approach, I think
adding eager finalization to MPS is the easiest way forward, and the
performance hit should be acceptable: it's a lot cheaper than MPS
finalization.

> The attachment also contains a second, optional hunk: making
> print_vectorlike_unreadable print "#<dead object>" for PVEC_FREE under
> HAVE_MPS instead of calling emacs_abort, so that this class of bug does
> not turn a Lisp-level error into a crash while the error is being
> printed.

If that helps debugging things, I think we should apply both.

Thanks!

Pip
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.