digitalmars.D.learn - GC: finalization order?!
- Martin Kinkelin (36/36) Feb 19 2011 Hi,
- bearophile (4/9) Feb 19 2011 It's intended, despite being not nice for the programmer. Generally in D...
- Martin Kinkelin (8/8) Feb 19 2011 Thanks guys, I'm honestly impressed by the responsiveness of this
- Jonathan M Davis (7/50) Feb 19 2011 IIRC, class destructors aren't supposed to reference any references or p...
- Johannes Pfau (8/15) Feb 19 2011 I think destructors shouldn't use references to the _garbage collected_
- Jonathan M Davis (6/21) Feb 19 2011 Yes. You're right. They can reference the non-GC heap just fine. It's ju...
- Simen kjaeraas (7/13) Feb 20 2011 D could support finalizers in lieu of (or in addition to) destructors. I...
- Jonathan M Davis (14/26) Feb 20 2011 I'm really not very well versed in the details of the GC, and I don't kn...
- Martin Kinkelin (75/75) Feb 20 2011 I came to the same conclusion. Even if Parent is a struct, it may get
- Steven Schveighoffer (7/32) Feb 20 2011 I hate to deflate your bubble, but this feature (custom handlers for new...
- Martin Kinkelin (1/1) Feb 20 2011 Hehe, thx for deflating and pointing in the right direction.
Hi, I'm very surprised by the GC finalization order (D 2.051, Windows). Minimalistic test: ---------- import std.stdio; class Child { this() { writeln("Child.__ctor()"); } ~this() { writeln("Child.__dtor()"); } } class Parent { private Child _child; this() { writeln("Parent.__ctor()"); _child = new Child(); } ~this() { writeln("Parent.__dtor()"); } } int main(string[] args) { auto parent = new Parent(); return 0; } ---------- Output: ---------- Parent.__ctor() Child.__ctor() Child.__dtor() Parent.__dtor() ---------- So parent._child gets destructed before parent, although parent obviously holds a reference to the Child instance. My problem is that I need to access _child in Parent.__dtor(), which therefore doesn't work as I expected. Is this a bug or really intended behaviour?! Thanks in advance, Martin
Feb 19 2011
Martin Kinkelin:So parent._child gets destructed before parent, although parent obviously holds a reference to the Child instance. My problem is that I need to access _child in Parent.__dtor(), which therefore doesn't work as I expected. Is this a bug or really intended behaviour?!It's intended, despite being not nice for the programmer. Generally in D finalization order done by the GC is not deterministic (it's not even sure you will have finalizations, I think), so you must design your program in a different way (like using RAII and structs, etc). Python GC is based on enhanced reference counting, so it's deterministic. But D uses a less deterministic GC, like a mark & sweep. Bye, bearophile
Feb 19 2011
Thanks guys, I'm honestly impressed by the responsiveness of this newsgroup! So I guess I'll try to mess around with GC.add/removeRoot(). For the curious: Child is a memory buffer using reference counting, and serves multiple Parent instances (multi-dimensional arrays). The goal is to free the buffer as soon as all related Parents have been destructed (either explicitly by clear() or by the GC).
Feb 19 2011
On Saturday 19 February 2011 05:54:43 Martin Kinkelin wrote:Hi, I'm very surprised by the GC finalization order (D 2.051, Windows). Minimalistic test: ---------- import std.stdio; class Child { this() { writeln("Child.__ctor()"); } ~this() { writeln("Child.__dtor()"); } } class Parent { private Child _child; this() { writeln("Parent.__ctor()"); _child = new Child(); } ~this() { writeln("Parent.__dtor()"); } } int main(string[] args) { auto parent = new Parent(); return 0; } ---------- Output: ---------- Parent.__ctor() Child.__ctor() Child.__dtor() Parent.__dtor() ---------- So parent._child gets destructed before parent, although parent obviously holds a reference to the Child instance. My problem is that I need to access _child in Parent.__dtor(), which therefore doesn't work as I expected. Is this a bug or really intended behaviour?! Thanks in advance,IIRC, class destructors aren't supposed to reference any references or pointers to the heap. They're intended for cleaning up other resources. I don't think that there are any guarantees with regards to the order of the destruction of objects which are being garbage collected. But I don't mess with destructors much, so I'm not all that well versed in the details. - Jonathan M Davis
Feb 19 2011
Jonathan M Davis wrote:IIRC, class destructors aren't supposed to reference any references or pointers to the heap. They're intended for cleaning up other resources. I don't think that there are any guarantees with regards to the order of the destruction of objects which are being garbage collected. But I don't mess with destructors much, so I'm not all that well versed in the details. - Jonathan M DavisI think destructors shouldn't use references to the _garbage collected_ heap. Freeing resources which were allocated with malloc should work. In fact freeing C memory is the only usecase for destructors I can think of. If you rely on destructors to release file handles / gpu textures / other limited resources you risk to run out of those. --=20 Johannes Pfau
Feb 19 2011
On Saturday 19 February 2011 12:34:39 Johannes Pfau wrote:Jonathan M Davis wrote:Yes. You're right. They can reference the non-GC heap just fine. It's just that they can't reference the GC heap - probably because the destructor order is indeterminate and so that the GC doesn't have to worry about dealing with circular references between garbage collected objects. - Jonathan M DavisIIRC, class destructors aren't supposed to reference any references or pointers to the heap. They're intended for cleaning up other resources. I don't think that there are any guarantees with regards to the order of the destruction of objects which are being garbage collected. But I don't mess with destructors much, so I'm not all that well versed in the details. - Jonathan M DavisI think destructors shouldn't use references to the _garbage collected_ heap. Freeing resources which were allocated with malloc should work. In fact freeing C memory is the only usecase for destructors I can think of. If you rely on destructors to release file handles / gpu textures / other limited resources you risk to run out of those.
Feb 19 2011
Jonathan M Davis <jmdavisProg gmx.com> wrote:Yes. You're right. They can reference the non-GC heap just fine. It's just that they can't reference the GC heap - probably because the destructor order is indeterminate and so that the GC doesn't have to worry about dealing with circular references between garbage collected objects.D could support finalizers in lieu of (or in addition to) destructors. In such a case, they would be called before the object graph were garbage- collected, and one could hence reference other objects on the GC heap. Is there any reason why this approach was not chosen? -- Simen
Feb 20 2011
On Sunday 20 February 2011 04:10:18 Simen kjaeraas wrote:Jonathan M Davis <jmdavisProg gmx.com> wrote:I'm really not very well versed in the details of the GC, and I don't know all that much about why it works the way that it works. For the most part, though, I don't see much need for either destructors or finalizers in classes. The main reason for them in C++ is for managing memory, which the GC takes care of for you. And since RAII is taken care of by structs, there really isn't much reason for class destructors that I can see - at least not normally. I can see why you might need it occasionally, but for the most part, you don't. Java has finalizers (but no constructors), but I've never needed them, and I've never seen them used. So, while the situation with class destructors and finalizers could be handled better in D, I don't think that it's normally an issue at all. Regardless, I'm not particularly well versed in the details of the GC, so I'm really not the best person to answer questions about it. - Jonathan M DavisYes. You're right. They can reference the non-GC heap just fine. It's just that they can't reference the GC heap - probably because the destructor order is indeterminate and so that the GC doesn't have to worry about dealing with circular references between garbage collected objects.D could support finalizers in lieu of (or in addition to) destructors. In such a case, they would be called before the object graph were garbage- collected, and one could hence reference other objects on the GC heap. Is there any reason why this approach was not chosen?
Feb 20 2011
I came to the same conclusion. Even if Parent is a struct, it may get destructed after its Child (e.g., in case the Parent struct is a field of another class). What I did is to use a custom allocator for Child, so that Child instances are not managed by the GC: ---------- import std.stdio; import std.c.stdlib; import core.exception; private class Child { new(size_t size) { writeln("Child.new()"); void* p = malloc(size); if (!p) onOutOfMemoryError(); return p; } delete(void* p) { writeln("Child.delete()"); if (p) free(p); } private size_t _refs; this() { writeln("Child.__ctor()"); _refs = 1; } ~this() { writeln("Child.__dtor()"); if (_refs != 0) throw new Exception("still referenced"); } void addRef() { _refs++; } void release() { if (--_refs == 0) delete this; } } class Parent { private Child _child; this() { writeln("Parent.__ctor()"); _child = new Child(); } ~this() { writeln("Parent.__dtor()"); if (_child) _child.release(); } } unittest { auto p = new Parent(); } ---------- Output: ---------- Parent.__ctor() Child.new() Child.__ctor() Parent.__dtor() Child.__dtor() Child.delete() ---------- This way, Child is destructed as soon as the last related Parent is destructed. Thanks for clarifying!
Feb 20 2011
On Sun, 20 Feb 2011 07:15:06 -0500, Martin Kinkelin <noone spam.com> wrote:I came to the same conclusion. Even if Parent is a struct, it may get destructed after its Child (e.g., in case the Parent struct is a field of another class). What I did is to use a custom allocator for Child, so that Child instances are not managed by the GC: ---------- import std.stdio; import std.c.stdlib; import core.exception; private class Child { new(size_t size) { writeln("Child.new()"); void* p = malloc(size); if (!p) onOutOfMemoryError(); return p; } delete(void* p) { writeln("Child.delete()"); if (p) free(p); }I hate to deflate your bubble, but this feature (custom handlers for new and delete) is going to be deprecated. However, you can reinflate with the intended replacement. Essentially, you will move allocation handling outside of child and move it to Parent. Check out emplace (not sure what module it is). -Steve
Feb 20 2011
Hehe, thx for deflating and pointing in the right direction.
Feb 20 2011