GC BUG: Referenced object destroyed before released

Christopher Wright dhasenan at gmail.com
Sun Mar 16 08:58:40 PDT 2008


Koroskin Denis wrote:
> On Sun, 16 Mar 2008 17:15:36 +0300, Vladimir Panteleev 
> <thecybershadow at gmail.com> wrote:
> 
>> Quoting from http://www.digitalmars.com/d/1.0/class.html#destructors :
>>
>>> Furthermore, the order in which the garbage collector calls 
>>> destructors for unreference objects is not specified.
>>
>> So, it's not a bug :) You can't rely on the order of which objects 
>> will be destroyed.
>>
> 
> Yes:
>> order in which the garbage collector calls destructors for 
>> /unreferenced/ objects is not specified.
> 
> However, in this particular example Resource _is_ a referenced object, 
> unlike Owner, which is not.
> In any case, is this code wrong? If not, why does it cause acess violation?

Resource is not referenced, from the garbage collector's point of view.

An object is referenced if there is a path to it from the GC's roots 
(the stack, registers, and anything else you care to add with 
std.gc.addRoot or some such). When main exits, there's no reference to 
Owner, which means there is no way to get to Resource, so it's not 
referenced.

Getting around this would not be extremely difficult, I think. You could 
just call all destructors and then collect the memory, which might leave 
some things in a bad state. Still, if you were careful, you could 
reference other objects in destructors without segfaulting.

Or, you could build a reference graph from the memory you're collecting, 
then call destructors based on that graph. This would be pretty slow; 
I'd support it as the standard, but people would certainly want a way to 
disable it, since it's unnecessary in many situations.

If you want deterministic destruction, you can do something like this:

interface IFinalizable
{
    void finalize();
}

class MyClass : IFinalizable
{
    // You would use a mixin for this...
    IFinalizable[] finalizeParents, finalizeChildren;
    public void finalize()
    {
       if (finalized) return;
       finalized = true;
       foreach (o; finalizeParents)
       {
          o.finalize;
       }

       destroyThis();

       foreach (o; finalizeChildren)
       {
          o.finalize;
       }
    }

    // And for this.
    ~this()
    {
       finalize();
    }

    // And this would be defined per-class, of course.
    private void destroyThis()
    {
       // Do whatever you need to do.
       // It's safe to use any IFinalizable, but not anything else.
    }
}

The runtime for this is quadratic in the worst case, plus it adds 16 
bytes to each object. On the other hand, memory is cheap these days, and 
in the average case, the runtime's going to be linear.

If you have a cycle of IFinalizables, you get nondeterministic 
destruction, but no infinite loops, which is the best you could ask for.



More information about the Digitalmars-d mailing list