www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.bugs - Destructed object is still alive.

reply Bastiaan Veelo <Bastiaan.N.Veelo ntnu.no> writes:
After having explicitly deleted an object, the code below shows that it 
is still possible to call a member function of that object, albeit not 
directly. I would have expected a segmentation fault in stead. Sorry I 
could not reduce the code more.

System: dmd 0.101 on Linux.

Output:
SpinBox 0x401cbfa0 constructed.
Button constructed.
Value in 0x401cbfa0: 1
SpinBox 0x401cbfa0 deleted.
Value in 0x401cbfa0: 2
THE END.
Button deleted.
Signal deleted.
Slot deleted.

I would have expected a segfault after the "SpinBox deleted" line.


#class Signal
#{
#	this(Button owner)
#	{
#		owner.register(this);
#	}
#	
#	~this()
#	{
#		printf("Signal deleted.\n");
#	}
#	
#	void connect(Slot s)
#	{
#		_slot = s;
#	}
#	
#	void emit()
#	{
#		_slot();
#	}
#private:
#	Slot _slot;
#}
#
#
#class Slot
#{
#	this(SpinBox owner, void delegate() callBack)
#	{
#		_callBack = callBack;
#		owner.register(this);
#	}
#	
#	~this()
#	{
#		printf("Slot deleted.\n");
#	}
#	
#	void opCall()
#	{
#		_callBack();
#	}
#private:
#	void delegate() _callBack;
#}
#
#class Button
#{
#	Signal pressed;
#	
#	void press()
#	{
#		pressed.emit();
#	}
#	
#	this()
#	{
#		pressed = new Signal(this);
#		printf("Button constructed.\n");
#	}
#	
#	~this()
#	{
#		printf("Button deleted.\n");
#	}
#
#	void register(Signal s)
#	{
#		_signals[s] = s;
#	}
#
#private:
#	Signal[Signal] _signals;
#}
#
#class SpinBox
#{
#	void increase()
#	{
#		val++;
#		printf( "Value in %p: %d\n", this, val);
#	}
#	
#	Slot up;
#	
#	this()
#	{
#		printf("SpinBox %p constructed.\n", this);
#		up = new Slot(this, &increase);
#	}
#	
#	~this()
#	{
#		printf("SpinBox %p deleted.\n", this);
#	}
#	
#	void register(Slot s)
#	{
#		_slots[s] = s;
#	}
#
#private:
#	Slot[Slot] _slots;
#	int val;
#}
#
#void main()
#{
#	SpinBox moneyInTheBank = new SpinBox();
#	Button incomeButton = new Button();
#	
#	incomeButton.pressed.connect(moneyInTheBank.up);
#	incomeButton.press();
#	
#	delete moneyInTheBank;
#
#	incomeButton.press();	// Should produce a segfault.
#	
#	printf("THE END.\n");
#}
Sep 17 2004
parent reply Stewart Gordon <smjg_1998 yahoo.com> writes:
Bastiaan Veelo wrote:

 After having explicitly deleted an object, the code below shows that it 
 is still possible to call a member function of that object, albeit not 
 directly. I would have expected a segmentation fault in stead. Sorry I 
 could not reduce the code more.

IMO that's not a bug, it's a feature. Firstly, AIUI delete doesn't deallocate the memory immediately - it just calls the destructor. The memory itself isn't freed until GC comes around. Secondly, GCs often keep ownership of memory they've collected, as it's more efficient than trading memory with the OS all the time. Hence your object reference still points into the GC heap, although the address is invalid and can be overwritten any time. To put it simply, delete has one purpose - to destruct an object when you're sure there's no chance of any potential ghosts to haunt the heap. Stewart. -- My e-mail is valid but not my primary mailbox. Please keep replies on the 'group where everyone may benefit.
Sep 17 2004
parent reply Bastiaan Veelo <Bastiaan.N.Veelo ntnu.no> writes:
Stewart Gordon wrote:
 Bastiaan Veelo wrote:
 
 After having explicitly deleted an object, the code below shows that 
 it is still possible to call a member function of that object, albeit 
 not directly. I would have expected a segmentation fault in stead. 
 Sorry I could not reduce the code more.

