October 24, 2020
On 10/23/20 4:27 PM, frame wrote:
> On Friday, 23 October 2020 at 19:39:52 UTC, Steven Schveighoffer wrote:
>> On 10/23/20 3:23 PM, frame wrote:
>>> On Friday, 23 October 2020 at 17:47:34 UTC, Steven Schveighoffer wrote:
>>>> On 10/23/20 12:48 PM, Ola Fosheim Grøstad wrote:
>>>>> On Friday, 23 October 2020 at 16:30:52 UTC, Steven Schveighoffer wrote:
>>
>>
>> 1. You generate a GC block (i.e. new class)
>> 2. You store it as part of an initializer for a stack local
>> 3. You never use it in the stack frame
>> 4. But you also in that same initialization put it into a location that is not scanned by the GC (e.g. C-malloc'd data)
>>
>> If you aren't doing all 4 of these things, then you don't have to worry.
>>
>>> Actually I do use the pointer in real code, (just one method call before a loop but still used).
>>>
>>
>> So it shouldn't be collected then, even with optimizations.
>>
> But I doesn't do 3. and it's collected :(
> There is no special release optimization, I just call it via rdmd defaults.

Wait, so you use it inside the function after declaring it? And the pointer isn't stored in the stack?

This is different from your original code. Can you post an example of that happening?

> 
> Even if I do not run into memory problems, calling the destructor on a live object is an unepxected side effect which makes the dtor concept useless for me. It closes the remote connection the object hold (please don't ask about the meaning, it just the way it has to work) and this must not happen while the program is still running.

Sure, but if you use it during your function, it should be stored on the stack, and therefore not collected.

-Steve
October 24, 2020
On 10/23/20 7:30 PM, Johan Engelen wrote:
> On Friday, 23 October 2020 at 20:27:37 UTC, frame wrote:
>>
>> Even if I do not run into memory problems, calling the destructor on a live object is an unepxected side effect which makes the dtor concept useless for me. It closes the remote connection the object hold (please don't ask about the meaning, it just the way it has to work) and this must not happen while the program is still running.
> 
> Hi frame,
>    Regardless of the topic being discussed, you should not do resource management using ctor/dtor of a GC-managed object. The dtor is not guaranteed to be called (not even on program exit). Indeed, the dtor as you know from stack allocated objects works very differently for GC-allocated objects; this feels like the dtor concept is useless (I agree, I've also grown used to RAII-thinking about dtors).
> 

A destructor is literally for resource management. For example, if a class represents an OS handle, and that handle is encapsulated, the destructor is the right place to clean that up.

If you want it cleaned up synchronously, you explicitly destroy the object, or provide a function to clean it up.

But if you don't clean it up, and the GC is cleaning up the object, there's no reason to leak the resource.

I wrote a program long ago that used classes to manage open files. I never worried about cleaning up the file handles, and it worked fine.

-Steve
October 24, 2020
On Friday, 23 October 2020 at 07:32:02 UTC, Ola Fosheim Grøstad wrote:
>
> So basically, destructors do not play well with GC. I've in the past argued that one should not allow destructors on GC objects since there are no proper guarantees anyway. That could also speed up collection. But people prefer convenience over correctness...
>

100% agree.

I think Andrei also argued once that class destructors should not be called by the GC.
But what happened instead later on is that GC-allocated struct also did get their destructor called by the GC.

Not that the very-much-unused "GC Proof Resource Class"[1] idiom turns the GC into a detector for... overreliance on the GC for freeing resources.

[1] http://p0nce.github.io/d-idioms/#GC-proof-resource-class


> IMO, rule of thumb: do not rely on destructors being called at the appropriate time for objects on the GC heap. Do explicit finalisation instead.

For the GC objects that transitively own something that isn't memory.

If A owns B owns (a GC-allocated int[]),
then A and B destructors don't need to be called manually.

October 24, 2020
On 10/24/20 8:01 AM, Guillaume Piolat wrote:
> Not that the very-much-unused "GC Proof Resource Class"[1] idiom turns the GC into a detector for... overreliance on the GC for freeing resources.
> 
> [1] http://p0nce.github.io/d-idioms/#GC-proof-resource-class

Please update this:

https://dlang.org/phobos/core_memory.html#.GC.inFinalizer

-Steve
October 24, 2020
On Saturday, 24 October 2020 at 09:02:41 UTC, frame wrote:
> Yeah, I understand but this doesn't help if resources are wiped when the object gets destroyed too.

Correct design solves your problem. If you rely exclusively on GC for resource management, this scenario is not supported, because it's unworkable.
October 24, 2020
On Saturday, 24 October 2020 at 11:56:54 UTC, Steven Schveighoffer wrote:
> On 10/23/20 7:30 PM, Johan Engelen wrote:
>> On Friday, 23 October 2020 at 20:27:37 UTC, frame wrote:
>>>
>>> Even if I do not run into memory problems, calling the destructor on a live object is an unepxected side effect which makes the dtor concept useless for me. It closes the remote connection the object hold (please don't ask about the meaning, it just the way it has to work) and this must not happen while the program is still running.
>> 
>> Hi frame,
>>    Regardless of the topic being discussed, you should not do resource management using ctor/dtor of a GC-managed object. The dtor is not guaranteed to be called (not even on program exit). Indeed, the dtor as you know from stack allocated objects works very differently for GC-allocated objects; this feels like the dtor concept is useless (I agree, I've also grown used to RAII-thinking about dtors).
>> 
>
> A destructor is literally for resource management. For example, if a class represents an OS handle, and that handle is encapsulated, the destructor is the right place to clean that up.

My point is that the destructor of a GC-managed object may not be called at all, hence in general (!) plain GC+destructor is not suitable for resource management. If you are trying to manage resources that are released automatically (and correctly) by the OS upon program termination, you are lucky and can get away with it.

-Johan


October 25, 2020
On Saturday, 24 October 2020 at 11:56:54 UTC, Steven Schveighoffer wrote:
> I wrote a program long ago that used classes to manage open files. I never worried about cleaning up the file handles, and it worked fine.

Programs that don't care to close file handles are the reason why people believe that windows can't run for more than a week without restarts, a file handle is small for the program, but kernel allocates a fairly big structure about 4kb; when the program opens a million handles, it consumes 4gb of kernel memory, when a non-technical user sees this system wide memory shortage, he concludes time has come to restart the system. Particularly Nvidia user service was guilty of this and given that it's installed on most machines, you get the corresponding result.
October 25, 2020
On 10/25/20 5:17 AM, Kagamin wrote:
> On Saturday, 24 October 2020 at 11:56:54 UTC, Steven Schveighoffer wrote:
>> I wrote a program long ago that used classes to manage open files. I never worried about cleaning up the file handles, and it worked fine.
> 
> Programs that don't care to close file handles are the reason why people believe that windows can't run for more than a week without restarts, a file handle is small for the program, but kernel allocates a fairly big structure about 4kb; when the program opens a million handles, it consumes 4gb of kernel memory, when a non-technical user sees this system wide memory shortage, he concludes time has come to restart the system. Particularly Nvidia user service was guilty of this and given that it's installed on most machines, you get the corresponding result.

This was not on Windows, and the GC cleaned up the file handles as it ran. They weren't left open constantly.

But even if you have synchronous management of files, having a destructor clean up a file that obviously isn't used any more isn't a bad thing.

-Steve
October 25, 2020
On Sunday, 25 October 2020 at 15:50:36 UTC, Steven Schveighoffer wrote:
> But even if you have synchronous management of files, having a destructor clean up a file that obviously isn't used any more isn't a bad thing.

Do you mean by the GC?

Well, it can be a bad thing if the GC most of the time run destructors if you do transactions that complete in destructors or have buffers that are flushed and closed in destructors. For correctness it is actually better that the GC never run destructors. Or... that the language is adjusted so that you get precise guarantees.



October 25, 2020
On Sunday, 25 October 2020 at 15:50:36 UTC, Steven Schveighoffer wrote:
> But even if you have synchronous management of files, having a destructor clean up a file that obviously isn't used any more isn't a bad thing.
>
> -Steve

The real problem with coincidental correctness comes when your resource dependency graph gets more complex.

The typical example is:

  1. You've loaded a library upon which all library object depend, you get a handle H to be released with releaseH(H). You put it in a class.

  2. This handle H allows you to create a library object A, to be released with releaseA(H, A). A cannot be released if the library has been released. So In A you keep a reference to H so that it's not cleared by the GC ; else you can't call releaseA(H, A) in A's destructor. The problem is that A is polymorphic, so it can't be a struct.

  3. Your A goes out of scope at the end of the program, the GC "chooses" to call H destructor first, and then A's destructor, where the H member is dead. The user end up with the conclusion: "the GC has collected a live object".
     However at first you don't see the bug because up to know A's destructor was called first.