www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Destructor/Finalizer Guarantees

reply "Maxime Chevalier-Boisvert" <maximechevalierb gmail.com> writes:
I have a situation where I have a VM (virtual machine) object, 
and several GCRoot (garbage collector root objects). The GCRoots 
are structs and will "register" themselves into a linked list 
belonging to the VM. I've made it so they unregister themselves 
in their destructor. This works perfectly well for GC roots which 
are on the stack.

However, recently, I ran into a case where I need GCRoots which 
are not on the stack. This is where things broke down. The VM 
object got destroyed before the GCRoots, and when these tried to 
unregister themselves, they accessed memory which had already 
been reclaimed (the dead VM). What I want to know is: what 
guarantees can I expect from destructor behavior?

I was thinking that when the VM gets destroyed, it could 
unregister all of its GCRoots at once. Then, when these are 
destroyed, they wouldn't try to touch the VM object. However, 
this only works if I can assume that the GC will first call the 
destructor on an object, then free the object, that this is done 
in a predictable order. Am I on the right track, or do I need to 
rethink this?
Nov 11 2014
next sibling parent ketmar via Digitalmars-d-learn <digitalmars-d-learn puremagic.com> writes:
On Tue, 11 Nov 2014 22:31:17 +0000
Maxime Chevalier-Boisvert via Digitalmars-d-learn
<digitalmars-d-learn puremagic.com> wrote:

 What I want to know is: what=20
 guarantees can I expect from destructor behavior?
destructors *may* be called eventually. or not. in any order. but never twice. think about object destructors as "finalizers". no calling order guarantees, nor even guarantees that something will be called at all. so if your VM becomes garbage, and registered objects are anchored only by VM (i.e. there are no more references to that objects), destructors can be called in any order.
Nov 11 2014
prev sibling next sibling parent "Dicebot" <public dicebot.lv> writes:
There is an issue with structs that are directly allocated on 
heap - destructors are never called for those. You will want to 
change those into classes for GC to do at least something about 
it.

See also this bug report : 
https://issues.dlang.org/show_bug.cgi?id=2834
Nov 11 2014
prev sibling next sibling parent "Dicebot" <public dicebot.lv> writes:
As for guarantees for class destructors - you have hard 
guarantees that if memory is reclaimed, destructor was called 
before. But no guarantees memory will actually be reclaimed.
Nov 11 2014
prev sibling next sibling parent Steven Schveighoffer <schveiguy yahoo.com> writes:
On 11/11/14 5:31 PM, Maxime Chevalier-Boisvert wrote:
 I have a situation where I have a VM (virtual machine) object, and
 several GCRoot (garbage collector root objects). The GCRoots are structs
 and will "register" themselves into a linked list belonging to the VM.
 I've made it so they unregister themselves in their destructor. This
 works perfectly well for GC roots which are on the stack.

 However, recently, I ran into a case where I need GCRoots which are not
 on the stack. This is where things broke down. The VM object got
 destroyed before the GCRoots, and when these tried to unregister
 themselves, they accessed memory which had already been reclaimed (the
 dead VM). What I want to know is: what guarantees can I expect from
 destructor behavior?

 I was thinking that when the VM gets destroyed, it could unregister all
 of its GCRoots at once. Then, when these are destroyed, they wouldn't
 try to touch the VM object. However, this only works if I can assume
 that the GC will first call the destructor on an object, then free the
 object, that this is done in a predictable order. Am I on the right
 track, or do I need to rethink this?