<snip> IMO that's not a bug, it's a feature. Firstly, AIUI delete doesn't deallocate the memory immediately - it just calls the destructor. The memory itself isn't freed until GC comes around.

I see. Dropping in a std.gc.fullCollect() right after the delete indeed changes the behaviour (Bus error). I find it hard to get used to this GC stuff. In D, no references to other objects may occur in destructors. So you can not use a destructor to clean up things (like notifying other objects of our departure) the way you can in C++. Is a separate method like a discard() member function customary to clean things up in? The docs say that "The destructor is expected to release any resources held by the object." What resources would this be that you would not want to release in such a discard() method already? It seems to me that destructors have very limited use. What does deleting an object impose other than calling the destructor? If there is no destructor, would deleting an object be equivalent to nulling all references to it? This is maybe the wrong list for som many questions... Thanks, Bastiaan.
Sep 17 2004
parent reply "Ben Hinkle" <bhinkle mathworks.com> writes:
"Bastiaan Veelo" <Bastiaan.N.Veelo ntnu.no> wrote in message
news:cifg3v$2o49$1 digitaldaemon.com...
 Stewart Gordon wrote:
 Bastiaan Veelo wrote:

 After having explicitly deleted an object, the code below shows that
 it is still possible to call a member function of that object, albeit
 not directly. I would have expected a segmentation fault in stead.
 Sorry I could not reduce the code more.

<snip> IMO that's not a bug, it's a feature. Firstly, AIUI delete doesn't deallocate the memory immediately - it just calls the destructor. The memory itself isn't freed until GC comes


 I see. Dropping in a std.gc.fullCollect() right after the delete indeed
 changes the behaviour (Bus error).

I wouldn't count on the object being valid (or invalid) after a delete call. The doc does say the memory is put on the free list but it looks like the current GC doesn't actually do that. I'm curious why you care what happens after a delete?
 I find it hard to get used to this GC stuff. In D, no references to
 other objects may occur in destructors. So you can not use a destructor
 to clean up things (like notifying other objects of our departure) the
 way you can in C++. Is a separate method like a discard() member
 function customary to clean things up in? The docs say that "The
 destructor is expected to release any resources held by the object."
 What resources would this be that you would not want to release in such
 a discard() method already? It seems to me that destructors have very
 limited use.

Agreed. It is very rare that destructors should be used. In particular there are questions about when/if a destructor gets run, from which thread it gets run and what else is alive when it gets run. Conceptually they are similar to C++ destructors since they both are about releasing resources but the "random" nature of D destructors makes them very different in practice.
 What does deleting an object impose other than calling the destructor?

I assume nothing else is guaranteed.
 If there is no destructor, would deleting an object be equivalent to
 nulling all references to it?

It looks like that is implementation-dependent. It could be that deleting an object with no destructor only releases the associated synchronization monitor (if any).
 This is maybe the wrong list for som many questions...


 Thanks,
 Bastiaan.

Sep 17 2004
next sibling parent reply Bastiaan Veelo <Bastiaan.N.Veelo ntnu.no> writes:
Ben Hinkle wrote:
 "Bastiaan Veelo" <Bastiaan.N.Veelo ntnu.no> wrote in message
 news:cifg3v$2o49$1 digitaldaemon.com...
 
Stewart Gordon wrote:

Bastiaan Veelo wrote:


After having explicitly deleted an object, the code below shows that
it is still possible to call a member function of that object, albeit
not directly. I would have expected a segmentation fault in stead.
Sorry I could not reduce the code more.

<snip> IMO that's not a bug, it's a feature. Firstly, AIUI delete doesn't deallocate the memory immediately - it just calls the destructor. The memory itself isn't freed until GC comes


around.
I see. Dropping in a std.gc.fullCollect() right after the delete indeed
changes the behaviour (Bus error).

