View mode: basic / threaded / horizontal-split · Log in · Help
May 24, 2012
Re: Destructor nonsense on dlang.org
On Thursday, 24 May 2012 at 17:57:11 UTC, Steven Schveighoffer 
wrote:
> On Thu, 24 May 2012 13:47:31 -0400, Tove <tove@fransson.se> 
> wrote:
>
> There's a big problem with this though.  Your destructor *has 
> no idea* whether it's being called from within a collection 
> cycle, or from clear.  You must assume the most restrictive 
> environment, i.e. that the dtor is being called from the GC.
>
> This is even true with struct dtors!
>
> -Steve

If there is a clear location where a manual close() function can 
be called... then there are many safe solutions to automatically 
and safely call clear instead.

std.typecons.Unique

If you are a library creator, you could even use a factory to 
enforce wrapping in Unique...  But I don't see any point of 
adding a non standard destructor function name, there are 
numerous ways to facilitate RAII.
May 24, 2012
Re: Destructor nonsense on dlang.org
On Thursday, 24 May 2012 at 17:57:11 UTC, Steven Schveighoffer 
wrote:
> On Thu, 24 May 2012 13:47:31 -0400, Tove <tove@fransson.se> 
> wrote:
>
>> On Thursday, 24 May 2012 at 17:06:19 UTC, ponce wrote:
>>> I really had a hard time to believe it when #D told me so, 
>>> but there is no guaranteed order of destruction and as you 
>>> cannot relies on members still being alive in a class 
>>> destructor.
>>> All of it can happen when making absolutely no cycles in the 
>>> object graph.
>>>
>>> What I do now is having a close function for each class which 
>>> hold a non-memory resource.
>>>
>>> This is writtent in TDPL, but I wish I was told earlier :)
>>
>> http://dlang.org/class.html#destructors
>>
>> "This rule does not apply to auto objects or objects deleted 
>> with the DeleteExpression, as the destructor is not being run 
>> by the garbage collector, meaning all references are valid."
>>
>> i.e. non gc resources are fine... and it's also fine if you 
>> call clear()... it's only a problem if you rely on automatic 
>> collection and reference a object... so there's no need for 
>> close, as clear() will do the trick.
>
> There's a big problem with this though.  Your destructor *has 
> no idea* whether it's being called from within a collection 
> cycle, or from clear.  You must assume the most restrictive 
> environment, i.e. that the dtor is being called from the GC.
>
> This is even true with struct dtors!
>
> -Steve

Looks to me like an issue with separation of concerns. I think 
that dtors need to only provide deterministic management of 
resources and not affect GC algorithms:
1. classes should *not* have dtors at all.
2. struct values should *not* be gc managed [*].

Composition of classes and structs should be handled as follows:
1. If a class contains a pointer to a struct it doesn't scan it 
in a GC cycle. The runtime can provide a hook so that structs 
could register a callback for RC purposes.
2. When a class contains a strcut value, they share the same 
lifetime thus the GC will call the struct's dtor when the object 
is collected.
3. If a struct contains a reference to an object (class instance) 
than *that* object instance is scanned by the GC.

[*] point 3 above means that struct pointers are scanned in a 
pass-thru way only for the purpose of scanning contained object 
references.

With the above semantics, the dtor does know that all its members 
(except for class references) are valid, including any pointers 
to other structs and has similar semantics to c++ dtors. This 
scheme can be enforced by the compiler and is safe since objects 
won't inherently hold any resources by themselves (they don't 
have dtors). This also allows to implement more advanced 
finalization schemes (e.g. dependencies between resources).
May 24, 2012
Re: Destructor nonsense on dlang.org
On Thursday, 24 May 2012 at 19:46:07 UTC, foobar wrote:
> Looks to me like an issue with separation of concerns. I think 
> that dtors need to only provide deterministic management of 
> resources and not affect GC algorithms:
> 1. classes should *not* have dtors at all.
> 2. struct values should *not* be gc managed [*].
>

Why not simply set "BlkAttr.NO_SCAN" on ourselves if we need 
certain resources in the destructor? Assuming we one day get user 
defined attributes, it can be make quite simple...
May 24, 2012
Re: Destructor nonsense on dlang.org
On Thursday, 24 May 2012 at 20:53:33 UTC, Tove wrote:
> On Thursday, 24 May 2012 at 19:46:07 UTC, foobar wrote:
>> Looks to me like an issue with separation of concerns. I think 
>> that dtors need to only provide deterministic management of 
>> resources and not affect GC algorithms:
>> 1. classes should *not* have dtors at all.
>> 2. struct values should *not* be gc managed [*].
>>
>
> Why not simply set "BlkAttr.NO_SCAN" on ourselves if we need 
> certain resources in the destructor? Assuming we one day get 
> user defined attributes, it can be make quite simple...

