Subject: bug#10836: guardians and weak references

From: Andy Wingo <[email-masked]>
Date: Sun, 19 Feb 2012 22:03:11 +0100

On Fri 17 Feb 2012 15:52, Andy Wingo <[email-masked]> writes:

>   (define x (list 1))
>   (define g (make-guardian))
>   (define p (make-object-property ))
>   (g x)
>   (set! (p x) 100)
>   $1 = 100
>   (p x)
>   $2 = 100
>   (set! x #f)
>   (gc) (gc) (gc)
>   (g)
>   $3 = (1)
>   (p $3)
>   $4 = #f
>
> The problem: weak references are dropped at finalization time, when
> guardians do their magic.  If the guardian resuscitates the value, weak
> references (like those in the object property) are lost.  This is in
> contradiction to what the manual says on the subject:
>
>      Being an element in a weak vector, a key in a hash table with weak
>      keys, or a value in a hash table with weak values does not prevent
>      an object from being returned by a guardian.  But as long as an
>      object can be returned from a guardian it will not be removed from
>      such a weak vector or hash table.  In other words, a weak link
>      does not prevent an object from being considered collectable, but
>      being inside a guardian prevents a weak link from being broken.

I have looked at this issue in depth.  Disappearing links are indeed
broken at finalization time (when the object is still reachable, from
the finalizer itself).

I tried reimplementing weak tables using finalizers.  (The finalizers
remove elements from the table).  As long as we ensure that the
guardian's finalizers run first, which is possible for Guile to do, that
does fix this bug.

However, using finalizers has the downside that it introduces races with
custom hash/equality predicates.  Consider:

  * Add an entry X -> Y to a weak-key table.

  * X becomes unreachable.  Libgc enqueues a finalizer.

  * Lookup the handle for (X -> Y), but using a custom predicate that
    relies on some aspect of X that is not its identity.  Keep X
    around.

  * The finalizer runs, removing the entry from the table.

  * Add an entry of a somehow equivalent X -> Y to a weak-key table.

  * A future lookup of X fails to return the same handle as before.

Disappearing links avoid this race, it seems, because they disappear
synchronously with the observable unreachability of the object, not
asynchronously (via finalizers).

This synchronicity is reflected in the use of the alloc-lock -- you can
only access disappearing links from within the global allocator lock.
OTOH finalizers can take more fine-grained mutexen on the structures
they manipulate, scaling better but at the cost of introducing
noticeable asynchrony.

I actually saw this bug, in which two symbols had the same characters
but different identities, with the race condition outlined above (lookup
vs finalizer).

Not sure what to do here.

Andy
-- 
http://wingolog.org/