Short answer, you have no guarantees that references to GC memory still point at valid memory. It is a very sticky problem to deal with. Reference counting and GC don't mix well, because the GC cannot guarantee destruction order. -Steve
Nov 11 2014
prev sibling next sibling parent reply "Algo" <qq qq.qq> writes:
On Tuesday, 11 November 2014 at 22:31:17 UTC, Maxime 
Chevalier-Boisvert wrote:
 I have a situation where I have a VM (virtual machine) object, 
 and several GCRoot (garbage collector root objects). The 
 GCRoots are structs and will "register" themselves into a 
 linked list belonging to the VM. I've made it so they 
 unregister themselves in their destructor. This works perfectly 
 well for GC roots which are on the stack.

 However, recently, I ran into a case where I need GCRoots which 
 are not on the stack. This is where things broke down. The VM 
 object got destroyed before the GCRoots, and when these tried 
 to unregister themselves, they accessed memory which had 
 already been reclaimed (the dead VM). What I want to know is: 
 what guarantees can I expect from destructor behavior?

 I was thinking that when the VM gets destroyed, it could 
 unregister all of its GCRoots at once. Then, when these are 
 destroyed, they wouldn't try to touch the VM object. However, 
 this only works if I can assume that the GC will first call the 
 destructor on an object, then free the object, that this is 
 done in a predictable order. Am I on the right track, or do I 
 need to rethink this?
Could this work? class VM { List gcRootList; this() { gcRootList.add(GCRoot.init); .. to class VM { static List[VM*] _gcRootLists; List* gcRootList; this() { _gcRootLists[&this] = List.init; gcRootList = &_gcRootLists[&this]; gcRootList.add(GCRoot.init); .. ~this() { _gcRootLists.remove(&this); ..
Nov 11 2014
parent "thedeemon" <dlang thedeemon.com> writes:
On Wednesday, 12 November 2014 at 04:06:11 UTC, Algo wrote:

 Could this work?

 class VM {
     static List[VM*] _gcRootLists;
     List* gcRootList;
     ~this() {
         _gcRootLists.remove(&this);
No. Hash-table operations may try to allocate or free memory which is not allowed during a GC cycle where the destructors are called. It will just cause InvalidMemoryOperationError and the program will exit abnormally.
Nov 12 2014
prev sibling next sibling parent "thedeemon" <dlang thedeemon.com> writes:
On Tuesday, 11 November 2014 at 22:31:17 UTC, Maxime 
Chevalier-Boisvert wrote:
 I've made it so they unregister themselves in their destructor. 
 ... However, this only works if I can assume that the GC will 
 first call the destructor on an object, then free the object, 
 that this is done in a predictable order.
This order is not really predictable now. In general in destructor you can't access anything outside the object's value typed fields. Any reference may point to a dead object at this moment, any external or global object may be destroyed already.
Nov 12 2014
prev sibling parent reply "Kagamin" <spam here.lot> writes:
With GC you usually have two destructors: one for managed 
resources and one for unmanaged resources. Destructor for managed 
resources should be run on live objects as soon as you don't need 
the resource, it calls unmanaged destructor too. Unmanaged 
destructor (finalizer) is called by GC during garbage collection 
and frees unmanaged resources (not managed by GC). Since they are 
not managed by GC, you decide, how they are disposed. Though 
calling finalizer during collection is a last resort for resource 
management, unmanaged destructor should be normally called from 
managed destructor.
Nov 12 2014
parent reply "eles" <eles215 gzk.dot> writes:
On Wednesday, 12 November 2014 at 14:36:19 UTC, Kagamin wrote:
 With GC you usually have two destructors:
Which is why this approach is so cumbersome. At least, in non-GC you only have just one kind of destructor.
Nov 12 2014
next sibling parent "Kagamin" <spam here.lot> writes:
It can work with only managed destructor - that's how it's 
usually done. Finalizer only guards against slow resource leak 
when you forget to free them.
Nov 12 2014
prev sibling parent "Kagamin" <spam here.lot> writes:
On Wednesday, 12 November 2014 at 14:42:38 UTC, eles wrote:
 Which is why this approach is so cumbersome. At least, in 
 non-GC you only have just one kind of destructor.
It's not necessarily very cumbersome. Standard library usually provides necessary integration: http://blogs.msdn.com/b/bclteam/archive/2005/03/16/396900.aspx
Nov 12 2014