Thread overview |
---|
November 11, 2014 Destructor/Finalizer Guarantees | ||||
---|---|---|---|---|
| ||||
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? |
November 11, 2014 Re: Destructor/Finalizer Guarantees | ||||
---|---|---|---|---|
| ||||
Posted in reply to Maxime Chevalier-Boisvert Attachments: | 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 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. |
November 11, 2014 Re: Destructor/Finalizer Guarantees | ||||
---|---|---|---|---|
| ||||
Posted in reply to Maxime Chevalier-Boisvert | 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 |
November 11, 2014 Re: Destructor/Finalizer Guarantees | ||||
---|---|---|---|---|
| ||||
Posted in reply to Maxime Chevalier-Boisvert | 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. |
November 12, 2014 Re: Destructor/Finalizer Guarantees | ||||
---|---|---|---|---|
| ||||
Posted in reply to Maxime Chevalier-Boisvert | 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
|
November 12, 2014 Re: Destructor/Finalizer Guarantees | ||||
---|---|---|---|---|
| ||||
Posted in reply to Maxime Chevalier-Boisvert | 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);
..
|
November 12, 2014 Re: Destructor/Finalizer Guarantees | ||||
---|---|---|---|---|
| ||||
Posted in reply to Maxime Chevalier-Boisvert | 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.
|
November 12, 2014 Re: Destructor/Finalizer Guarantees | ||||
---|---|---|---|---|
| ||||
Posted in reply to Algo | 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.
|
November 12, 2014 Re: Destructor/Finalizer Guarantees | ||||
---|---|---|---|---|
| ||||
Posted in reply to Maxime Chevalier-Boisvert | 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. |
November 12, 2014 Re: Destructor/Finalizer Guarantees | ||||
---|---|---|---|---|
| ||||
Posted in reply to Kagamin | 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.
|
Copyright © 1999-2021 by the D Language Foundation