www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.bugs - Destructor Bug?

reply AJG <AJG_member pathlink.com> writes:
Hi,

I took me a while to find and reduce this to a minimal example. I was going nuts
thinking it was my database abstraction layer. I think it's a bug in D.










Segmentation Fault w/ DMD 0.128 on linux.

Cheers,
--AJG.
Jul 30 2005
parent reply "Uwe Salomon" <post uwesalomon.de> writes:
 I took me a while to find and reduce this to a minimal example. I was  
 going nuts
 thinking it was my database abstraction layer. I think it's a bug in D.










 Segmentation Fault w/ DMD 0.128 on linux.
http://www.digitalmars.com/d/class.html#destructors "When the garbage collector calls a destructor for an object of a class that has members that are references to garbage collected objects, those references are no longer valid. This means that destructors cannot reference sub objects." So this is documented and correct behaviour. Ciao uwe
Jul 31 2005
next sibling parent reply AJG <AJG_member pathlink.com> writes:
Hi,

http://www.digitalmars.com/d/class.html#destructors
"When the garbage collector calls a destructor for an object of a class  
that has members that are references to garbage collected objects, those  
references are no longer valid. This means that destructors cannot  
reference sub objects."

So this is documented and correct behaviour.
Is there any way around this? I'd like to close a database connection upon destruction, but this won't let me. In addition, what's the point of a destructor that's can't access sub-objects? How would you do clean-up operations that are not strictly memory-related? Like clearing a mutex, or releasing a lock on a shared resource (such as a hardware device), or performing database finalization (closing connections, committing changes, etc.)? Is there a preferred way of doing that right now? Thanks for the help! --AJG.
Jul 31 2005
next sibling parent reply "Uwe Salomon" <post uwesalomon.de> writes:
 Is there any way around this? I'd like to close a database connection  
 upon destruction, but this won't let me.
I had a similar problem when implementing signals&slots. You can create the sub-object on the C heap with malloc(). Or close the connection in the sub-object's destructor. This problem can happen if there are difficult relations between objects, but mostly it shows imperfect design, i think. If a class owns a child, and it has to call finalization routines of it when being destructed, that somehow means that the child does not fully take care of its own destruction, right? So it does not fully protect its invariant. Either that should be changed, or if that is not possible a sub-struct should be used, and the parent class takes care of protecting the invariant. Ciao uwe
Jul 31 2005
parent reply AJG <AJG_member pathlink.com> writes:
Hi,

 Is there any way around this? I'd like to close a database connection  
 upon destruction, but this won't let me.
I had a similar problem when implementing signals&slots. You can create the sub-object on the C heap with malloc(). Or close the connection in the sub-object's destructor.
Hm... maybe this could work. What's the equivalent code using malloc? I mean, how do you call the object's constructor(s) using this method?
This problem can happen if there are difficult relations between objects,  
but mostly it shows imperfect design, i think. If a class owns a child,  
and it has to call finalization routines of it when being destructed, that  
somehow means that the child does not fully take care of its own  
destruction, right? So it does not fully protect its invariant. Either  
that should be changed, or if that is not possible a sub-struct should be  
used, and the parent class takes care of protecting the invariant.
It very well might be a problem in one class' design. However, just because this is the case, doesn't mean we shouldn't be allowed to do it. We should be allowed, precisely to fix this kind of problem when the code is not ours. What about legacy code and libraries? Sometimes these things don't follow ideal OO design, and as it stands, we can't properly fix it in the destructor. In addition, what about when clean-up isn't that simple? What if there are options that need to be specified? As a trivial example, you could have: void DB::close(bool commit) { if (commit) m_Conn.commit(); m_Conn.close(); } Sure, you could move those parameters to the constructor as AutoCommit, but why move finalization logic to the constructor? IMO that's why we have destructors, to fully take care of cleanup for objects that don't do it easily and automatically. Thanks for the help, --AJG.
Jul 31 2005
parent "Uwe Salomon" <post uwesalomon.de> writes:
 Hm... maybe this could work. What's the equivalent code using malloc? I  
 mean, how do you call the object's constructor(s) using this method?
