digitalmars.D - Destructor called while object is still alive
- frame (32/32) Oct 22 2020 Not sure if this is expected behaviour, but I find this weird:
- Daniel Kozak (12/17) Oct 22 2020 There is no use of foo anywhere so there is no reason to not collect it.
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (6/18) Oct 22 2020 It is in use until the end of main. Think locking. Seems like the
- Steven Schveighoffer (11/32) Oct 23 2020 I don't think so. A class reference itself doesn't have a destructor.
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (14/23) Oct 23 2020 But that is only a consequence of the optimizer not being written
- Steven Schveighoffer (24/49) Oct 23 2020 No, D classes map to C++ pointers to classes.
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (15/34) Oct 23 2020 Huh? D has to be able to call virtual destructors for C++ objects.
- Steven Schveighoffer (31/43) Oct 23 2020 And when are those called? When the lifetime of the *object* is over,
- H. S. Teoh (7/18) Oct 23 2020 [...]
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (30/47) Oct 23 2020 But the owning pointer is keeping the object alive.
- frame (7/16) Oct 23 2020 So how can I avoid this issue with DMD? Disabling any
- frame (3/4) Oct 23 2020 Correction: Issue disappears with -g flag, still exists with
- H. S. Teoh (11/17) Oct 23 2020 [...]
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (6/24) Oct 23 2020 LDC is better at detecting unreachable code so it actually does
- Johan Engelen (8/30) Oct 23 2020 I would certainly expect so. Same for GDC.
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (6/10) Oct 23 2020 Maybe you only need to do it in functions that are marked as not
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (8/10) Oct 24 2020 That would require that functions are marked as "sometimes does
- Steven Schveighoffer (13/30) Oct 23 2020 I don't know if you need to do that. As much discussion as this post has...
- frame (11/29) Oct 23 2020 But I doesn't do 3. and it's collected :(
- Johan Engelen (11/17) Oct 23 2020 Hi frame,
- Steven Schveighoffer (11/27) Oct 24 2020 A destructor is literally for resource management. For example, if a
- Johan Engelen (9/32) Oct 24 2020 My point is that the destructor of a GC-managed object may not be
- Kagamin (12/15) Oct 25 2020 Programs that don't care to close file handles are the reason why
- Steven Schveighoffer (7/19) Oct 25 2020 This was not on Windows, and the GC cleaned up the file handles as it
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (9/12) Oct 25 2020 Do you mean by the GC?
- Guillaume Piolat (20/24) Oct 25 2020 The real problem with coincidental correctness comes when your
- Kagamin (6/9) Oct 26 2020 Yes, GC environments differentiate between resource management
- Steven Schveighoffer (8/38) Oct 24 2020 Wait, so you use it inside the function after declaring it? And the
- frame (24/37) Oct 30 2020 Sorry, did not notice it.
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (2/4) Oct 23 2020 A dirty fix is to manually add the pointer as a GC root.
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (6/12) Oct 23 2020 Another fix is to make the compiler think the end is reachable by
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (11/11) Oct 23 2020 Not the prettiest or fastest trick, but the following code will
- frame (4/10) Oct 23 2020 Well that works thanks. But shouldn't the runtime do this
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (4/15) Oct 23 2020 No, because it means that the GC will not try to release the
- Kagamin (3/5) Oct 23 2020 You avoid it by correctly designing your code. Resource
- Kagamin (4/9) Oct 23 2020 Your C++ glasses are too thick, the snippet uses reference type,
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (6/8) Oct 23 2020 "Resource acquisition is initialization" is typically done with
- Steven Schveighoffer (9/18) Oct 23 2020 There's no RAII for classes or structs allocated on the heap in C++. You...
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (14/17) Oct 23 2020 The destructor may not be called at all by the GC, or it might be
- Daniel N (6/15) Oct 23 2020 Simple fix:
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (11/28) Oct 23 2020 Does not work with LDC -O3:
- Boris Carvajal (9/19) Oct 23 2020 Wow I couldn't believe 'scope' didn't work because it's used a
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (6/8) Oct 23 2020 Hm. I think what happens is that exit() makes the destructor call
- Boris Carvajal (3/12) Oct 23 2020 exit() is irrelevant, I checked the asm output.
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (3/4) Oct 23 2020 Ok, I guess GC.collect() is called after main() by the runtime
- Boris Carvajal (7/12) Oct 23 2020 Just to clarify my point.
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (10/22) Oct 23 2020 I understand.
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (3/5) Oct 23 2020 (I meant "deterministic destruction")
- Guillaume Piolat (15/23) Oct 24 2020 100% agree.
- Steven Schveighoffer (4/8) Oct 24 2020 Please update this:
Not sure if this is expected behaviour, but I find this weird: The destructor is called on the object just because it gets out of scope? I expect that either GC will not touch the object or only if no reference is on the object anymore. If the object is used in the loop, this will not happen but the GC or compiler should not decide to kill the object if a valid pointer is still in current scope? Even this is a "intelligent" feature by the compiler for non reachable code like that loop, it's still confusing. There are maybe situations while the object should stay in background, eg. socket related/event stuff. If there is a valid pointer, there is no excuse to reap it. class Foo { ~this() { writefln("Warning: destructor called (%s) on %s", typeid(this), this.__vptr); } } class Bar { static Foo create() { return new Foo(); } } void main() { auto foo = Bar.create(); writefln("address: %s", foo.__vptr); while(true) { Thread.sleep(1.seconds); GC.collect(); } } // DMD32 v2.094.1 -m64 on windows
Oct 22 2020
On Fri, Oct 23, 2020 at 8:20 AM frame via Digitalmars-d < digitalmars-d puremagic.com> wrote:Even this is a "intelligent" feature by the compiler for non reachable code like that loop, it's still confusing. There are maybe situations while the object should stay in background, eg. socket related/event stuff. If there is a valid pointer, there is no excuse to reap it.There is no use of foo anywhere so there is no reason to not collect it. You even does not need to have while in your code: void main() { auto foo = Bar.create(); writefln("address: %s", foo.__vptr); GC.collect(); Thread.sleep(1.seconds); //foo.sayHello(); // if uncommented GC would not call destructor until end of main }
Oct 22 2020
On Friday, 23 October 2020 at 06:37:43 UTC, Daniel Kozak wrote:On Fri, Oct 23, 2020 at 8:20 AM frame via Digitalmars-d < digitalmars-d puremagic.com> wrote:It is in use until the end of main. Think locking. Seems like the pointer is optimized away and then the GC collects. Makes RAII useless with GC. Basically a consequence of the optimizer assuming that there is no GC. A rather serious correctness issue.Even this is a "intelligent" feature by the compiler for non reachable code like that loop, it's still confusing. There are maybe situations while the object should stay in background, eg. socket related/event stuff. If there is a valid pointer, there is no excuse to reap it.There is no use of foo anywhere so there is no reason to not collect it.
Oct 22 2020
On 10/23/20 2:57 AM, Ola Fosheim Grøstad wrote:On Friday, 23 October 2020 at 06:37:43 UTC, Daniel Kozak wrote:But you don't get RAII with classes or pointers. Only with structs.On Fri, Oct 23, 2020 at 8:20 AM frame via Digitalmars-d < digitalmars-d puremagic.com> wrote:It is in use until the end of main. Think locking. Seems like the pointer is optimized away and then the GC collects. Makes RAII useless with GC.Even this is a "intelligent" feature by the compiler for non reachable code like that loop, it's still confusing. There are maybe situations while the object should stay in background, eg. socket related/event stuff. If there is a valid pointer, there is no excuse to reap it.There is no use of foo anywhere so there is no reason to not collect it.Basically a consequence of the optimizer assuming that there is no GC. A rather serious correctness issue.I don't think so. A class reference itself doesn't have a destructor. The compiler is free to elide storage just like it was a pointer. One cannot expect any specific behavior from the GC in this case, as the correctness of the program doesn't depend on the object staying in place for the duration of the function. Here there is just a disconnect in how the OP expects the compiler to implement the machine code. Use a type that *does* support RAII (i.e. struct with dtor), and it will be allocated and properly destroyed. -Steve
Oct 23 2020
On Friday, 23 October 2020 at 16:00:58 UTC, Steven Schveighoffer wrote:But you don't get RAII with classes or pointers. Only with structs.But that is only a consequence of the optimizer not being written with a GC in mind. That is the point. Isn't D structs the same as C++ PODs? I thought C++ classes with virtuals/RTTI is supposed to map to D classes? Then RAII has to work.I don't think so. A class reference itself doesn't have a destructor. The compiler is free to elide storage just like it was a pointer. One cannot expect any specific behavior from the GC in this case, as the correctness of the program doesn't depend on the object staying in place for the duration of the function. Here there is just a disconnect in how the OP expects the compiler to implement the machine code.That would be very surprising semantics as you expect a pointer to be LIVE throughout the scope in which it was instantiated. If it is LIVE then it cannot be collected by the GC. This all boils down to the optimizer being GC ignorant. I understand that it is too much work to make the optimizer take that into consideration, but it most certainly goes against the expected norm for the semantics of BLOCKS that LIVE pointers are being collected.
Oct 23 2020
On 10/23/20 12:14 PM, Ola Fosheim Grøstad wrote:On Friday, 23 October 2020 at 16:00:58 UTC, Steven Schveighoffer wrote:No, D classes map to C++ pointers to classes. How does C++ RAII work for class pointers that aren't used?But you don't get RAII with classes or pointers. Only with structs.But that is only a consequence of the optimizer not being written with a GC in mind. That is the point. Isn't D structs the same as C++ PODs? I thought C++ classes with virtuals/RTTI is supposed to map to D classes? Then RAII has to work.Expectation for what purpose? I think of code being executed in the way I write it. We all know that optimizers don't do that. As long as it's not changing the semantic meaning of the program, I'm fine with it. If you look at this code: void main() { int i; foo(); } Does i even exist? Does it exist after the first line? Is it important if the compiler allocates space for it on the stack? Does it get initialized to 0? All of these things are items that have no bearing on the execution of the program, so it's fine for the optimizer to just completely get rid of the variable storage. This is no different.I don't think so. A class reference itself doesn't have a destructor. The compiler is free to elide storage just like it was a pointer. One cannot expect any specific behavior from the GC in this case, as the correctness of the program doesn't depend on the object staying in place for the duration of the function. Here there is just a disconnect in how the OP expects the compiler to implement the machine code.That would be very surprising semantics as you expect a pointer to be LIVE throughout the scope in which it was instantiated. If it is LIVE then it cannot be collected by the GC.This all boils down to the optimizer being GC ignorant. I understand that it is too much work to make the optimizer take that into consideration, but it most certainly goes against the expected norm for the semantics of BLOCKS that LIVE pointers are being collected.I think the optimizer is correct. There are no live pointers, because the object is not used, ever. How many times has someone posted code touting a benchmark of something and the optimizer gets rid of the entire program? How is this any different? -Steve
Oct 23 2020
On Friday, 23 October 2020 at 16:30:52 UTC, Steven Schveighoffer wrote:No, D classes map to C++ pointers to classes.Huh? D has to be able to call virtual destructors for C++ objects.How does C++ RAII work for class pointers that aren't used?What do you mean? The pointer is valid until the end of the scope. Then the object is destructed if it is a sole owning pointer. _all_ GC pointers are conceptually owning pointers. They have to stay live throughout the whole scope.Expectation for what purpose?Basic CS conventions?I think of code being executed in the way I write it. We all know that optimizers don't do that. As long as it's not changing the semantic meaning of the program, I'm fine with it.But it did! That was the point!If you look at this code: void main() { int i; foo(); } Does i even exist? Does it exist after the first line? Is it important if the compiler allocates space for it on the stack? Does it get initialized to 0?What do you mean? There are no side effects? Do D allow side effects in destructors? If yes, then you cannot cap the lifetime of the object at your own will.just completely get rid of the variable storage. This is no different.It clearly is. The order of the writeln() execution was wrong.I think the optimizer is correct. There are no live pointers, because the object is not used, ever.Of course it is used. It has a destructor with a side effect.
Oct 23 2020
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:And when are those called? When the lifetime of the *object* is over, not the lifetime of the pointer to the object. My point was that C++ doesn't have class references, they have class pointers, which is akin to D class references.No, D classes map to C++ pointers to classes.Huh? D has to be able to call virtual destructors for C++ objects.I mean, if I compile this code for C++, does it store the pointer on the stack? void foo() { SomeClass* ptr = new SomeClass(); ... // bunch of other code that never uses ptr } C++ doesn't have automatic management using pointers. So the answer is, the optimizer might just not store `ptr`. Just like it doesn't happen in D.How does C++ RAII work for class pointers that aren't used?What do you mean? The pointer is valid until the end of the scope. Then the object is destructed if it is a sole owning pointer._all_ GC pointers are conceptually owning pointers. They have to stay live throughout the whole scope.It doesn't have to even have a pointer if it's never used. However, even though this case isn't "bad", there are bad cases that can be a problem: auto c = storeWithC(new C); If the function is something that squirrels away the parameter into a C-malloc'd block or C global (basically anything that is not scanned by the GC), and then returns the parameter, c is still not allocated on the stack, and the GC might collect it. If DMD isn't smart enough to see that the reference may have escaped, then it shouldn't elide storage. Under this view, I think actually the OP's case is a problem. Because Bar.create() could potentially be saving the result into a C heap block, in which case eliding the pointer storage is potentially going to cause memory corruption. The optimizer is wrong, and should be changed. Note that LDC does not do this optimization. -Steve
Oct 23 2020
On Fri, Oct 23, 2020 at 01:47:34PM -0400, Steven Schveighoffer via Digitalmars-d wrote: [...]If DMD isn't smart enough to see that the reference may have escaped, then it shouldn't elide storage. Under this view, I think actually the OP's case is a problem. Because Bar.create() could potentially be saving the result into a C heap block, in which case eliding the pointer storage is potentially going to cause memory corruption. The optimizer is wrong, and should be changed. Note that LDC does not do this optimization.[...] Yet another reason for me to avoid DMD and use LDC instead... T -- This sentence is false.
Oct 23 2020
On Friday, 23 October 2020 at 17:47:34 UTC, Steven Schveighoffer wrote:And when are those called? When the lifetime of the *object* is over, not the lifetime of the pointer to the object.But the owning pointer is keeping the object alive. A gc pointer is keeping the object alive, and is keeping it alive throughout its own lifetime. So yes, the lifetime of the object is supposed be at least as long as the owning pointer. And the lifetime lasts till the end of the scope. If there is a sideeffect then it should be considered to be _live_ until the end of the scope. With the GC semantics you don't get a guarantee for when it will be destructed, but you should have strong guarantees for how long the lifetime at least can be expected to be. Ideally you would also have guarantees for eventual destruction (i.e. that all objects are destroyed before the program terminates). With precise GC sans union, you should be able to get that.My point was that C++ doesn't have class references, they have class pointers, which is akin to D class references.Well, "reference" and "pointer" are two words for the same thing, although D has some syntactical and semantic restrictions on class references. That is pretty irrelevant though, as D class references with GC are owning pointers C++ have owning pointers named unique_ptr and shared_ptr. Naked pointers are borrowing pointers, or for manual management. Forget about the mechanisms, conceptually that is the case.I mean, if I compile this code for C++, does it store the pointer on the stack? void foo() { SomeClass* ptr = new SomeClass(); ... // bunch of other code that never uses ptr } C++ doesn't have automatic management using pointers. So the answer is, the optimizer might just not store `ptr`. Just like it doesn't happen in D.But C++ compilers don't provide a GC, and naked pointers are not owning pointers.But a destructor with a side effect IS usage, and it should never be executed when there is a conceptually live pointer pointing to the object. It is very simple, really._all_ GC pointers are conceptually owning pointers. They have to stay live throughout the whole scope.It doesn't have to even have a pointer if it's never used.
Oct 23 2020
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:No, D classes map to C++ pointers to classes.It doesn't have to even have a pointer if it's never used. ... The optimizer is wrong, and should be changed. Note that LDC does not do this optimization. -SteveSo how can I avoid this issue with DMD? Disabling any optimization? In fact it does not occur with debug mode enabled. scope doesn't change a thing. Actually I do use the pointer in real code, (just one method call before a loop but still used).
Oct 23 2020
On Friday, 23 October 2020 at 19:23:03 UTC, frame wrote:In fact it does not occur with debug mode enabled.Correction: Issue disappears with -g flag, still exists with -debug flag
Oct 23 2020
On Fri, Oct 23, 2020 at 07:23:03PM +0000, frame via Digitalmars-d wrote:On Friday, 23 October 2020 at 17:47:34 UTC, Steven Schveighoffer wrote:[...][...]The optimizer is wrong, and should be changed. Note that LDC does not do this optimization.So how can I avoid this issue with DMD? Disabling any optimization? In fact it does not occur with debug mode enabled.[...] IMNSHO, drop dmd and use ldc2 instead. Life is too short to have to deal with dmd backend bugs when I'm trying to get things *done*. This isn't the first time the dmd backend has problems when optimization / inlining are enabled, and I fear it won't be the last. T -- MS Windows: 64-bit rehash of 32-bit extensions and a graphical shell for a 16-bit patch to an 8-bit operating system originally coded for a 4-bit microprocessor, written by a 2-bit company that can't stand 1-bit of competition.
Oct 23 2020
On Friday, 23 October 2020 at 19:36:38 UTC, H. S. Teoh wrote:On Fri, Oct 23, 2020 at 07:23:03PM +0000, frame via Digitalmars-d wrote:LDC is better at detecting unreachable code so it actually does worse than DMD. https://run.dlang.io/is/2U5OgM LDC -O3 fails DMD -release succeedsOn Friday, 23 October 2020 at 17:47:34 UTC, Steven Schveighoffer wrote:[...][...]The optimizer is wrong, and should be changed. Note that LDC does not do this optimization.So how can I avoid this issue with DMD? Disabling any optimization? In fact it does not occur with debug mode enabled.[...] IMNSHO, drop dmd and use ldc2 instead. Life is too short to have to deal with dmd backend bugs when I'm trying to get things *done*. This isn't the first time the dmd backend has problems when optimization / inlining are enabled, and I fear it won't be the last.
Oct 23 2020
On Friday, 23 October 2020 at 21:55:36 UTC, Ola Fosheim Grøstad wrote:On Friday, 23 October 2020 at 19:36:38 UTC, H. S. Teoh wrote:I would certainly expect so. Same for GDC. We can fix this in LDC (quite easily it looks like. We can force LLVM to put&keep certain pointers on the stack), but I first would like to see consensus about this issue from the language designers and see it in text in the language spec. -JohanOn Fri, Oct 23, 2020 at 07:23:03PM +0000, frame via Digitalmars-d wrote:LDC is better at detecting unreachable code so it actually does worse than DMD.On Friday, 23 October 2020 at 17:47:34 UTC, Steven Schveighoffer wrote:[...][...]The optimizer is wrong, and should be changed. Note that LDC does not do this optimization.So how can I avoid this issue with DMD? Disabling any optimization? In fact it does not occur with debug mode enabled.[...] IMNSHO, drop dmd and use ldc2 instead. Life is too short to have to deal with dmd backend bugs when I'm trying to get things *done*. This isn't the first time the dmd backend has problems when optimization / inlining are enabled, and I fear it won't be the last.
Oct 23 2020
On Friday, 23 October 2020 at 23:33:59 UTC, Johan Engelen wrote:We can fix this in LDC (quite easily it looks like. We can force LLVM to put&keep certain pointers on the stack), but I first would like to see consensus about this issue from the language designers and see it in text in the language spec.Maybe you only need to do it in functions that are marked as not returning normally (bottom). It is already implicit in the current spec that variables are valid throughout their scope, so it is the current behaviour that should be documented as it violates static binding principles.
Oct 23 2020
On Saturday, 24 October 2020 at 06:50:13 UTC, Ola Fosheim Grøstad wrote:Maybe you only need to do it in functions that are marked as not returning normally (bottom).That would require that functions are marked as "sometimes does not return normally"... I guess another option is to go over the ssa after optimization and do a rerun with spilled pointers to the stack... Spilling all potential gc pointers returned from functions to the stack is exessive. It would include void pointers etc.
Oct 24 2020
On 10/23/20 3:23 PM, frame wrote:On Friday, 23 October 2020 at 17:47:34 UTC, Steven Schveighoffer wrote:I don't know if you need to do that. As much discussion as this post has generated, and even though the optimization is wrong, the specific circumstances that could cause a piece of memory to be collected before it's used is really unlikely. To spell it out again: 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.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:No, D classes map to C++ pointers to classes.It doesn't have to even have a pointer if it's never used. ... The optimizer is wrong, and should be changed. Note that LDC does not do this optimization.So how can I avoid this issue with DMD? Disabling any optimization? In fact it does not occur with debug mode enabled.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. -Steve
Oct 23 2020
On Friday, 23 October 2020 at 19:39:52 UTC, Steven Schveighoffer wrote:On 10/23/20 3:23 PM, frame wrote:But I doesn't do 3. and it's collected :( There is no special release optimization, I just call it via rdmd defaults. 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.On Friday, 23 October 2020 at 17:47:34 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.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: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. -Steve
Oct 23 2020
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). cheers, Johan
Oct 23 2020
On 10/23/20 7:30 PM, Johan Engelen wrote:On Friday, 23 October 2020 at 20:27:37 UTC, frame wrote: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. -SteveEven 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).
Oct 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: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. -JohanOn Friday, 23 October 2020 at 20:27:37 UTC, frame wrote: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.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).
Oct 24 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.
Oct 25 2020
On 10/25/20 5:17 AM, Kagamin wrote:On Saturday, 24 October 2020 at 11:56:54 UTC, Steven Schveighoffer wrote: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. -SteveI 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.
Oct 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.
Oct 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. -SteveThe 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.
Oct 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.Yes, GC environments differentiate between resource management and defensive cleanup, the latter is done by destructor/finalizer, the former is done synchronously, they also run in different environments, so the code is often different too.
Oct 26 2020
On 10/23/20 4:27 PM, frame wrote:On Friday, 23 October 2020 at 19:39:52 UTC, Steven Schveighoffer wrote: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?On 10/23/20 3:23 PM, frame wrote:But I doesn't do 3. and it's collected :( There is no special release optimization, I just call it via rdmd defaults.On Friday, 23 October 2020 at 17:47:34 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.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: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.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
Oct 24 2020
On Saturday, 24 October 2020 at 11:52:27 UTC, Steven Schveighoffer wrote: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: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?Sorry, did not notice it. This is basically the code I posted at begin. A class object holds a connection and the whole object can be fetched via a static method, returning as class variable. There is no problem if the compiler does not detect unreachable code. But for testing purposes I just attached a loop below the instantion after calling connect() on my new object. After a while, the destructor was called while the loop was still running and the object idle but alive. I'm not sure how this have an impact on other scenarios - if the object would have started a thread and communicate via socket, for example. But I think it would be the same behaviour if the compiler want to be smart. It smells wrong since the object pointer was still valid and resides in the current scope and GC should be only allowed to purge the object after leaving the scope. Resource management is no point here. It was just a side effect that the connection was closed because the connection was created by that class and the complete object was just killed by GC. addRoot() also solves the problem for the testcase. So again, the object was alive and valid - not total out of scope or garbage - just unused after that line.
Oct 30 2020
On Friday, 23 October 2020 at 19:23:03 UTC, frame wrote:So how can I avoid this issue with DMD? Disabling any optimization? In fact it does not occur with debug mode enabled.A dirty fix is to manually add the pointer as a GC root.
Oct 23 2020
On Friday, 23 October 2020 at 19:39:52 UTC, Ola Fosheim Grøstad wrote:On Friday, 23 October 2020 at 19:23:03 UTC, frame wrote:Another fix is to make the compiler think the end is reachable by warpping the infinitie loop in an if statements that tests something that the compiler cannot prove to be true. E.g. if (alwaystrue()) inifinteloopSo how can I avoid this issue with DMD? Disabling any optimization? In fact it does not occur with debug mode enabled.A dirty fix is to manually add the pointer as a GC root.
Oct 23 2020
Not the prettiest or fastest trick, but the following code will prevent the end of the scope being viewed as unreachable. You could do a lot better with just loading a 1 into a register using machine language. Probably something else you can test in the standard library that the compiler does not have access to. auto nevernull = malloc(1); if (nevernull != null){ free(nevernull); while(1){…}; } // this is no longer viewed as unreachable
Oct 23 2020
On Friday, 23 October 2020 at 19:39:52 UTC, Ola Fosheim Grøstad wrote:On Friday, 23 October 2020 at 19:23:03 UTC, frame wrote:Well that works thanks. But shouldn't the runtime do this automatically?So how can I avoid this issue with DMD? Disabling any optimization? In fact it does not occur with debug mode enabled.A dirty fix is to manually add the pointer as a GC root.
Oct 23 2020
On Friday, 23 October 2020 at 20:46:13 UTC, frame wrote:On Friday, 23 October 2020 at 19:39:52 UTC, Ola Fosheim Grøstad wrote:No, because it means that the GC will not try to release the memory... Maybe cleaner to use a global variable instead?On Friday, 23 October 2020 at 19:23:03 UTC, frame wrote:Well that works thanks. But shouldn't the runtime do this automatically?So how can I avoid this issue with DMD? Disabling any optimization? In fact it does not occur with debug mode enabled.A dirty fix is to manually add the pointer as a GC root.
Oct 23 2020
On Friday, 23 October 2020 at 19:23:03 UTC, frame wrote:So how can I avoid this issue with DMD? Disabling any optimization?You avoid it by correctly designing your code. Resource management with class destructors is incorrect design.
Oct 23 2020
On Saturday, 24 October 2020 at 06:35:00 UTC, Kagamin wrote:On Friday, 23 October 2020 at 19:23:03 UTC, frame wrote:Yeah, I understand but this doesn't help if resources are wiped when the object gets destroyed too. I have no problem with that GC may not call the destructor - I have a problem if the GC call the destructor out of the wild. It's also ok that the optimizations are done in my point of view but as a newbie, for me this should be well documented somewhere.So how can I avoid this issue with DMD? Disabling any optimization?You avoid it by correctly designing your code. Resource management with class destructors is incorrect design.
Oct 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.
Oct 24 2020
On Friday, 23 October 2020 at 06:57:05 UTC, Ola Fosheim Grøstad wrote:It is in use until the end of main. Think locking. Seems like the pointer is optimized away and then the GC collects. Makes RAII useless with GC. Basically a consequence of the optimizer assuming that there is no GC. A rather serious correctness issue.Your C++ glasses are too thick, the snippet uses reference type, there's no RAII there.
Oct 23 2020
On Friday, 23 October 2020 at 16:27:53 UTC, Kagamin wrote:Your C++ glasses are too thick, the snippet uses reference type, there's no RAII there."Resource acquisition is initialization" is typically done with reference types. Doesn't matter whether you name them "struct" or "class". So what? It is a completely bogus argument. Since D now has a precise GC you should get reliable destruction if you avoid unions.
Oct 23 2020
On 10/23/20 12:33 PM, Ola Fosheim Grøstad wrote:On Friday, 23 October 2020 at 16:27:53 UTC, Kagamin wrote:There's no RAII for classes or structs allocated on the heap in C++. You have to manually manage the lifetime (either by manually freeing it or encapsulating it into an RAII structure like shared_ptr). You only get RAII for items allocated on the stack. But the *resource* is likely a reference. The *owner* of the resource is the struct that lives on the stack. A class reference is simply a pointer, not an RAII-enabled structure. -SteveYour C++ glasses are too thick, the snippet uses reference type, there's no RAII there."Resource acquisition is initialization" is typically done with reference types. Doesn't matter whether you name them "struct" or "class". So what? It is a completely bogus argument. Since D now has a precise GC you should get reliable destruction if you avoid unions.
Oct 23 2020
On Friday, 23 October 2020 at 06:15:32 UTC, frame wrote:Not sure if this is expected behaviour, but I find this weird: The destructor is called on the object just because it gets out of scope?The destructor may not be called at all by the GC, or it might be called whenever there is no pointer found in memory/registers. Since D is essentially using C-style code-gen/optimizers that has little to no knowledge of a GC it may be called prematurely or never. 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... 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.
Oct 23 2020
On Friday, 23 October 2020 at 07:32:02 UTC, Ola Fosheim Grøstad wrote:On Friday, 23 October 2020 at 06:15:32 UTC, frame wrote:Simple fix: auto foo = Bar.create(); => scope foo = Bar.create();Not sure if this is expected behaviour, but I find this weird: The destructor is called on the object just because it gets out of scope?The destructor may not be called at all by the GC, or it might be called whenever there is no pointer found in memory/registers. Since D is essentially using C-style code-gen/optimizers that has little to no knowledge of a GC it may be called prematurely or never.
Oct 23 2020
On Friday, 23 October 2020 at 07:39:37 UTC, Daniel N wrote:On Friday, 23 October 2020 at 07:32:02 UTC, Ola Fosheim Grøstad wrote:Does not work with LDC -O3: void main() { scope foo = Bar.create(); GC.collect(); writefln("Collection done"); exit(0); } Output: Warning: destructor called (onlineapp.Foo) on 55A899C1C570 Collection doneOn Friday, 23 October 2020 at 06:15:32 UTC, frame wrote:Simple fix: auto foo = Bar.create(); => scope foo = Bar.create();Not sure if this is expected behaviour, but I find this weird: The destructor is called on the object just because it gets out of scope?The destructor may not be called at all by the GC, or it might be called whenever there is no pointer found in memory/registers. Since D is essentially using C-style code-gen/optimizers that has little to no knowledge of a GC it may be called prematurely or never.
Oct 23 2020
On Friday, 23 October 2020 at 08:32:45 UTC, Ola Fosheim Grøstad wrote:Does not work with LDC -O3: void main() { scope foo = Bar.create(); GC.collect(); writefln("Collection done"); exit(0); } Output: Warning: destructor called (onlineapp.Foo) on 55A899C1C570 Collection doneWow I couldn't believe 'scope' didn't work because it's used a lot for this same purpose. But after checking DMD sources I realized that 'scope' works only if it precedes a new expression initializer (scope var = new ClassName...) which makes sense but it's really confusing. That means in OP example 'scope' does nothing, it's just like 'auto' and the class is heap allocated.
Oct 23 2020
On Friday, 23 October 2020 at 12:53:52 UTC, Boris Carvajal wrote:That means in OP example 'scope' does nothing, it's just like 'auto' and the class is heap allocated.Hm. I think what happens is that exit() makes the destructor call unreachable therefore the pointer to the object is not saved on the stack and not retained in any registers. Thus GC.collect finds no references to it. It works if you remove exit().
Oct 23 2020
On Friday, 23 October 2020 at 13:11:49 UTC, Ola Fosheim Grøstad wrote:On Friday, 23 October 2020 at 12:53:52 UTC, Boris Carvajal wrote:exit() is irrelevant, I checked the asm output.That means in OP example 'scope' does nothing, it's just like 'auto' and the class is heap allocated.Hm. I think what happens is that exit() makes the destructor call unreachable therefore the pointer to the object is not saved on the stack and not retained in any registers. Thus GC.collect finds no references to it. It works if you remove exit().
Oct 23 2020
On Friday, 23 October 2020 at 13:23:18 UTC, Boris Carvajal wrote:exit() is irrelevant, I checked the asm output.Ok, I guess GC.collect() is called after main() by the runtime then.
Oct 23 2020
On Friday, 23 October 2020 at 13:34:56 UTC, Ola Fosheim Grøstad wrote:On Friday, 23 October 2020 at 13:23:18 UTC, Boris Carvajal wrote:Just to clarify my point. I was talking about 'scope foo = Bar.create();' that does nothing, neither the class is stack allocated nor the pointer is kept until the end of the function. I think you are totally right about the optimizer vs D GC.exit() is irrelevant, I checked the asm output.Ok, I guess GC.collect() is called after main() by the runtime then.
Oct 23 2020
On Friday, 23 October 2020 at 13:48:52 UTC, Boris Carvajal wrote:On Friday, 23 October 2020 at 13:34:56 UTC, Ola Fosheim Grøstad wrote:I understand. I guess the goal of safe makes the expected semantics for scope (explicit destruction) impossible. The compiler does not know if there are more references to the scope-pointed object. D would need an "isolated" pointer type for that. I think an ARC implementation written for C++ shared_ptr would have been interesting for class-objects, but it is not on the table...On Friday, 23 October 2020 at 13:23:18 UTC, Boris Carvajal wrote:Just to clarify my point. I was talking about 'scope foo = Bar.create();' that does nothing, neither the class is stack allocated nor the pointer is kept until the end of the function.exit() is irrelevant, I checked the asm output.Ok, I guess GC.collect() is called after main() by the runtime then.
Oct 23 2020
On Friday, 23 October 2020 at 13:58:04 UTC, Ola Fosheim Grøstad wrote:I guess the goal of safe makes the expected semantics for scope (explicit destruction) impossible.(I meant "deterministic destruction")
Oct 23 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-classIMO, 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.
Oct 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-classPlease update this: https://dlang.org/phobos/core_memory.html#.GC.inFinalizer -Steve
Oct 24 2020