May 24, 2012
On 24-05-2012 18:06, Andrei Alexandrescu wrote:
> On 5/24/12 9:57 AM, Alex Rønne Petersen wrote:
>> On 24-05-2012 16:54, Andrei Alexandrescu wrote:
>>> On 5/24/12 9:28 AM, Alex Rønne Petersen wrote:
>>>> The GC should (and probably does) assume at shutdown that all objects
>>>> are unreferenced, and therefore reclaim and finalize them.
>>>
>>> They may refer to one another.
>>>
>>> Andrei
>>>
>>
>> Doesn't matter: Nothing is guaranteed about order of finalization (and
>> this is reasonable). Thus, the finalizers can be run in any arbitrary
>> order. The important point here is that they are run *at all*.
>
> It does matter because a destructor may use an object that has just been
> destroyed.
>
> Andrei
>

No, the docs specifically state that this is invalid (and it currently throws InvalidMemoryOperationError in most cases).

Whether it *should* be allowed is arguable, but it isn't currently, both in docs and impl.

-- 
Alex Rønne Petersen
alex@lycus.org
http://lycus.org
May 24, 2012
On 5/24/12 10:27 AM, deadalnix wrote:
> Le 24/05/2012 16:54, Andrei Alexandrescu a écrit :
>> On 5/24/12 9:28 AM, Alex Rønne Petersen wrote:
>>> The GC should (and probably does) assume at shutdown that all objects
>>> are unreferenced, and therefore reclaim and finalize them.
>>
>> They may refer to one another.
>>
>> Andrei
>>
>
> So what ?
>
> Each GC passes must, mark object that have to be removed, call finalizer
> on them all, THEN recycle memory.
>
> So « zombie » object can still refer to one another in finalization.

This is possible but not trivial as the state of zombie objects must be properly defined. Often such objects will fail their invariant (a reasonable state of a zombie object is with the correct vtable, no mutex, and all fields in the pre-constructor state).

> The real problem is resurrection, which should be 100% forbiden and this
> must be enforced by the language (ie, the scopeness of this parameter is
> something important).

As one aspect, calls to new should fail during destruction.


Andrei
May 24, 2012
On 24-05-2012 18:10, Andrei Alexandrescu wrote:
> On 5/24/12 10:27 AM, deadalnix wrote:
>> Le 24/05/2012 16:54, Andrei Alexandrescu a écrit :
>>> On 5/24/12 9:28 AM, Alex Rønne Petersen wrote:
>>>> The GC should (and probably does) assume at shutdown that all objects
>>>> are unreferenced, and therefore reclaim and finalize them.
>>>
>>> They may refer to one another.
>>>
>>> Andrei
>>>
>>
>> So what ?
>>
>> Each GC passes must, mark object that have to be removed, call finalizer
>> on them all, THEN recycle memory.
>>
>> So « zombie » object can still refer to one another in finalization.
>
> This is possible but not trivial as the state of zombie objects must be
> properly defined. Often such objects will fail their invariant (a
> reasonable state of a zombie object is with the correct vtable, no
> mutex, and all fields in the pre-constructor state).
>
>> The real problem is resurrection, which should be 100% forbiden and this
>> must be enforced by the language (ie, the scopeness of this parameter is
>> something important).
>
> As one aspect, calls to new should fail during destruction.
>
>
> Andrei

Finalization happens once the world has been resumed, meaning GC allocation (and even explicit deallocation) should be perfectly safe. This is absolutely essential: Finalization models where finalizers run in a paused world are doomed to fail miserably.

-- 
Alex Rønne Petersen
alex@lycus.org
http://lycus.org
May 24, 2012
On Thursday, 24 May 2012 at 16:06:23 UTC, Andrei Alexandrescu wrote:
> On 5/24/12 9:57 AM, Alex Rønne Petersen wrote:
>> Doesn't matter: Nothing is guaranteed about order of finalization (and
>> this is reasonable). Thus, the finalizers can be run in any arbitrary
>> order. The important point here is that they are run *at all*.
> It does matter because a destructor may use an object that has just been destroyed.

You can't do that in today's D either, going by the spec as well by the actual implementation.

David
May 24, 2012
Le 24/05/2012 18:10, Alex Rønne Petersen a écrit :
> On 24-05-2012 18:06, Andrei Alexandrescu wrote:
>> It does matter because a destructor may use an object that has just been
>> destroyed.
>>
>> Andrei
>>
>
> No, the docs specifically state that this is invalid (and it currently
> throws InvalidMemoryOperationError in most cases).
>
> Whether it *should* be allowed is arguable, but it isn't currently, both
> in docs and impl.
>

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 :)
May 24, 2012
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.


May 24, 2012
On 24-05-2012 19:47, Tove 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.
>
>

I would strongly advise against that, because a missed clear() means your finalizer may be run by the runtime's finalization machinery, and thus invalidate any invariants you were relying on in the finalizer.

-- 
Alex Rønne Petersen
alex@lycus.org
http://lycus.org
May 24, 2012
On Thursday, 24 May 2012 at 17:55:02 UTC, Alex Rønne Petersen wrote:
> I would strongly advise against that, because a missed clear() means your finalizer may be run by the runtime's finalization machinery, and thus invalidate any invariants you were relying on in the finalizer.

Yes – the »correc†
May 24, 2012
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
May 24, 2012
On Thursday, 24 May 2012 at 17:55:02 UTC, Alex Rønne Petersen wrote:
> I would strongly advise against that, because a missed clear() means your finalizer may be run by the runtime's finalization machinery, and thus invalidate any invariants you were relying on in the finalizer.

Yes – the »correct« way to handle situations where you need deterministic finalization is to use structs on the stack, possibly in conjunction with reference counting. Of course, there are some situations (e.g. when there are cycles) where this doesn't work, but at least it covers most of the »external non-memory resource« cases.

Composability can still be a problem, though, because holding a reference in a (GC-managed) class object might leave you with exactly the same problem you tried to avoid in the first place.

David