Hmm, as it is legacy code, i fear that it is not possible. You can look into http://www.digitalmars.com/techtips/class_objects.html But this is more like a hack. The correct way would be to overload operator new and delete for the class. See http://www.digitalmars.com/d/memory.html#newdelete And you are right, in this case there is really a need for the destructor to access sub-objects. Anyways, imagine two objects that are "linked together": class A { B b; } class B { A a; } One of them has to be deleted first. Which one, that is undetermined. Thus you cannot be sure to be able to access b in A's destructor and vice versa. The GC would have to examine this kind of relationships, and they could be much more complex. That's the reason for the current rule. Another solution, by the way: use delete on your object. In this case you *can* reference sub-objects. Ciao uwe
Aug 01 2005
prev sibling next sibling parent reply "Uwe Salomon" <post uwesalomon.de> writes:
To answer your other question fully:

 In addition, what's the point of a destructor that's can't access  
 sub-objects?
It would make the job of the garbage collector more difficult, i guess.
 How would you do clean-up operations that are not strictly  
 memory-related? Like
 clearing a mutex, or releasing a lock on a shared resource (such as a  
 hardware
 device), or performing database finalization (closing connections,  
 committing changes, etc.)?
I can only think of the following solutions: * Allocate the objects somewhere else than the GC heap. * Use structs. This is somehow similar to the next one: * Change the design. Example: a file object could have a "autoClose" property that is set to true by the parent if necessary, and the file destructor automatically closes the attached handle in this case. The third one should be the preferred, of course. Ciao uwe
Jul 31 2005
parent AJG <AJG_member pathlink.com> writes:
Hi,

To answer your other question fully:

 In addition, what's the point of a destructor that's can't access  
 sub-objects?
It would make the job of the garbage collector more difficult, i guess.
I wonder whether it makes it trivially more difficult or not. It could also be that Walter specifially doesn't want that kind of behaviour in the destructor (for philosophical reasons, a la opAssign). Although that would just be mean. ;) destructor, right?
 How would you do clean-up operations that are not strictly  
 memory-related? Like
 clearing a mutex, or releasing a lock on a shared resource (such as a  
 hardware
 device), or performing database finalization (closing connections,  
 committing changes, etc.)?
I can only think of the following solutions: * Allocate the objects somewhere else than the GC heap.
Yeah, this could be an option. I posted a question about this on the other message.
* Use structs. This is somehow similar to the next one:
* Change the design. Example: a file object could have a "autoClose"  
property that is set to true by the parent if necessary, and the file  
destructor automatically closes the attached handle in this case.
The third one should be the preferred, of course.
Unfortunately, I can't do such a thing because it's not my code. Thanks for the help, --AJG.
Jul 31 2005
prev sibling parent reply Dave <Dave_member pathlink.com> writes:
In article <dcj0gn$c7r$1 digitaldaemon.com>, AJG says...
Hi,

http://www.digitalmars.com/d/class.html#destructors
"When the garbage collector calls a destructor for an object of a class  
that has members that are references to garbage collected objects, those  
references are no longer valid. This means that destructors cannot  
reference sub objects."

So this is documented and correct behaviour.
Is there any way around this? I'd like to close a database connection upon destruction, but this won't let me. In addition, what's the point of a destructor that's can't access sub-objects? How would you do clean-up operations that are not strictly memory-related? Like clearing a mutex, or releasing a lock on a shared resource (such as a hardware device), or performing database finalization (closing connections, committing changes, etc.)? Is there a preferred way of doing that right now? Thanks for the help! --AJG.
Would something like this cause a complete re-write for you? #class DBConn #class Test #void main() { new Test(); } I would think this would be the 'preferred' pattern since (IMHO) an object like DBConn should always clean-up after itself anyhow. Calling delete on the object is allowed, the object just can't be dereferenced in the parent dtor. - Dave
Jul 31 2005
parent reply AJG <AJG_member pathlink.com> writes:
Hi,

Would something like this cause a complete re-write for you?

#class DBConn







#class Test






#void main() { new Test(); }

