www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - GC does not delete subclass

reply Matthias Thurau <Matthiasth gmx.de> writes:
Hi,
I just made some small examples for myself to understand the GC. Here i have a
testcase, that has an unexpected behavior for me:



import std.stdio;

static int number   =   0;
static int number2   =   0;

class MyClass {
public:
  this()    {   writefln("Constructor %d", number);
                myNumber = number; number++;
                member  =   new My2Class(); };
  ~this()   {   writefln("Destructor %d", myNumber); };

  My2Class    member;

  int myNumber;
};

class My2Class {
public:
  this()    {   writefln("Sub_Constructor2 %d", number2);
                myNumber2 = number2; number2++; };
  ~this()   {   writefln("Sub_Destructor2 %d", myNumber2); };

  int myNumber2;
};




int main(char[][] args) {
    {
        scope MyClass A  =    new MyClass();
    }

    std.gc.fullCollect();

    writefln("The Sub Class from 0 does not get deleted yet!");

    MyClass C   =    new MyClass();

    return 0;
};



The Output ist: 

Constructor 0
Sub_Constructor2 0
Destructor 0
The Sub Class from 0 does not get deleted yet!
Constructor 1
Sub_Constructor2 1
Sub_Destructor2 1
Destructor 1
Sub_Destructor2 0
Dec 17 2007
parent reply Matthias Thurau <Matthiasth gmx.de> writes:
BTW:
This is working correctly:

void func() {
    MyClass A  =    new MyClass();
};

int main(char[][] args) {
    
    func();

    std.gc.fullCollect();
    writefln("The Sub Class from 0 does not get deleted yet!");
    MyClass C   =    new MyClass();

    return 0;
};

When i put the "scope" into the func(), then i get the output from above again.
The right ouput was in my opinion:
Constructor 0
Sub_Constructor2 0
Sub_Destructor2 0
Destructor 0
The Sub Class from 0 does not get deleted yet!
Constructor 1
Sub_Constructor2 1
Sub_Destructor2 1
Destructor 1
Dec 17 2007
parent reply Jason House <jason.james.house gmail.com> writes:
Matthias Thurau Wrote:

 When i put the "scope" into the func(), then i get the output from above again.

It looks like a bug to me. Does this happen with Phobos or Tango? (or both?) I know they use different gc's.
Dec 17 2007
parent reply Sean Kelly <sean f4.ca> writes:
Jason House wrote:
 Matthias Thurau Wrote:
 
 When i put the "scope" into the func(), then i get the output from above again.

It looks like a bug to me. Does this happen with Phobos or Tango? (or both?) I know they use different gc's.

I've noticed that the most recent object to be constructed is often not deleted in simple test cases. My guess is that a reference to this address is probably still lingering in a register somewhere, so the GC thinks it's still alive. This happens in Phobos and Tango. Sean
Dec 17 2007
parent reply Matthias Thurau <Matthiasth gmx.de> writes:
thanks for replys...

So how do i handle this? Should i ignore that or am i doing something wrong?
(Ist it a bug or not? :)

bye
Dec 18 2007
next sibling parent Lars Ivar Igesund <larsivar igesund.net> writes:
Matthias Thurau wrote:

 thanks for replys...
 
 So how do i handle this? Should i ignore that or am i doing something
 wrong? (Ist it a bug or not? :)
 
 bye

It is hardly a bug. You should in any case not depend on an object being collected. Garbage collection is not deterministic. -- Lars Ivar Igesund blog at http://larsivi.net DSource, #d.tango & #D: larsivi Dancing the Tango
Dec 18 2007
prev sibling parent reply 0ffh <frank frankhirsch.youknow.what.todo.net> writes:
Matthias Thurau wrote:
 thanks for replys...
 
 So how do i handle this? Should i ignore that or am i doing something wrong?
(Ist it a bug or not? :)

Ignore it. Most probably no bug! Who has ever claimed that the GC collects objects in any specific order? regards, frank
Dec 18 2007
parent reply Jason House <jason.james.house gmail.com> writes:
0ffh Wrote:

 Matthias Thurau wrote:
 thanks for replys...
 
 So how do i handle this? Should i ignore that or am i doing something wrong?
(Ist it a bug or not? :)

Ignore it. Most probably no bug! Who has ever claimed that the GC collects objects in any specific order?

I don't think it was specifically the order but rather full collection not fully collecting. I haven't tried this, but is it possible to declare class member variables as scope so that they will get deleted when the rest of the object does? This could save coding of custom destructors in some cases.
Dec 18 2007
parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Jason House" wrote
 0ffh Wrote:

 Matthias Thurau wrote:
 thanks for replys...

 So how do i handle this? Should i ignore that or am i doing something 
 wrong? (Ist it a bug or not? :)

