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