digitalmars.D.learn - Destructor/Finalizer Guarantees
- Maxime Chevalier-Boisvert (19/19) Nov 11 2014 I have a situation where I have a VM (virtual machine) object,
- ketmar via Digitalmars-d-learn (10/12) Nov 11 2014 On Tue, 11 Nov 2014 22:31:17 +0000
- Dicebot (6/6) Nov 11 2014 There is an issue with structs that are directly allocated on
- Dicebot (3/3) Nov 11 2014 As for guarantees for class destructors - you have hard
- Steven Schveighoffer (6/23) Nov 11 2014 Short answer, you have no guarantees that references to GC memory still
- Algo (20/39) Nov 11 2014 Could this work?
- thedeemon (5/11) Nov 12 2014 No. Hash-table operations may try to allocate or free memory
- thedeemon (6/10) Nov 12 2014 This order is not really predictable now. In general in
- Kagamin (10/10) Nov 12 2014 With GC you usually have two destructors: one for managed
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
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
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
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
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
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
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
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
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
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
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
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