Ignore it. Most probably no bug! Who has ever claimed that the GC collects objects in any specific order?

I don't think it was specifically the order but rather full collection not fully collecting. I haven't tried this, but is it possible to declare class member variables as scope so that they will get deleted when the rest of the object does? This could save coding of custom destructors in some cases.

Custom destructors should NEVER destroy other GC allocated objects. the GC cannot guarantee the order of collection, meaning if you have a pointer to another GC allocated object, that object may have already been collected. There is no real reason to delete other objects in a destructor. If the objects are no longer referenced, the GC will destroy those also. The only things you should destroy in a destructor are non-GC allocated resources, such as file descriptors, or memory allocated with C's malloc. So to answer your question: scope is not necessary as a modifier for a member variable. -Steve
Dec 18 2007
next sibling parent reply Matthias Thurau <Matthiasth gmx.de> writes:
"If the objects are no longer referenced, the GC will destroy those also."

Thats exact the case: In my example the GC isn t destroying that unreferenced
member.
Dec 18 2007
next sibling parent reply Regan Heath <regan netmail.co.nz> writes:
Matthias Thurau wrote:
 "If the objects are no longer referenced, the GC will destroy those also."
 
 Thats exact the case: In my example the GC isn t destroying that unreferenced
member.

I imagine Sean was correct when he said: "I've noticed that the most recent object to be constructed is often not deleted in simple test cases. My guess is that a reference to this address is probably still lingering in a register somewhere, so the GC thinks it's still alive. This happens in Phobos and Tango." In which case, yes, there is a 'bug' in full collect. Post a bug report, if one does not already exist. :) Regan
Dec 18 2007
parent 0ffh <frank frankhirsch.youknow.what.todo.net> writes:
Regan Heath wrote:
 I imagine Sean was correct when he said:
 
 "I've noticed that the most recent object to be constructed is often not 
 deleted in simple test cases.  My guess is that a reference to this 
 address is probably still lingering in a register somewhere, so the GC 
 thinks it's still alive.  This happens in Phobos and Tango."
 
 In which case, yes, there is a 'bug' in full collect.  Post a bug 
 report, if one does not already exist. :)

Given that Sean is correct (which is plausible), then it is incorrect to speak of a bug (or even a 'bug'). The algorithm works as specified. If someone does not like (or understand) the specification you can hardly (actually, not even remotely) speak of a bug. Sorry for nitpicking, but this is bothering me. regards, frank
Dec 18 2007
prev sibling next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Matthias Thurau" wrote
 "If the objects are no longer referenced, the GC will destroy those also."

 Thats exact the case: In my example the GC isn t destroying that 
 unreferenced member.

My understanding of scope is that the object is supposed to be forcefully deleted at the end of the scope (not through the GC). So in your case, I believe there is a bug, because delete should call the destructor immediately. i.e.: { scope C = new C; } should be equivalent to: { auto C = new C; delete C; } So I believe that the GC shouldn't be calling the destructor in fullcollect, it should be called BEFORE fullcollect is called. Of course, I may be misunderstanding how scope works :) With regards to my response, I just wanted to point out to Jason that destructors should not be used as he was alluding to. -Steve
Dec 18 2007
parent reply Sean Kelly <sean f4.ca> writes:
Steven Schveighoffer wrote:
 "Matthias Thurau" wrote
 "If the objects are no longer referenced, the GC will destroy those also."

 Thats exact the case: In my example the GC isn t destroying that 
 unreferenced member.

My understanding of scope is that the object is supposed to be forcefully deleted at the end of the scope (not through the GC). So in your case, I believe there is a bug, because delete should call the destructor immediately. i.e.: { scope C = new C; } should be equivalent to: { auto C = new C; delete C; } So I believe that the GC shouldn't be calling the destructor in fullcollect, it should be called BEFORE fullcollect is called.

I'm pretty sure that this is what's happening. However, it is only deleting C, not the objects that C points to. This is necessary behavior. Consider: class A {} class B { this( A a ) { val = a; } A val; } void fn( A val ) { scope B = new B( val ); } You certainly wouldn't want the instance of A deleted when fn exits as well. Sean
Dec 18 2007
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Sean Kelly" wrote
 I'm pretty sure that this is what's happening.  However, it is only 
 deleting C, not the objects that C points to.  This is necessary behavior. 
 Consider:

     class A
     {}
     class B
     {
         this( A a )
         {
             val = a;
         }
         A val;
     }

     void fn( A val )
     {
         scope B = new B( val );
     }

 You certainly wouldn't want the instance of A deleted when fn exits as 
 well.

