Thread overview
Behaviour of fullCollect() depends on a printf in the constructor...
Aug 30, 2004
Bastiaan Veelo
Aug 30, 2004
Bastiaan Veelo
Aug 30, 2004
Bastiaan Veelo
Aug 30, 2004
Walter
August 30, 2004
Hi,

Being new to garbage collection, I find the following puzzling. In the code below, where two objects refer to each other, main() has no references to any objects anymore when fullCollect() is run, but none of the objects are deleted. However, when we put a printf in the constructor for B (line 24), the behaviour changes to the expected behaviour: all objects are deleted before fullCollect() returns. Please explain this to me :-)

Bastiaan.


#//file: gctest.d
#
#class A
#{
#	B b;
#	
#	this()
#	{
#		b = new B(this);
#		printf("A constructed.\n");
#	}
#	~this()
#	{
#		printf("A destructed.\n");
#	}
#}
#
#class B
#{
#	this(Object owner)
#	{
#		_owner = owner;
#		// uncomment the following and the GC runs as expected.
#//		printf("B constructed.\n");
#	}
#	~this()
#	{
#		printf("B destructed.\n");
#	}
#private:
#	Object _owner;
#}
#
#import std.gc;
#
#void main()
#{
#	A a = new A;
#	a = null;
#	printf("Running fullCollect()...\n");
#	fullCollect();
#	printf("Done collecting.\n");
#}
August 30, 2004
[Additional info. Cross-posting to D.bugs as this is probably a bug.]

Bastiaan Veelo wrote:
> Hi,
> 
> Being new to garbage collection, I find the following puzzling. In the code below, where two objects refer to each other, main() has no references to any objects anymore when fullCollect() is run, but none of the objects are deleted. However, when we put a printf in the constructor for B (line 24), the behaviour changes to the expected behaviour: all objects are deleted before fullCollect() returns. Please explain this to me :-)
> 
> Bastiaan.
> 
> 
> #//file: gctest.d
> #
> #class A
> #{
> #    B b;
> #   #    this()
> #    {
> #        b = new B(this);
> #        printf("A constructed.\n");
> #    }
> #    ~this()
> #    {
> #        printf("A destructed.\n");
> #    }
> #}
> #
> #class B
> #{
> #    this(Object owner)
> #    {
> #        _owner = owner;
> #        // uncomment the following and the GC runs as expected.
> #//        printf("B constructed.\n");
> #    }
> #    ~this()
> #    {
> #        printf("B destructed.\n");
> #    }
> #private:
> #    Object _owner;
> #}
> #
> #import std.gc;
> #
> #void main()
> #{
> #    A a = new A;
> #    a = null;
> #    printf("Running fullCollect()...\n");
> #    fullCollect();
> #    printf("Done collecting.\n");
> #}

Additional info:
Run on linux, compiled with dmd-0.100, the output of the above code is

A constructed.
Running fullCollect()...
Done collecting.
B destructed.
A destructed.


With the printf you get the expected time of destruction:

B constructed.
A constructed.
Running fullCollect()...
B destructed.
A destructed.
Done collecting.


Bastiaan.
August 30, 2004
[Even more info.]

Bastiaan Veelo wrote:
> [Additional info. Cross-posting to D.bugs as this is probably a bug.]
> 
> Bastiaan Veelo wrote:
> 
>> Hi,
>>
>> Being new to garbage collection, I find the following puzzling. In the code below, where two objects refer to each other, main() has no references to any objects anymore when fullCollect() is run, but none of the objects are deleted. However, when we put a printf in the constructor for B (line 24), the behaviour changes to the expected behaviour: all objects are deleted before fullCollect() returns. Please explain this to me :-)
>>
>> Bastiaan.
>>
>>
>> #//file: gctest.d
>> #
>> #class A
>> #{
>> #    B b;
>> #   #    this()
>> #    {
>> #        b = new B(this);
>> #        printf("A constructed.\n");
>> #    }
>> #    ~this()
>> #    {
>> #        printf("A destructed.\n");
>> #    }
>> #}
>> #
>> #class B
>> #{
>> #    this(Object owner)
>> #    {
>> #        _owner = owner;
>> #        // uncomment the following and the GC runs as expected.
>> #//        printf("B constructed.\n");
>> #    }
>> #    ~this()
>> #    {
>> #        printf("B destructed.\n");
>> #    }
>> #private:
>> #    Object _owner;
>> #}
>> #
>> #import std.gc;
>> #
>> #void main()
>> #{
>> #    A a = new A;
>> #    a = null;
>> #    printf("Running fullCollect()...\n");
>> #    fullCollect();
>> #    printf("Done collecting.\n");
>> #}
> 
> 
> Additional info:
> Run on linux, compiled with dmd-0.100, the output of the above code is
> 
> A constructed.
> Running fullCollect()...
> Done collecting.
> B destructed.
> A destructed.
> 
> 
> With the printf you get the expected time of destruction:
> 
> B constructed.
> A constructed.
> Running fullCollect()...
> B destructed.
> A destructed.
> Done collecting.
> 
> 
> Bastiaan.

When compiled with gdc release 1f, which is based on the 0.82 frontend, this code executes as expected in both cases; no bugs revealed.

Bastiaan.
August 30, 2004
What happens is the gc scans the stack and registers for references to the heap. There can be 'left over' values on the stack and registers that are detected this way, which will hold on to the objects in the heap. The call to printf() likely overwrites those left over values, and so the objects get collected.

The bottom line is you shouldn't write gc code as if it had deterministic finalization. If that's what you need, use auto classes and RAII techniques.