December 21, 2017
On Thursday, 21 December 2017 at 18:48:38 UTC, H. S. Teoh wrote:
>> Alas, RefCounted doesn't work well with inheritance...
>
> Oh?  What's the issue?

Implicit casts don't work so you can't pass a RefCounted!Class as RefCounted!Interface except in simple cases using alias this tricks.


December 22, 2017
On Thursday, 21 December 2017 at 19:43:16 UTC, Steven Schveighoffer wrote:

> The gist is, you cannot expect that destructors will be run in a timely manner, or at all.
>
> They may be called, and most of the time they are. But the language nor the current implementation makes a guarantee that they will be called.

I understand that we can't deterministically predict when a destructor will be called, but if we can't deterministically predict if a destructor will be called, that seems asinine.

What condition(s) would cause a destructor for an object that is managed by the GC to potentially not be called?

Thanks,
Mike

December 22, 2017
On Friday, 22 December 2017 at 00:09:31 UTC, Mike Franklin wrote:

>
> What condition(s) would cause a destructor for an object that is managed by the GC to potentially not be called?
>

Here:

===========
import std.stdio;

class Clazz {
    ~this() {
        writeln("Class dest");
    }
}

void makeClazz() {
    auto clazz = new Clazz;
}

void main() {
    makeClazz();
}

static ~this() {
    writeln("Static dest");
}
============

This outputs:

============
Static dest
Class dest
============

The class destructor is not run during the lifetime of the program. The fact that it's run during runtime termination is an implementation detail. Another implementation might not run a finalization at termination.

So the destructors (finalizers) are only run when an object is collected. No collection, no destructor call.
December 22, 2017
On Thursday, 21 December 2017 at 19:43:16 UTC, Steven Schveighoffer wrote:
> On 12/20/17 9:57 PM, Mike Franklin wrote:
>> [...]
>
> It's implementation defined :)
>
> The gist is, you cannot expect that destructors will be run in a timely manner, or at all.
>
> They may be called, and most of the time they are. But the language nor the current implementation makes a guarantee that they will be called.
>
> For this reason, any classes that use non-memory resources should clean up those resources before becoming garbage. This is why most of the time, such items are managed by structs.
>
> Note that the same non-guarantee exists in other GC'd languages, such as Java or C#.
>
> -Steve

Except for that in C# you have the IDisposable interface, which can actually be used to prevent this kind of stuff and generally used to clean up non-GC memory.
December 22, 2017
On Thursday, 21 December 2017 at 18:20:19 UTC, H. S. Teoh wrote:
> Even calling GC.collect directly did not guarantee the DB handle was closed at the right time.  This may have been a bug in my code that left dangling references to it, or perhaps the array of Database handles was still scanned through by the GC even though the only remaining array slice has a shorter length. Whatever the reason was, it left me with the very unpleasant prospect of silently accumulating file descriptor leaks.

Last I checked, the GC doesn't understand arrays. It only understands "segment of memory that might contain pointers" and "segment of memory that doesn't contain pointers". You might have gotten better results if you had nulled out the reference in the array.

Of course, that relies on not having any remaining references on the stack or in registers, neither of which is easy to guarantee.
December 22, 2017
On Thursday, 21 December 2017 at 18:45:27 UTC, Adam D. Ruppe wrote:
> On Thursday, 21 December 2017 at 18:20:19 UTC, H. S. Teoh wrote:
>> When the scoped destruction of structs isn't an option, RefCounted!T seems to be a less evil alternative than an unreliable class dtor. :-/
>
> Alas, RefCounted doesn't work well with inheritance...
>
> Though, what you could do is make the refcounted owners and borrow the actual reference later.

i really wonder how Objective-C and Swift is pulling this off.
December 23, 2017
On Friday, 22 December 2017 at 23:34:55 UTC, Mengu wrote:
> i really wonder how Objective-C and Swift is pulling this off.

It isn't a fundamental problem, D just can't express it in the existing language (heck, even D, as defined, could do it, the implementation just isn't there.)
December 23, 2017
On Friday, 22 December 2017 at 00:09:31 UTC, Mike Franklin wrote:
>
> What condition(s) would cause a destructor for an object that is managed by the GC to potentially not be called?
>

Good question. It's true that barring an Error, they should be called by the GC at runtime termination.

December 24, 2017
On Friday, December 22, 2017 01:23:26 Mike Parker via Digitalmars-d-learn wrote:
> The class destructor is not run during the lifetime of the program. The fact that it's run during runtime termination is an implementation detail. Another implementation might not run a finalization at termination.
>
> So the destructors (finalizers) are only run when an object is collected. No collection, no destructor call.

IIRC, there were reasons why running the destructors/finalizers when unloading dynamically loaded libraries was a problem too and one that we were stuck with, but I could be remembering incorrectly.

Regardless, even if it were the case that it were guaranteed that all finalizers were run when the program exited, it would still be terrible practice to rely on it. It's trivial to end up in a situation where no collection is run for quite some time (e.g. just don't do much memory allocation for a while), which would leave any resources that needed to be freed by a finalizer unfreed for quite a while even though they weren't needed anymore. So practically speaking, it doesn't really matter where the finalizers are guaranteed to run. Relying on them to be run rather than forcing them to be run via destroy or using some other helper function is just going to cause problems, so it's just plain bad practice to rely on finalizers to be run to release resources. That's just life with GC's in general, not just D's. It's why C# has the while dispose/IDisposable thing, and why D code should either be using destroy to deal with freeing resources for a class or using structs on the stack for resources that need to be freed. Or alternate memory strategies can be used via std.experimental.allocator. The GC works great for lots of stuff but not for system resources. Honestly, in some ways, we'd be better off if D didn't even have finalizers.

- Jonathan M Davis

December 28, 2017
On Sun, Dec 24, 2017 at 02:07:26PM -0700, Jonathan M Davis via Digitalmars-d-learn wrote: [...]
> Regardless, even if it were the case that it were guaranteed that all finalizers were run when the program exited, it would still be terrible practice to rely on it. It's trivial to end up in a situation where no collection is run for quite some time (e.g. just don't do much memory allocation for a while), which would leave any resources that needed to be freed by a finalizer unfreed for quite a while even though they weren't needed anymore. So practically speaking, it doesn't really matter where the finalizers are guaranteed to run. Relying on them to be run rather than forcing them to be run via destroy or using some other helper function is just going to cause problems, so it's just plain bad practice to rely on finalizers to be run to release resources. That's just life with GC's in general, not just D's. It's why C# has the while dispose/IDisposable thing, and why D code should either be using destroy to deal with freeing resources for a class or using structs on the stack for resources that need to be freed. Or alternate memory strategies can be used via std.experimental.allocator. The GC works great for lots of stuff but not for system resources. Honestly, in some ways, we'd be better off if D didn't even have finalizers.
[...]

This makes me wonder if a better approach to memory management is to use refcounting by default, and fallback to the GC to collect cycles.

In my current project, I'll probably end up having to use RefCounted!Database just so I can have deterministic releasing of database handles without needing to worry about dangling references that may still be lingering around. (Currently, I'm just calling .destroy directly on the handle when I'm done with it, but there's a slim possibility that there might be dangling references left somewhere. So that needs to be addressed at some point.)


T

-- 
"I suspect the best way to deal with procrastination is to put off the procrastination itself until later. I've been meaning to try this, but haven't gotten around to it yet. " -- swr