I wouldn't count on the object being valid (or invalid) after a delete call. The doc does say the memory is put on the free list but it looks like the current GC doesn't actually do that. I'm curious why you care what happens after a delete?

It was in testing my signal and slots mechanism (dcouple on dsource), in which cleaning up is one of the main focus points. I was curious as to what would happen if a user would delete an object containing signals and slots. I expected a crash, because there is no cleanup code in the destructor of that object, and was surprised that the object was still functioning afterwards. It looks like a discard() method is necessary on objects containing signals and slots, which users must call when they have no use for an object anymore. In C++, they would delete the object at this time. This will be a confusing aspect of my library. An alternative is to make objects that contain signals and/or slots (the owner) and the signals and slots themselves aware of each other's well-being, and have the destructor of the owner instruct its signals and slots /that are not deleted yet/ to disconnect. That way, deletion of the object automatically shuts it off as far as slots are concerned. I already have this functionality amongst signals and slots, but it does not include the owner. I was kind of hoping I could simplify and loose the reference to the owner in the signals and slots, but I think I am going to need it. It seems strange though that this extra data and functionality is required to make the object go away properly, and may not be needed during normal operation. I always thought GC was going to make things easier --- I hope it will sometime. Bastiaan.
Sep 17 2004
parent reply "Ben Hinkle" <bhinkle mathworks.com> writes:
"Bastiaan Veelo" <Bastiaan.N.Veelo ntnu.no> wrote in message
news:ciftct$dft$1 digitaldaemon.com...
 Ben Hinkle wrote:
 "Bastiaan Veelo" <Bastiaan.N.Veelo ntnu.no> wrote in message
 news:cifg3v$2o49$1 digitaldaemon.com...

Stewart Gordon wrote:

Bastiaan Veelo wrote:


After having explicitly deleted an object, the code below shows that
it is still possible to call a member function of that object, albeit
not directly. I would have expected a segmentation fault in stead.
Sorry I could not reduce the code more.

<snip> IMO that's not a bug, it's a feature. Firstly, AIUI delete doesn't deallocate the memory immediately - it




calls the destructor.  The memory itself isn't freed until GC comes


around.
I see. Dropping in a std.gc.fullCollect() right after the delete indeed
changes the behaviour (Bus error).

I wouldn't count on the object being valid (or invalid) after a delete


 The doc does say the memory is put on the free list but it looks like


 current GC doesn't actually do that. I'm curious why you care what


 after a delete?

It was in testing my signal and slots mechanism (dcouple on dsource), in which cleaning up is one of the main focus points. I was curious as to what would happen if a user would delete an object containing signals and slots. I expected a crash, because there is no cleanup code in the destructor of that object, and was surprised that the object was still functioning afterwards. It looks like a discard() method is necessary on objects containing signals and slots, which users must call when they have no use for an object anymore. In C++, they would delete the object at this time. This will be a confusing aspect of my library.

Why is that confusing? Writing delete foo; is not much different than foo.disconnect(); or foo.discard(); or whatever the API happens to be.
 An alternative is to make objects that contain signals and/or slots (the
 owner) and the signals and slots themselves aware of each other's
 well-being, and have the destructor of the owner instruct its signals
 and slots /that are not deleted yet/ to disconnect. That way, deletion
 of the object automatically shuts it off as far as slots are concerned.
 I already have this functionality amongst signals and slots, but it does
 not include the owner. I was kind of hoping I could simplify and loose
 the reference to the owner in the signals and slots, but I think I am
 going to need it. It seems strange though that this extra data and
 functionality is required to make the object go away properly, and may
 not be needed during normal operation.

I looked at the module dcouple.signal on dsource and noticed that SignalGenericCore's destructor calls disconnect() which loops over the slots in _slots and disconnects each one. I would be careful about looping over _slots from a destructor since _slots might already have been cleaned up - or partially cleaned up.
 I always thought GC was going to make things easier --- I hope it will
 sometime.

The GC makes memory management easier but should not be used to manage relationships between objects - or at least one should be very careful when trying to manage relationships using the GC.
 Bastiaan.