I would think this would be the 'preferred' pattern since (IMHO) an object like
DBConn should always clean-up after itself anyhow.
The problem is that the object I'm using is not my own. I'm using Jeremy Cowgar's DDBI, which has explicit connect() and close() functions, and no destructor. What I had before worked because I inherited from the object, and I could call close() as my own function in my own destructor. But this forced me to inherit from a specific DB, like MySQL. To enable support for any DB type (PG, SQLite, etc.), I need to contain the database as a sub-object, and then create it depending on a parameter. When I made this change, the segmentation faults started because of the problem in the destructor. I could hack DDBI to auto-close the connection, but I'd rather not, since that's not its interface. In my opinion destructors are broken, and very limited. We should be able to reference sub-objects that don't do automatic cleanup precisely for cases like this. Thanks for the help. --AJG.
Jul 31 2005
next sibling parent reply Manfred Nowak <svv1999 hotmail.com> writes:
AJG <AJG_member pathlink.com> wrote:

[...]
 In my opinion destructors are broken, and very limited. We
 should be able to reference sub-objects that don't do automatic
 cleanup precisely for cases like this. 
Why is it not possible to embed the foreign object into a class with a subclass, whose destrcutor calls the cleanup procedure of the foreign object? Then because the subclass' destructor is executed before the embedding class' destructor the cleanup procedure will be executed before the foreign object is destroyed. -manfred
Jul 31 2005
parent reply AJG <AJG_member pathlink.com> writes:
Hi, 

Why is it not possible to embed the foreign object into a class with 
a subclass, whose destrcutor calls the cleanup procedure of the 
foreign object?
Because it's not just one type of object. It's a whole collection of possible sub-classes of a BaseDatabase class.
Then because the subclass' destructor is executed before the 
embedding class' destructor the cleanup procedure will be executed 
before the foreign object is destroyed.
Yeah, it's a possible workaround. At any rate, if you are doing this, it's essentially a hack to bypass the normal destructor restrictions, which shouldn't be there in the first place. Cheers, --AJG.
Jul 31 2005
parent reply Manfred Nowak <svv1999 hotmail.com> writes:
AJG <AJG_member pathlink.com> wrote:

Why is it not possible to embed the foreign object into a class
with a subclass, whose destrcutor calls the cleanup procedure of
the foreign object?
Because it's not just one type of object. It's a whole collection of possible sub-classes of a BaseDatabase class.
I do not understand this argument. If the subclasses represent different database types, then why is the cleanup procedure the same in all cases?
Then because the subclass' destructor is executed before the 
embedding class' destructor the cleanup procedure will be
executed before the foreign object is destroyed.
Yeah, it's a possible workaround.
No it is not a workaround as I found out: | The garbage collector is not guaranteed to run the destructor | for all unreferenced objects. And this restrictions follows in a natural way from the GC.
 bypass the normal destructor
 restrictions, which shouldn't be there in the first place.
Because of the GC they must be there and if you want to guarantee the execution of the cleanup operation you have to resort to deleting explictely. -manfred
Jul 31 2005
next sibling parent "Regan Heath" <regan netwin.co.nz> writes:
 Because of the GC they must be there and if you want to guarantee the
 execution of the cleanup operation you have to resort to deleting
 explictely.
Or use the "auto" keyword. Regan
Jul 31 2005
prev sibling parent reply AJG <AJG_member pathlink.com> writes:
Hi,

Why is it not possible to embed the foreign object into a class
with a subclass, whose destrcutor calls the cleanup procedure of
the foreign object?
Because it's not just one type of object. It's a whole collection of possible sub-classes of a BaseDatabase class.
I do not understand this argument. If the subclasses represent different database types, then why is the cleanup procedure the same in all cases?
They all override a virtual void close(); finalization method to terminate the connection. In order for your solution to work, I'd have to wrap up (inherit from) every subclass in another object that calls this method as a protected member, whereas if I could reference the object in the destructor, I could just call .close() once in one single object. I don't know if that makes sense. If it doesn't could you provide an example of what you mean?
Then because the subclass' destructor is executed before the 
embedding class' destructor the cleanup procedure will be
executed before the foreign object is destroyed.
Yeah, it's a possible workaround.
No it is not a workaround as I found out: | The garbage collector is not guaranteed to run the destructor | for all unreferenced objects.
Oh, great. Yet another restriction.
And this restrictions follows in a natural way from the GC.

 bypass the normal destructor
 restrictions, which shouldn't be there in the first place.