Oh! I misunderstood the original post. Yes, I agree that the current behavior should be the expected behavior for the original example. I thought that for some reason the destructor of the scoped class was not getting called when the scope exited. -Steve
Dec 18 2007
prev sibling parent reply Sean Kelly <sean f4.ca> writes:
Matthias Thurau wrote:
 "If the objects are no longer referenced, the GC will destroy those also."
 
 Thats exact the case: In my example the GC isn t destroying that unreferenced
member.

Technically, it is. It's just not destroying it in the collection immediately following the object's allocation, which isn't guaranteed anyway. As I said previously, it's likely that a register still holds the address of this object when the allocation is triggered, because basically nothing happens between the object's construction and the collection. In a real application, such things are unlikely occur. Sean
Dec 18 2007
parent reply Gide Nwawudu <gide btinternet.com> writes:
On Tue, 18 Dec 2007 11:45:49 -0800, Sean Kelly <sean f4.ca> wrote:

Matthias Thurau wrote:
 "If the objects are no longer referenced, the GC will destroy those also."
 
 Thats exact the case: In my example the GC isn t destroying that unreferenced
member.

Technically, it is. It's just not destroying it in the collection immediately following the object's allocation, which isn't guaranteed anyway. As I said previously, it's likely that a register still holds the address of this object when the allocation is triggered, because basically nothing happens between the object's construction and the collection. In a real application, such things are unlikely occur.

  ~this()   {   writefln("Destructor %d", myNumber); }

To help the GC, is Java-like null assignment necessary? The following line helps std.gc.fullCollect() to destroy My2Class members. ~this() { writefln("Destructor %d", myNumber); member = null; } Gide
Dec 18 2007
parent reply Sean Kelly <sean f4.ca> writes:
Gide Nwawudu wrote:
 On Tue, 18 Dec 2007 11:45:49 -0800, Sean Kelly <sean f4.ca> wrote:
 
 Matthias Thurau wrote:
 "If the objects are no longer referenced, the GC will destroy those also."

 Thats exact the case: In my example the GC isn t destroying that unreferenced
member.

immediately following the object's allocation, which isn't guaranteed anyway. As I said previously, it's likely that a register still holds the address of this object when the allocation is triggered, because basically nothing happens between the object's construction and the collection. In a real application, such things are unlikely occur.

  ~this()   {   writefln("Destructor %d", myNumber); }

To help the GC, is Java-like null assignment necessary? The following line helps std.gc.fullCollect() to destroy My2Class members. ~this() { writefln("Destructor %d", myNumber); member = null; }

Not usually. The problem is this case is a register with the value, not a memory location. I've run into it before when trying to create simple unit tests and demos in D. The easiest way around it that I've found is to construct an additional "throw away" object after the object you expect to be cleaned up, to overwrite any lingering register data that may still point to the old object. This is handy for tiny test apps, but again, I don't think anything needs to be done for real apps. Sean
Dec 18 2007
parent reply Matthias Thurau <Matthiasth gmx.de> writes:
"The problem is this case is a register with the value, not
a memory location"