Sep 20 2004
parent Bastiaan Veelo <Bastiaan.N.Veelo ntnu.no> writes:
Ben Hinkle wrote:
<snip>
It looks like a discard() method is necessary on objects containing
signals and slots, which users must call when they have no use for an
object anymore. In C++, they would delete the object at this time. This
will be a confusing aspect of my library.

Why is that confusing? Writing delete foo; is not much different than foo.disconnect(); or foo.discard(); or whatever the API happens to be.

It is not much different, but when explicitly deleting an object with signals and/or slots can give you problems, I think that is a discomfort and a potential source of frustration. Anyway, the repository now contains a working approach to delete, I think. Signals, slots and their owning classes (that inherit SignalSlotManager) know about each others health and clean up references to each other in their destructors. Owners delete their signals and slots, but only if not destructed already. <snip>
 I looked at the module dcouple.signal on dsource and noticed that
 SignalGenericCore's destructor calls disconnect() which loops over the slots
 in _slots and disconnects each one. I would be careful about looping over
 _slots from a destructor since _slots might already have been cleaned up -
 or partially cleaned up.

Ah, somehow I assumed that the non-deterministic finalisation only applied to objects, not for example a D AA, which _slots is. Because an AA does not need to be newed, I thought it would be alive as long as it is in scope, including the destructor. But you are probably right. It might be possible to use a specially implemented doubly linked list as the container of slots. Elements would remove themselves from the list from within their destructor, pulling the same trick as Signals and Slots do, which mutually disconnect each other when they are destructed. I will end up with densely packed cross-references... Thanks for the guidance, this is very valuable to me. Bastiaan.
Sep 21 2004
prev sibling parent reply Stewart Gordon <Stewart_member pathlink.com> writes:
In article <cifife$2s1u$1 digitaldaemon.com>, Ben Hinkle says...
<snip>
 Agreed.  It is very rare that destructors should be used.  In 
 particular there are questions about when/if a destructor gets run, 
 from which thread it gets run and what else is alive when it gets 
 run.

But I've seen it said that you can override the question about if by calling gc_term when your program exits....
 Conceptually they are similar to C++ destructors since they both 
 are about releasing resources but the "random" nature of D 
 destructors makes them very different in practice.

I've had no problems simply using destructors to free resources. However, when one destructor wants to call another, things get difficult, and the question of what else is alive is indeed a question. I'm working on a tree control implementation for SDWF 0.4. AIUI, you don't need to free the handles of individual tree items, but the Item objects still present a challenge. This is because the TreeView class needs to be able to get the current selection. To do this, it keeps an AA mapping handles to Item objects. When an item is removed, it removes it from this AA, but there are also the item's child items to remove. So the Item destructor calls its child items' destructors. (This feature is as yet untested.) The issue is ensuring that an object isn't destructed twice (or more). This is needed anyway, otherwise everything that's been explicitly deleted would be destructed again when the GC discovers it's unreachable. This is actually a straightforward issue to address - either every object's isDestructed property needs to be accessible, or (even better) delete would automagically check. Stewart.
Sep 20 2004
parent reply "Ben Hinkle" <bhinkle mathworks.com> writes:
"Stewart Gordon" <Stewart_member pathlink.com> wrote in message
news:cimoal$2nor$1 digitaldaemon.com...
 In article <cifife$2s1u$1 digitaldaemon.com>, Ben Hinkle says...
 <snip>
 Agreed.  It is very rare that destructors should be used.  In
 particular there are questions about when/if a destructor gets run,
 from which thread it gets run and what else is alive when it gets
 run.

But I've seen it said that you can override the question about if by calling gc_term when your program exits....

gc_term is already called at program exit. The garbage collector could even then mistakely believe an object has a live reference to it so it is possible to never actually collect a given object. The probability is very low (I assume) but it is there. Of course most system resources are cleaned up by the OS at program exit anyway so it shouldn't really matter.
 Conceptually they are similar to C++ destructors since they both
 are about releasing resources but the "random" nature of D
 destructors makes them very different in practice.