Tested my idea... unfortunately it's broken...

GC.collect() while the program is running, is OK... so I was 
hoping to add:
GC.disable() just before main() ends, but apparently this request 
is ignored.

i.e. back to square 1, undefined collecting order once the 
program exits.

import std.stdio;
import core.memory;

class Important
{
  this()
  {
    us ~= this;
  }
  ~this()
  {
    writeln("2");
  }

private:
  static Important[] us;
}

class CollectMe
{
  Important resource;

  this()
  {
    resource = new Important();
  }
  ~this()
  {
    writeln("1");
    clear(resource);
  }
}

void main()
{
  GC.setAttr(cast(void*)new CollectMe(), GC.BlkAttr.NO_SCAN);
  GC.collect();
  GC.disable();

  writeln("3");
}
May 25, 2012
Re: Destructor nonsense on dlang.org
On Thursday, 24 May 2012 at 13:49:38 UTC, Steven Schveighoffer 
wrote:
> You actually need a finalizer if you want to have resources 
> that aren't GC allocated.


Does that ring a bell? ;)
May 25, 2012
Re: Destructor nonsense on dlang.org
On Thursday, 24 May 2012 at 16:06:23 UTC, Andrei Alexandrescu 
wrote:
> It does matter because a destructor may use an object that has 
> just been destroyed.
>
> Andrei


Andrei: .NET has this exact problem, and handles it pretty well.

There are two types of "Dispose()": manual and automatic.

Whenever you're wrapping some unmanaged resource (e.g. file 
handle), you wrap it inside some managed object (e.g. 
SafeFileHandle).

Then you embed _that_ resource in the actual object you want 
(e.g. FileStream).

Now, there are two ways a FileStream can get destroyed:

1. Through a manual call to FileStream.Dispose(). In this case, 
all embedded objects (e.g. SafeFileHandle) are *guaranteed* to be 
valid, so we simply flush the file and call 
SafeFileHandle.Dispose() to dispose of the managed resources, and 
then dispose of all the unmanaged resources (which are primitive 
fields, guaranteed to be accessible). Furthermore, the object 
suppresses its own finalizer.

2. Through a garbage-collected call to ~FileStream(). In this 
case, the managed resources such as SafeFileHandle will be (or is 
already) destroyed SEPARATELY, and so we do _NOT_ access them. We 
ONLY dispose of the unmanaged resources, if any, and let the 
managed resources take care of themselves.


It's a pretty well-defined sequence, and it works well in 
practice.

(Of course, you don't actually _need_ this double-indirection 
here: You could instead just wrap the unmanaged resource 
manually, and do everything in FileStream. The reason for the 
double-indirection is something slightly unrelated. I was just 
explaining how to take care of the managed resource disposal 
problem that you mentioned.)


You could point out that, in this case, the FileStream doesn't 
flush its buffers  before the file handle is destroyed, if the GC 
collects the object.

That problem is solvable in two ways, although .NET simply chose 
to not worry about it, as far as I know:

1. Simply wrap the handle inside FileStream. Since it will be 
unmanaged, you can access it during disposal.

2. If that isn't possible, keep a _strong_, *unmanaged* reference 
to your _managed_ SafeFileHandle object. (This is accomplished 
through acquiring a cookie from the GC.) Because of this, 
SafeFileHandle will NOT be destroyed before FileStream. You can 
then use this fact to access SafeFileHandle inside FileStream's 
finalizer, through the unmanaged (but safe) cookie.



tl;dr: It's a completely solved problem in .NET; there really 
shouldn't be any issues with it in D either.
May 25, 2012
Re: Destructor nonsense on dlang.org
On 2012-05-24 21:46, foobar wrote:

> Looks to me like an issue with separation of concerns. I think that
> dtors need to only provide deterministic management of resources and not
> affect GC algorithms:
> 1. classes should *not* have dtors at all.
> 2. struct values should *not* be gc managed [*].
>
> Composition of classes and structs should be handled as follows:
> 1. If a class contains a pointer to a struct it doesn't scan it in a GC
> cycle. The runtime can provide a hook so that structs could register a
> callback for RC purposes.
> 2. When a class contains a strcut value, they share the same lifetime
> thus the GC will call the struct's dtor when the object is collected.

How is that any different than having destructors for classes.