I don t understand what a register is :( Could you give me an example? And
maybe Your workaround for that?

greetings
Dec 19 2007
parent reply guslay <guslay gmail.com> writes:
Matthias Thurau Wrote:

 "The problem is this case is a register with the value, not
 a memory location"
 
 I don t understand what a register is :( Could you give me an example? And
maybe Your workaround for that?
 
 greetings

A register is a small storage available to the cpu to make computation. http://en.wikipedia.org/wiki/Processor_register However I doubt that Sean's assumption is valid in this case. I've modified the original testcase to call { MyClass A = new MyClass(); } up to a couple tens of THOUSAND times. Whereas the scoped class is destroyed on scope exit, the destructor of the embedded object is only called at program termination (as described). Collect as no effect on them, no matter how many instances are created. That would be a lot of registers! Some other things I have noticed: * Without scope attribute, all instances of both classes are destroyed on collect. No problem. * With the scope attribute, embedded instances are destroyed in reverse order of creation, by group of 256. Sub_Destructor2 255 ... Sub_Destructor2 0 Sub_Destructor2 511 ... Sub_Destructor2 256 .. This GC behavior is stranger than usual... (using Phobos.)
Dec 19 2007
parent reply Sean Kelly <sean f4.ca> writes:
guslay wrote:
 Matthias Thurau Wrote:
 
 "The problem is this case is a register with the value, not
 a memory location"

 I don t understand what a register is :( Could you give me an example? And
maybe Your workaround for that?

 greetings

A register is a small storage available to the cpu to make computation. http://en.wikipedia.org/wiki/Processor_register However I doubt that Sean's assumption is valid in this case. I've modified the original testcase to call { MyClass A = new MyClass(); } up to a couple tens of THOUSAND times. Whereas the scoped class is destroyed on scope exit, the destructor of the embedded object is only called at program termination (as described). Collect as no effect on them, no matter how many instances are created.

Oops. My comment about registers referred to the GC not collecting the most recently constructed object which simply went out of scope. Objects created with the "scope" attribute are entirely different. In that case, the "scope" object is deleted when scope exits, but no objects it references are deleted. This is by design, and I addressed it in another post in this thread. Sean
Dec 19 2007
parent reply Jason House <jason.james.house gmail.com> writes:
Sean Kelly Wrote:

 guslay wrote:
 However I doubt that Sean's assumption is valid in this case. I've modified
the original testcase to call
 
 { MyClass A =    new MyClass(); }
 
 up to a couple tens of THOUSAND times.
 
 Whereas the scoped class is destroyed on scope exit, the destructor of the
embedded object is only called at program termination (as described). Collect as
 no effect on them, no matter how many instances are created.

Oops. My comment about registers referred to the GC not collecting the most recently constructed object which simply went out of scope. Objects created with the "scope" attribute are entirely different. In that case, the "scope" object is deleted when scope exits, but no objects it references are deleted. This is by design, and I addressed it in another post in this thread.

I think that the point of the original post is getting lost. After having scope variable(s) go out of focus (and get deallocated), a call to full collect is done. The issue was that full collect did not collect the objects allocated by the scope objects, even though there was no references to them.
Dec 19 2007
parent Sean Kelly <sean f4.ca> writes:
Jason House wrote:
 Sean Kelly Wrote:
 
 guslay wrote:
 However I doubt that Sean's assumption is valid in this case. I've modified
the original testcase to call

 { MyClass A =    new MyClass(); }

 up to a couple tens of THOUSAND times.

 Whereas the scoped class is destroyed on scope exit, the destructor of the
embedded object is only called at program termination (as described). Collect as
 no effect on them, no matter how many instances are created.

most recently constructed object which simply went out of scope. Objects created with the "scope" attribute are entirely different. In that case, the "scope" object is deleted when scope exits, but no objects it references are deleted. This is by design, and I addressed it in another post in this thread.

I think that the point of the original post is getting lost. After having scope variable(s) go out of focus (and get deallocated), a call to full collect is done. The issue was that full collect did not collect the objects allocated by the scope objects, even though there was no references to them.

Right. This related to my comment about registers above. Sean
Dec 19 2007
prev sibling parent reply Jason House <jason.james.house gmail.com> writes:
Steven Schveighoffer Wrote:

 "Jason House" wrote
 I haven't tried this, but is it possible to declare class member variables 
 as scope so that they will get deleted when the rest of the object does? 
 This could save coding of custom destructors in some cases.

Custom destructors should NEVER destroy other GC allocated objects. the GC cannot guarantee the order of collection, meaning if you have a pointer to another GC allocated object, that object may have already been collected. There is no real reason to delete other objects in a destructor. If the objects are no longer referenced, the GC will destroy those also. The only things you should destroy in a destructor are non-GC allocated resources, such as file descriptors, or memory allocated with C's malloc. So to answer your question: scope is not necessary as a modifier for a member variable.

I wasn't asking about necessary. I was asking if it's valid. It's easy enough to construct RAII-like examples where a timely destruction of a member variable would be handy. It can also be handy when standard GC operation is overridden.
Dec 18 2007
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Jason House" wrote
 Steven Schveighoffer Wrote:

 "Jason House" wrote
 I haven't tried this, but is it possible to declare class member 
 variables
 as scope so that they will get deleted when the rest of the object 
 does?
 This could save coding of custom destructors in some cases.

Custom destructors should NEVER destroy other GC allocated objects. the GC cannot guarantee the order of collection, meaning if you have a pointer to another GC allocated object, that object may have already been collected. There is no real reason to delete other objects in a destructor. If the objects are no longer referenced, the GC will destroy those also. The only things you should destroy in a destructor are non-GC allocated resources, such as file descriptors, or memory allocated with C's malloc. So to answer your question: scope is not necessary as a modifier for a member variable.

I wasn't asking about necessary. I was asking if it's valid. It's easy enough to construct RAII-like examples where a timely destruction of a member variable would be handy. It can also be handy when standard GC operation is overridden.

Not neccessary == not implemented :) Why implement a feature that is not needed? I guess I should have stated that. From the attributes page (http://www.digitalmars.com/d/attribute.html): "scope cannot be applied to globals, statics, data members, ref or out parameters." Timely destruction of a member that is not referenced in any other object/thread should occur when the object that owns the member is destroyed. No need to force it, which is why scope isn't necessary or valid. -Steve
Dec 18 2007