I've had no problems simply using destructors to free resources.

system resources should be fine. GC managed resources should be avoided with a ten foot pole.
 However, when one destructor wants to call another, things get
 difficult, and the question of what else is alive is indeed a
 question.

 I'm working on a tree control implementation for SDWF 0.4.  AIUI, you
 don't need to free the handles of individual tree items, but the Item
 objects still present a challenge.

 This is because the TreeView class needs to be able to get the
 current selection.  To do this, it keeps an AA mapping handles to
 Item objects.

ok
 When an item is removed, it removes it from this AA,
 but there are also the item's child items to remove.  So the Item
 destructor calls its child items' destructors.  (This feature is as
 yet untested.)

What is the API to remove an item? If it is by calling delete you will have a problem since the AA is managed by the GC. If it by using a member functions like remove() then I'd have the parent's remove function call remove on the children.
 The issue is ensuring that an object isn't destructed twice (or
 more).  This is needed anyway, otherwise everything that's been
 explicitly deleted would be destructed again when the GC discovers
 it's unreachable.

The GC already guarantees an object's destructor won't be called twice. I'm not quite sure if this is what you mean, though.
 This is actually a straightforward issue to address - either every
 object's isDestructed property needs to be accessible, or (even
 better) delete would automagically check.

 Stewart.

Sep 20 2004
parent reply Stewart Gordon <Stewart_member pathlink.com> writes:
In article <cimpvb$2oog$1 digitaldaemon.com>, Ben Hinkle says...

 "Stewart Gordon" <Stewart_member pathlink.com> wrote in message 
 news:cimoal$2nor$1 digitaldaemon.com...

 gc_term is already called at program exit.

Unless the compiler has changed since last time, then not according to what I was told. Hence its presence in the boilerplate WinMain function is extra to D's standard behaviour.
 The garbage collector could even then mistakely believe an object 
 has a live reference to it so it is possible to never actually 
 collect a given object.  The probability is very low (I assume) but 
 it is there.  Of course most system resources are cleaned up by the 
 OS at program exit anyway so it shouldn't really matter.

Yes, _most_ system resources. But not, for example, file objects with write caching. And even with those that are, I guess some people like to be on the safe side anyway.... <snip>
 When an item is removed, it removes it from this AA, but there are 
 also the item's child items to remove.  So the Item destructor 
 calls its child items' destructors.  (This feature is as yet 
 untested.)

What is the API to remove an item? If it is by calling delete you will have a problem since the AA is managed by the GC.

It has a remove method, which calls the WinAPI to remove the item from the tree and then deletes this.
 If it by using a member functions like remove() then I'd have the 
 parent's remove function call remove on the children.

That would cause them to be separately removed from the tree. If done after the parent is removed, it would be an invalid WinAPI call; if done before, it would work but might be more time-consuming if there's lots to remove. Of course, another possibility is to have remove call some internal method (other than the destructor) to remove stuff from the index....
 The issue is ensuring that an object isn't destructed twice (or 
 more).  This is needed anyway, otherwise everything that's been 
 explicitly deleted would be destructed again when the GC discovers 
 it's unreachable.

The GC already guarantees an object's destructor won't be called twice. I'm not quite sure if this is what you mean, though.

I mean, if you have something like Qwert yuiop = new Qwert; delete yuiop; delete yuiop; will there necessarily be only one destructor call? Stewart.
Sep 20 2004
parent reply Ben Hinkle <bhinkle4 juno.com> writes:
Stewart Gordon wrote:

 In article <cimpvb$2oog$1 digitaldaemon.com>, Ben Hinkle says...
 
 "Stewart Gordon" <Stewart_member pathlink.com> wrote in message
 news:cimoal$2nor$1 digitaldaemon.com...

 gc_term is already called at program exit.

Unless the compiler has changed since last time, then not according to what I was told. Hence its presence in the boilerplate WinMain function is extra to D's standard behaviour.

