digitalmars.D - GC and destruction events
- Ben Hinkle (49/49) Sep 14 2004 Some recent posts got me thinking about the fact that the garbage collec...
- pragma (13/17) Sep 14 2004 Ben, I like what you've done here. I coded something similar a while ba...
- Ben Hinkle (32/48) Sep 14 2004 collector
- Russ Lewis (16/16) Sep 14 2004 Perhaps we should just add functionality to the GC to cover this. We
- Ben Hinkle (4/23) Sep 14 2004 yeah - that would be a better solution. I wonder why collectors in C# an...
- Arcane Jill (30/37) Sep 15 2004 I suspect that it would be more practical and flexible, long term, to st...
- Russ Lewis (10/64) Sep 15 2004 I think you make a lot of sense here.
Some recent posts got me thinking about the fact that the garbage collector collects garbage in random order and so one can't rely on destructors being run in any particular order. To work around this I've hacked up destructor events. There is one interface and one mixin template. I've also included two example classes A and B to illustrate how destructor events work to protect a call to B.foo from A.~this. The code for A is a tad verbose but if you think about it there isn't much else one can do. Here's the code: alias void delegate() DtorCallback; interface DtorEvent { void dtorCallback(DtorCallback cb); DtorCallback dtorCallback(); } template DtorEventImpl() { DtorCallback dtorCallback_; DtorCallback dtorCallback() { return dtorCallback_; } void dtorCallback(DtorCallback cb) { dtorCallback_ = cb; } void fireDtorEvent(){ if (dtorCallback_) dtorCallback_(); } } class A { B b; this() { b = new B; b.dtorCallback = &deletingB; } ~this() { printf("A.~this\n"); if (b) { // b is being destroyed after A b.dtorCallback = null; // prevent b from called a deleted A b.foo(); } } void deletingB() { // b is being destroyed before A printf("A.deletingB\n"); if (b) { b.foo(); b = null; // prevent ~this from calling a deleted B } } } class B : DtorEvent { mixin DtorEventImpl; ~this() { fireDtorEvent(); } void foo() { printf("B.foo\n"); } } int main() { A a = new A; // delete a; // test explicit delete, too return 0; }
Sep 14 2004
In article <ci7jem$86o$1 digitaldaemon.com>, Ben Hinkle says...Some recent posts got me thinking about the fact that the garbage collector collects garbage in random order and so one can't rely on destructors being run in any particular order. To work around this I've hacked up destructor events. There is one interface and one mixin template.Ben, I like what you've done here. I coded something similar a while back that was in the same vein, did you take a look at this before? Then again, maybe wer're solving two different problems: http://svn.dsource.org/svn/projects/dsp/trunk/misc/systemFinalizer.d Maybe we could mesh our designs? I like the idea of having a mixin that can be dropped into a class to register it for deterministic finalization. Its something that D could really use. Also, I thought the problem was that destructors only run in an unpredictable order at program termination (if at all). Is this still the case, or is there a deeper problem during runtime? - Pragma [[ Eric Anderton at yahoo dot com ]]
Sep 14 2004
"pragma" <pragma_member pathlink.com> wrote in message news:ci7ko5$8pp$1 digitaldaemon.com...In article <ci7jem$86o$1 digitaldaemon.com>, Ben Hinkle says...collectorSome recent posts got me thinking about the fact that the garbagebeingcollects garbage in random order and so one can't rely on destructorsdestructorrun in any particular order. To work around this I've hacked upthatevents. There is one interface and one mixin template.Ben, I like what you've done here. I coded something similar a while backwas in the same vein, did you take a look at this before? Then again,maybewer're solving two different problems: http://svn.dsource.org/svn/projects/dsp/trunk/misc/systemFinalizer.dThey look to be related but slightly different. With your code a reference to the object being tracked is put in a static table so the object is never collected until program exit (and maybe not even then?). If one uses the AutoSystemFinalizer the objects will get collected after the scope is exited so at least then the objects don't sit around forever. With my code I was trying to keep all the references just between the object being tracked and the object doing the tracking so they can be collected at any time they both become garbage.Maybe we could mesh our designs? I like the idea of having a mixin thatcan bedropped into a class to register it for deterministic finalization. Its something that D could really use.I wasn't going to develop that code any more. It was just to illustrate how one could go about enforcing an order. It is slightly subtle but doable. If you want to expand/merge/whatever my posed code feel free to. I am tempted to try to add something like this to std.stream to make a BufferedStream automatically flush the buffer and close the backing stream in destructors but the whole DtorEvent thing needs the backing stream to fire the event and if someone has a Stream subclass that doesn't fire the event it messes up the whole thing. So for now I'm leaning towards forcing Stream users to always close their streams explicitly.Also, I thought the problem was that destructors only run in anunpredictableorder at program termination (if at all). Is this still the case, or isthere adeeper problem during runtime?The unpredictable order is any time the GC runs. Since the GC always runs at exit people tend to see the problem at exit. The way the GC works is it marks all garbage and then loops over the marked memory and calls any destructors for a given memory block. It has no idea if one block of memory needs to be collected before another block.
Sep 14 2004
Perhaps we should just add functionality to the GC to cover this. We could add a function to the GC that looks like this: void gc.SetDestroyOrder(void *a,void *b); This function would register a destruction ordering; a would always get cleaned up before b. The function would throw an exception if this created a loop. Then you could write classes like this: class A { B b; this() { b = new B; gc.SetDestroyOrder(this,b); } ~this() { b.DoSomething(); } }
Sep 14 2004
Russ Lewis wrote:Perhaps we should just add functionality to the GC to cover this. We could add a function to the GC that looks like this: void gc.SetDestroyOrder(void *a,void *b); This function would register a destruction ordering; a would always get cleaned up before b. The function would throw an exception if this created a loop. Then you could write classes like this: class A { B b; this() { b = new B; gc.SetDestroyOrder(this,b); } ~this() { b.DoSomething(); } }Java don't have that ability? It seems like it would be pretty nice. I wonder if it is a performance thing.
Sep 14 2004
In article <ci7rqu$cah$1 digitaldaemon.com>, Russ Lewis says...Perhaps we should just add functionality to the GC to cover this. We could add a function to the GC that looks like this: void gc.SetDestroyOrder(void *a,void *b);I suspect that it would be more practical and flexible, long term, to stop thinking of /the/ garbage collecter - instead, think only of /a/ garbage collector. That is, the built-in GC may not necessarily be the only one. If you link with a DLL, maybe that DLL will have its own GC. Maybe that DLL wasn't even written in D, but some other language (maybe even a D++ of the future). Or maybe you'd like to write a custom allocator which uses its own GC for some specialized purpose. Right now, you can't do this, because the existing GC won't co-operate with other GCs. It believes itself to be the only one in existence. What makes more sense to me would be to separate out the function of garbage /management/ from the function of garbage /collection/. A garbage collector could allocate memory (by whatever own means) and then register that memory with the (unique) garbage manager. The GM would decide when a particular block of memory was unreachable, but instead of destructing/freeing it, all it would have to do is notify the particular GC which registered it that it was now unused. The particular GC which allocated it could then destruct/free it (and this in turn could be a two-stage process, if a custom allocator/deallocator had been used). So, back to...void gc.SetDestroyOrder(void *a,void *b); This function would register a destruction ordering; a would always get cleaned up before b. The function would throw an exception if this created a loop.That could now be implemented as a member function of /a/ (not the) garbage collector - perhaps even a subclass of a Phobos class called GarbageCollector. New GC => new functionality. Maybe this is getting way too sophisticated, but the DLL issue will come back to haunt us if we ignore it. Not everything wants to be statically linked, and there are sufficient problems with destruction events that systems programmers are going to want to take control of memory management somehow. Right now, we can do that only if we completely ignore the GC. It would be so much nicer to be able to co-operate with it. Arcane Jill
Sep 15 2004
I think you make a lot of sense here. Question: Should we have the capacity for different GMs? Some objects may be subject to different types of garbage collection criteria, or some types of data may be more easily managed with a different type of collector (like a refcounting collector, or a by-hand collector). Imagine a collector tied into a windowing protocol; as long as the window is active, the windowing manager holds a virtual reference to the window object; when the window is destroyed, the virtual reference to the window object goes away and the window object MIGHT be collected. Arcane Jill wrote:In article <ci7rqu$cah$1 digitaldaemon.com>, Russ Lewis says...Perhaps we should just add functionality to the GC to cover this. We could add a function to the GC that looks like this: void gc.SetDestroyOrder(void *a,void *b);I suspect that it would be more practical and flexible, long term, to stop thinking of /the/ garbage collecter - instead, think only of /a/ garbage collector. That is, the built-in GC may not necessarily be the only one. If you link with a DLL, maybe that DLL will have its own GC. Maybe that DLL wasn't even written in D, but some other language (maybe even a D++ of the future). Or maybe you'd like to write a custom allocator which uses its own GC for some specialized purpose. Right now, you can't do this, because the existing GC won't co-operate with other GCs. It believes itself to be the only one in existence. What makes more sense to me would be to separate out the function of garbage /management/ from the function of garbage /collection/. A garbage collector could allocate memory (by whatever own means) and then register that memory with the (unique) garbage manager. The GM would decide when a particular block of memory was unreachable, but instead of destructing/freeing it, all it would have to do is notify the particular GC which registered it that it was now unused. The particular GC which allocated it could then destruct/free it (and this in turn could be a two-stage process, if a custom allocator/deallocator had been used). So, back to...void gc.SetDestroyOrder(void *a,void *b); This function would register a destruction ordering; a would always get cleaned up before b. The function would throw an exception if this created a loop.That could now be implemented as a member function of /a/ (not the) garbage collector - perhaps even a subclass of a Phobos class called GarbageCollector. New GC => new functionality. Maybe this is getting way too sophisticated, but the DLL issue will come back to haunt us if we ignore it. Not everything wants to be statically linked, and there are sufficient problems with destruction events that systems programmers are going to want to take control of memory management somehow. Right now, we can do that only if we completely ignore the GC. It would be so much nicer to be able to co-operate with it. Arcane Jill
Sep 15 2004