[D-runtime] rt_attachDisposeEvent, monitors, delegate storage, garbage collection, and lifetime (or: you can flee in horror now)

Sean Kelly sean at invisibleduck.org
Mon Apr 16 11:24:15 PDT 2012


On Mar 17, 2012, at 9:15 PM, Alex Rønne Petersen wrote:
> 
> Now, there are a lot of subtleties about this code, but finalization
> is what's most important here. See the comment above the
> rt_attachDisposeEvent call. At first glance, the logic seems sound:
> Since the context of the dispose delegate's closure references the
> 'this' object, surely it becomes part of the delegate's context and is
> therefore GC-tracked! Indeed, the first part is true, but the second
> is not. The reason is quite simple: The delegate registered with
> rt_attachDisposeEvent is stored into memory allocated through good old
> libc. This means that the context of the delegate is effectively
> unreachable to the GC, thus *rendering the Weak(T) object
> unreachable*!
> 
> It turns out that, in druntime, monitors are allocated from the native
> heap, and not through the GC. This makes sense, for the most part,
> because they aren't going to have anything in them that needs GC
> tracking. Or so the assumption might have been originally. This no
> longer holds true, now that we can store full-blown delegates into the
> monitor's devt array.

This is done for two reasons.  First, nothing in the monitor should unintentionally keep the object alive.  But more importantly, the monitor should functional when the GC finalizes the object, which would not be the case if the monitor were GC-allocated.  This is a known limitation of using core.sync.mutex as an object monitor, and I don't want to impose the same limitation on the built-in monitor.

> Now you might ask: Why don't I just add a finalizer to Weak(T) which
> unregisters the finalization callback for the hidden object, such that
> this lifetime issue cannot happen? Why, the reason is simple! It could
> potentially cause a deadlock (but in practice, causes a segmentation
> fault due to some druntime voodoo).

It doesn't have to cause a dealock.  Finalizers are run after all threads have been restarted, so there's no risk of blocking on a lock held by a suspended thread.  The rest is just a matter of designing your weak pointer properly.  The signal/slot code has a similar requirement, though it's even trickier there because user-defined callbacks are involved.


More information about the D-runtime mailing list