ah - I see what you mean. I meant that it is called by the standard D startup/shutdown code in src/phobos/internal/dmain2.d
 The garbage collector could even then mistakely believe an object
 has a live reference to it so it is possible to never actually
 collect a given object.  The probability is very low (I assume) but
 it is there.  Of course most system resources are cleaned up by the
 OS at program exit anyway so it shouldn't really matter.

Yes, _most_ system resources. But not, for example, file objects with write caching. And even with those that are, I guess some people like to be on the safe side anyway....

agreed, I think.
 <snip>
 When an item is removed, it removes it from this AA, but there are
 also the item's child items to remove.  So the Item destructor
 calls its child items' destructors.  (This feature is as yet
 untested.)

What is the API to remove an item? If it is by calling delete you will have a problem since the AA is managed by the GC.

It has a remove method, which calls the WinAPI to remove the item from the tree and then deletes this.

ok. though I wonder what deleting this does. Is it needed? Why not just have remove call the Windows API to remove the item?
 If it by using a member functions like remove() then I'd have the
 parent's remove function call remove on the children.

That would cause them to be separately removed from the tree. If done after the parent is removed, it would be an invalid WinAPI call; if done before, it would work but might be more time-consuming if there's lots to remove. Of course, another possibility is to have remove call some internal method (other than the destructor) to remove stuff from the index....

I'm a little lost without more context so I'll try getting your library and poking around sometime. I can see your point about worrying about efficiency and also trying to use destructors to guarantee removal.
 The issue is ensuring that an object isn't destructed twice (or
 more).  This is needed anyway, otherwise everything that's been
 explicitly deleted would be destructed again when the GC discovers
 it's unreachable.

The GC already guarantees an object's destructor won't be called twice. I'm not quite sure if this is what you mean, though.

I mean, if you have something like Qwert yuiop = new Qwert; delete yuiop; delete yuiop; will there necessarily be only one destructor call?

I'm not sure - or maybe the answer is "it depends". There might be a problem if another thread jumps in between the two delete calls and allocates a new object right exactly where yuiop is pointing and then the second delete would actually end up deleting that new object (ick!). I don't really know the GC well enough to know if this is possible but off the top of my head I would assume it could happen. If such threading behavior can't happen in your app then yes the destructor is only called once because the first delete clears a flag that the GC uses to keep track of what needs destroying.
 
 Stewart.

Sep 20 2004
parent Stewart Gordon <Stewart_member pathlink.com> writes:
In article <cin1og$2tuj$1 digitaldaemon.com>, Ben Hinkle says...
<snip>
 It has a remove method, which calls the WinAPI to remove the item 
 from the tree and then deletes this.

ok. though I wonder what deleting this does. Is it needed?

As I said, removes it from the SDWF-side index and recursively does the same to the children.
 Why not just have remove call the Windows API to remove the item?

Because that wouldn't remove it from the SDWF-side index. OK, so there are other possibilities, and they aren't really doing anything but taking up space in there.... <snip>
 I'm a little lost without more context so I'll try getting your 
 library and poking around sometime.  I can see your point about 
 worrying about efficiency and also trying to use destructors to 
 guarantee removal.

This whole feature is as yet unreleased, but I'd be happy to send you a prototype when I've got one ready. <snip>
 I mean, if you have something like
 
     Qwert yuiop = new Qwert;
     delete yuiop;
     delete yuiop;
 
 will there necessarily be only one destructor call?

I'm not sure - or maybe the answer is "it depends". There might be a problem if another thread jumps in between the two delete calls and allocates a new object right exactly where yuiop is pointing and then the second delete would actually end up deleting that new object (ick!).

Only if delete immediately frees the memory. But you'd want to think about thread-safety here anyway.
 I don't really know the GC well enough to know if this is possible 
 but off the top of my head I would assume it could happen.  If such 
 threading behavior can't happen in your app then yes the destructor 
 is only called once because the first delete clears a flag that the 
 GC uses to keep track of what needs destroying.

Of course, that would depend on how it keeps track of which object is which. Having memory freed only during GC passes simplifies this considerably. Stewart.
Sep 20 2004