Because of the GC they must be there and if you want to guarantee the execution of the cleanup operation you have to resort to deleting explictely.
Couldn't the GC just tag the objects that are needed and keep those from being collected until their containers' run their respective destructors? Cheers, --AJG.
Aug 01 2005
parent Manfred Nowak <svv1999 hotmail.com> writes:
AJG <AJG_member pathlink.com> wrote:

 I don't know if that makes sense. If it doesn't could you
 provide an example of what you mean? 
We need not try to explore this any further because it will not yield a solution.
 Couldn't the GC just tag the objects that are needed and keep
 those from being collected until their containers' run their
 respective destructors? 
D is built around the type of GC currently used. Especially tagging does not help in case of cyclic references. -manfred
Aug 01 2005
prev sibling parent "Ben Hinkle" <bhinkle mathworks.com> writes:
 I could hack DDBI to auto-close the connection, but I'd rather not, since 
 that's
 not its interface. In my opinion destructors are broken, and very limited. 
 We
 should be able to reference sub-objects that don't do automatic cleanup
 precisely for cases like this.
I agree destructors in D are very limited. They seem to be only useful for a class that owns an external resource that must be destroyed when the object is destroyed. To me that's ok if it gives the GC enough freedom to be faster and more predictable. I can see how one could say dtors in D are broken if that own an external resource do not have dtors then I'd request one from the author as a safety net. A user class holding a reference to a Database should not have a dtor since those classes do not own the external resource. In any case if the database dtor ends up closing the external resource it could very well be a bug since the user code should have closed the database long before the dtor ended up running.
Aug 01 2005
prev sibling parent reply Russ Lewis <spamhole-2001-07-16 deming-os.org> writes:
The issue here is that when 2 objects become garbage at the same time, 
there is no defined order in which they are cleaned up.  So, sometimes, 
the code like you wrote below will work, because the holder of the 
reference is destroyed first; other times, it won't, because they are 
cleaned up in the opposite order.

The reason that this problem exists is because the GC must deal with 
loops; when a loop of object references goes out of scope, what gets 
deleted first?



*** 'auto' solution ***
One solution is to use 'auto' objects. 
http://digitalmars.com/d/class.html says that auto objects don't have 
this ordering issue.  However, I don't think that you can reassign 
'auto' objects, so this is not an option if you will sometimes have to 
change the reference variable.



*** manual refcounting solution ***
Another solution is to find a way to make sure that the refered-to 
object is not garbage until after the referer dies; one way to do that 
is with an outside reference.  The code example below has an associative 
array which stores references to objects in a global; these references 
can be added to or removed as needed, to ensure ordering:

uint[Object] referenceTable;
void AddRef(Object o) {
   // you can write more efficient code (avoid the double lookup in
   // the AA) but this is easy to read for learning purposes.  Also,
   // this code is not thread-safe.

   if(o in referenceTable)
     referenceTable[o]++;
   else
     referenceTable[o] = 1;
}
void DelRef(Object o) {
   // again, double-lookup and thread safety issues.

   assert(o in referenceTable);
   referenceTable[o]--;
   if(referenceTable[o] == 0)
     referenceTable.remove(o);
       // don't delete, since somebody else in the world
       // might still have a reference.  Just drop this
       // reference, making it possible that this *might*
       // be garbage on the next pass.
}

class Test {
   Object obj = null;
   this () { obj = new Object(); AddRef(obj); }
   ~this() { obj.toString();     DelRef(obj); }
}

Uwe Salomon wrote:
 I took me a while to find and reduce this to a minimal example. I was  
 going nuts
 thinking it was my database abstraction layer. I think it's a bug in D.










 Segmentation Fault w/ DMD 0.128 on linux.