-- 
/Jacob Carlborg
May 25, 2012
Re: Destructor nonsense on dlang.org
On Friday, 25 May 2012 at 07:13:11 UTC, Jacob Carlborg wrote:
> On 2012-05-24 21:46, foobar wrote:
>
>> Looks to me like an issue with separation of concerns. I think 
>> that
>> dtors need to only provide deterministic management of 
>> resources and not
>> affect GC algorithms:
>> 1. classes should *not* have dtors at all.
>> 2. struct values should *not* be gc managed [*].
>>
>> Composition of classes and structs should be handled as 
>> follows:
>> 1. If a class contains a pointer to a struct it doesn't scan 
>> it in a GC
>> cycle. The runtime can provide a hook so that structs could 
>> register a
>> callback for RC purposes.
>> 2. When a class contains a strcut value, they share the same 
>> lifetime
>> thus the GC will call the struct's dtor when the object is 
>> collected.
>
> How is that any different than having destructors for classes.

It makes the call order deterministic like in C++.

e.g.
class Foo {}

struct A {
  resource r;
  ~this() { release(r); }
}

struct B {
  A* a;
  Foo foo;
  ~this() { delete a; } // [1]
}

Lets look at point [1]:
The "foo" instance is "managed" by the GC since the only resource 
it holds is memory. The "a" member wraps some "non-managed" 
resource (e.g. file descriptor) and in this model is still valid, 
thus allows me to deterministically dispose of it as in c++.

This can be simply checked at compile-time - you can only 
reference non class instance members in the destructor, so adding 
a "delete foo;" statement at point [1] simply won't compile.
May 25, 2012
Re: Destructor nonsense on dlang.org
On Thu, 24 May 2012 18:57:11 +0100, Steven Schveighoffer  
<schveiguy@yahoo.com> wrote:
> On Thu, 24 May 2012 13:47:31 -0400, Tove <tove@fransson.se> wrote:
>
>> On Thursday, 24 May 2012 at 17:06:19 UTC, ponce wrote:
>>> I really had a hard time to believe it when #D told me so, but there  
>>> is no guaranteed order of destruction and as you cannot relies on  
>>> members still being alive in a class destructor.
>>> All of it can happen when making absolutely no cycles in the object  
>>> graph.
>>>
>>> What I do now is having a close function for each class which hold a  
>>> non-memory resource.
>>>
>>> This is writtent in TDPL, but I wish I was told earlier :)
>>
>> http://dlang.org/class.html#destructors
>>
>> "This rule does not apply to auto objects or objects deleted with the  
>> DeleteExpression, as the destructor is not being run by the garbage  
>> collector, meaning all references are valid."
>>
>> i.e. non gc resources are fine... and it's also fine if you call  
>> clear()... it's only a problem if you rely on automatic collection and  
>> reference a object... so there's no need for close, as clear() will do  
>> the trick.
>
> There's a big problem with this though.  Your destructor *has no idea*  
> whether it's being called from within a collection cycle, or from  
> clear.  You must assume the most restrictive environment, i.e. that the  
> dtor is being called from the GC.
>
> This is even true with struct dtors!

The C# dispose model suggests/gives examples of handling this sort of  
problem using a bool and 2 dispose methods, see:
http://msdn.microsoft.com/en-us/library/fs2xkftw.aspx

Could we do something similar in D, i.e. provide a template class which  
could wrap any reference and implement this..

R

-- 
Using Opera's revolutionary email client: http://www.opera.com/mail/
May 25, 2012
Re: Destructor nonsense on dlang.org
On 2012-05-25 11:02, foobar wrote:

> It makes the call order deterministic like in C++.
>
> e.g.
> class Foo {}
>
> struct A {
> resource r;
> ~this() { release(r); }
> }
>
> struct B {
> A* a;
> Foo foo;
> ~this() { delete a; } // [1]
> }

I though we were talking about classes holding structs:

class B {
    A* a;
    Foo foo;
    ~this() { delete a; }
}

In this case you don't know when/if the destructor of B is called. It 
doesn't help to wrap it in a struct, you could just have put it directly 
in A. Is that correct?

> Lets look at point [1]:
> The "foo" instance is "managed" by the GC since the only resource it
> holds is memory. The "a" member wraps some "non-managed" resource (e.g.
> file descriptor) and in this model is still valid, thus allows me to
> deterministically dispose of it as in c++.

Ok, but if B is a class?

> This can be simply checked at compile-time - you can only reference non
> class instance members in the destructor, so adding a "delete foo;"
> statement at point [1] simply won't compile.

If you have a pointer to a struct you don't know how it was created. 
It's possible it's been created with "new", which means the garbage 
collector needs to delete it.

-- 
/Jacob Carlborg
1 2 3 4 5 6 7
Top | Discussion index | About this forum | D home