http://www.digitalmars.com/d/class.html#destructors "When the garbage collector calls a destructor for an object of a class that has members that are references to garbage collected objects, those references are no longer valid. This means that destructors cannot reference sub objects." So this is documented and correct behaviour. Ciao uwe
Aug 01 2005
parent reply "Walter" <newshound digitalmars.com> writes:
Generally speaking, destructors called by the garbage collector are not
terribly useful, for the reasons you mentioned. Destructors called when they
go out of scope (i.e. auto objects) are very useful.
Aug 02 2005
next sibling parent reply AJG <AJG_member pathlink.com> writes:
Hi Walter,

Generally speaking, destructors called by the garbage collector are not
terribly useful, for the reasons you mentioned. Destructors called when they
go out of scope (i.e. auto objects) are very useful.
What about getting the compiler to complain if there are sub-object member references in the destructor of a non-auto object, since they are not allowed? Would that be possible? It'd be an improvement. Thanks, --AJG.
Aug 02 2005
parent reply "Uwe Salomon" <post uwesalomon.de> writes:
 Generally speaking, destructors called by the garbage collector are not
 terribly useful, for the reasons you mentioned. Destructors called when  
 they
 go out of scope (i.e. auto objects) are very useful.
What about getting the compiler to complain if there are sub-object member references in the destructor of a non-auto object, since they are not allowed? Would that be possible? It'd be an improvement.
Hmm, but sub-object references are allowed for auto objects and objects that are explicitly deleted... This somehow seems like a design weakness to me. :( Ciao uwe
Aug 02 2005
parent reply AJG <AJG_member pathlink.com> writes:
Hi,

 Generally speaking, destructors called by the garbage collector are not
 terribly useful, for the reasons you mentioned. Destructors called when  
 they
 go out of scope (i.e. auto objects) are very useful.
What about getting the compiler to complain if there are sub-object member references in the destructor of a non-auto object, since they are not allowed? Would that be possible? It'd be an improvement.
Hmm, but sub-object references are allowed for auto objects and objects that are explicitly deleted... This somehow seems like a design weakness to me. :(
Yeah, but those would be fine. Auto-objects would be allowed. Delete Object would be allowed. Everything else (which is illegal, if I'm not mistaken), is disallowed. Right? Cheers, --AJG.
Aug 02 2005
parent reply "Uwe Salomon" <post uwesalomon.de> writes:
 Hmm, but sub-object references are allowed for auto objects and objects
 that are explicitly deleted... This somehow seems like a design weakness
 to me. :(
Yeah, but those would be fine. Auto-objects would be allowed. Delete Object would be allowed. Everything else (which is illegal, if I'm not mistaken), is disallowed. Right?
Yep. But how should the compiler know that? class SomeClass { SomeOtherClass m_cl; // ... ~this() { m_cl.doSomething(); } } SomeClass[] array; int main() { array ~= new SomeClass; array ~= new SomeClass; delete array[0]; } In this case the compiler would have to conclude that the second object in the array will be found by the GC. How to do that during compilation time? Ciao uwe
Aug 02 2005
parent reply AJG <AJG_member pathlink.com> writes:
Hi,

 Hmm, but sub-object references are allowed for auto objects and objects
 that are explicitly deleted... This somehow seems like a design weakness
 to me. :(
Yeah, but those would be fine. Auto-objects would be allowed. Delete Object would be allowed. Everything else (which is illegal, if I'm not mistaken), is disallowed. Right?
Yep. But how should the compiler know that? class SomeClass { SomeOtherClass m_cl; // ... ~this() { m_cl.doSomething(); } } SomeClass[] array; int main() { array ~= new SomeClass; array ~= new SomeClass; delete array[0]; }
In this case the compiler would have to conclude that the second object in  
the array will be found by the GC. How to do that during compilation time?
Ohhhhh... I see what you mean now. So if you call explicit delete on the _super_ object, then its destructor can safely access sub-object references, is that correct? That's just confusing and error-prone in my opinion. So depending on how it's deleted (GC vs. explicit) an object might (a) work perfectly, or (b) crash hard with a segmentation fault. I don't think that kind of design (and the behaviour that it causes) should be encouraged. I'm not sure what the compiler can do but I think it's a problem that should be thought about. I think (for now) maybe the compiler should emit at least a warning when referencing sub-objects in the destructor, given the hidden danger of doing so. Although fundamentally, the real solution would be to fix the GC so that it can deal with full-featured destructors. Cheers, --AJG.
Aug 02 2005
parent "Uwe Salomon" <post uwesalomon.de> writes:
 Ohhhhh... I see what you mean now. So if you call explicit delete on the  
 _super_
 object, then its destructor can safely access sub-object references, is  
 that correct?
Exactly. Perhaps this would be a solution to your problem?
Aug 02 2005
prev sibling parent reply "Ben Hinkle" <bhinkle mathworks.com> writes:
"Walter" <newshound digitalmars.com> wrote in message 
news:dcn7ss$1drb$1 digitaldaemon.com...
 Generally speaking, destructors called by the garbage collector are not
 terribly useful, for the reasons you mentioned. Destructors called when 
 they
 go out of scope (i.e. auto objects) are very useful.
This got me thinking about the uses of auto classes I've seen in D so far (note by auto objects I assume you mean an instance of an auto class). I've run into 4 auto classes: MmFile, ExeModule, PerformanceCounterScope (in phobos) and ScopedLock (in the locks library). The first two shouldn't be auto IMHO and MmFile is no longer auto. The last two are auto in order to ensure an action is performed at scope exit - either stop a performance counter or release a lock. Both of these auto classes can be replaced with the following auto class. By using this ScopeExit one can separate the concept of dtor called by the GC from the dtor called by exiting a scope. Also by reusing ScopeExit one can avoid ever having to define an auto class by hand. // the only auto class you'll ever need auto class ScopeExit { void delegate() dg; this(void delegate() dg) { this.dg = dg; } ~this(){ dg(); } } // example class Foo { void close() { printf("closing a Foo\n"); } } int main() { Foo f = new Foo; { auto ScopeExit exit = new ScopeExit(&f.close); printf("before exiting scope\n"); } printf("after exiting scope\n"); return 0; } Replacement code for the two auto classes PerformanceCounterScope and ScopedLock the code would look something like PerformanceCounterScope scope = new PerformanceCounterScope(counter); // original ScopeExit exit = new ScopeExit(&counter.stop); counter.start(); // new code ScopedLock scope = new ScopedLock(lock); // original code ScopeExit exit = new ScopeExit(&lock.unlock); lock.lock(); // new code
Aug 02 2005
parent Holger <Holger_member pathlink.com> writes:
In article <dcojar$2ggu$1 digitaldaemon.com>, Ben Hinkle says...
"Walter" <newshound digitalmars.com> wrote in message 
news:dcn7ss$1drb$1 digitaldaemon.com...
 Generally speaking, destructors called by the garbage collector are not
 terribly useful, for the reasons you mentioned. Destructors called when 
 they
 go out of scope (i.e. auto objects) are very useful.
This got me thinking about the uses of auto classes I've seen in D so far (note by auto objects I assume you mean an instance of an auto class). I've run into 4 auto classes: MmFile, ExeModule, PerformanceCounterScope (in phobos) and ScopedLock (in the locks library). The first two shouldn't be auto IMHO and MmFile is no longer auto. The last two are auto in order to ensure an action is performed at scope exit - either stop a performance counter or release a lock. Both of these auto classes can be replaced with the following auto class. By using this ScopeExit one can separate the concept of dtor called by the GC from the dtor called by exiting a scope. Also by reusing ScopeExit one can avoid ever having to define an auto class by hand. // the only auto class you'll ever need auto class ScopeExit { void delegate() dg; this(void delegate() dg) { this.dg = dg; } ~this(){ dg(); } } // example class Foo { void close() { printf("closing a Foo\n"); } } int main() { Foo f = new Foo; { auto ScopeExit exit = new ScopeExit(&f.close); printf("before exiting scope\n"); } printf("after exiting scope\n"); return 0; } Replacement code for the two auto classes PerformanceCounterScope and ScopedLock the code would look something like PerformanceCounterScope scope = new PerformanceCounterScope(counter); // original ScopeExit exit = new ScopeExit(&counter.stop); counter.start(); // new code ScopedLock scope = new ScopedLock(lock); // original code ScopeExit exit = new ScopeExit(&lock.unlock); lock.lock(); // new code
Neat! I like the idea. Cheers, Holger
Aug 02 2005