Re: On the D Blog--Symphony of Destruction: Structs, Classes, and the GC

2021-03-22 Thread James Blachly via Digitalmars-d-announce

On 3/18/21 5:21 AM, Per Nordlöw wrote:

On Thursday, 4 March 2021 at 13:54:48 UTC, Mike Parker wrote:

The blog:
https://dlang.org/blog/2021/03/04/symphony-of-destruction-structs-classes-and-the-gc-part-one/ 



Btw, what is the motive behind D's GC not being able to correctly handle 
GC allocations in  class destructors.


Is it by design or because of limitations in D's current GC implementation?

And how does this relate to exception-throwing destructors in other 
managed languages such as C# and Go; are they forbidden or allowed and 
safe thanks to a more resilient GC?


It is a frustrating rough edge, esp for non-experts; I cut myself when I 
tried to use a custom logging function (which of course GC allocates) in 
class destructors.


Re: On the D Blog--Symphony of Destruction: Structs, Classes, and the GC

2021-03-19 Thread apz28 via Digitalmars-d-announce

On Thursday, 18 March 2021 at 12:55:17 UTC, Mike Parker wrote:
On Thursday, 18 March 2021 at 12:27:56 UTC, Petar Kirov 
[ZombineDev] wrote:




Just implementation deficiency. I think it is fixable with 
some refactoring of the GC pipeline. One approach would be, 
(similar to other language implementations - see below), that 
GC-allocated objects with destructors should be placed on a 
queue and their destructors be called when the GC has finished 
the collection. Afterwards, the GC can release their memory 
during the next collection.


This need to improve which allow to call free memory in destructor
For realloc..., when size = 0, it needs to call free

https://github.com/dlang/druntime/blob/3a32cc0305d4dd066f719d4c2df97337c86ea7ff/src/core/internal/gc/impl/conservative/gc.d#L444

vs

https://github.com/dlang/druntime/blob/3a32cc0305d4dd066f719d4c2df97337c86ea7ff/src/core/internal/gc/impl/conservative/gc.d#L678




Re: On the D Blog--Symphony of Destruction: Structs, Classes, and the GC

2021-03-19 Thread Paulo Pinto via Digitalmars-d-announce
On Thursday, 18 March 2021 at 12:27:56 UTC, Petar Kirov 
[ZombineDev] wrote:

On Thursday, 18 March 2021 at 09:21:27 UTC, Per Nordlöw wrote:

[...]


Just implementation deficiency. I think it is fixable with some 
refactoring of the GC pipeline. One approach would be, (similar 
to other language implementations - see below), that 
GC-allocated objects with destructors should be placed on a 
queue and their destructors be called when the GC has finished 
the collection. Afterwards, the GC can release their memory 
during the next collection.


[...]


Small correction, since .NET 5 / C# 9, implementing IDisposable 
isn't required if the Dispose() method is available.


This is done as performance improvement for using structs with 
determistic destruction and avoid implicit convertions to 
references when interfaces are used.


Re: On the D Blog--Symphony of Destruction: Structs, Classes, and the GC

2021-03-19 Thread Per Nordlöw via Digitalmars-d-announce

On Thursday, 18 March 2021 at 12:21:46 UTC, Mike Parker wrote:
I actually don't agree with that. I'll be discussion the 
solution in the next article:


if(!GC.inFinalizer) {
...
}

It's perfectly fine to perform GC operations in destructors 
when they aren't invoked by the GC.


Could we at least add some guard in the GC that notifies the user 
of the reason for getting an exception, preferrably including a 
source position, when trying to allocate in a destructor run 
during finalization? Not getting an explanation has stolen hours 
of my development time on several occasion. And likely happen in 
the future for other users aswell potentially making them abandon 
D for other languages.


Re: On the D Blog--Symphony of Destruction: Structs, Classes, and the GC

2021-03-18 Thread Steven Schveighoffer via Digitalmars-d-announce

On 3/18/21 8:55 AM, Mike Parker wrote:

On Thursday, 18 March 2021 at 12:27:56 UTC, Petar Kirov [ZombineDev] wrote:



Just implementation deficiency. I think it is fixable with some 
refactoring of the GC pipeline. One approach would be, (similar to 
other language implementations - see below), that GC-allocated objects 
with destructors should be placed on a queue and their destructors be 
called when the GC has finished the collection. Afterwards, the GC can 
release their memory during the next collection.


As I understand, finalizers in D are run because the GC needs more 
memory *now*. Deferring release of memory until the next collection 
would defeat the purpose. We would need to decouple collection cycles 
from allocation. Am I missing something?


I think this is the proper way to look at it. We are running a 
collection cycle because more memory is needed. If you allocate inside 
the GC, likely you would trigger another GC.


However, there are probably ways around this. For instance, you can 
allocate memory without triggering a GC, and we can probably try that 
instead.


AIUI, the stop-the-world phase is only for scanning. Once scanning is 
done, there is nothing to say we can't change the pool data while 
looking for blocks to finalize.


Most likely, some of them will free up blocks that then can be used by a 
finalizer allocation.


Would be a good SAOC project.

-Steve


Re: On the D Blog--Symphony of Destruction: Structs, Classes, and the GC

2021-03-18 Thread Mike Parker via Digitalmars-d-announce
On Thursday, 18 March 2021 at 12:27:56 UTC, Petar Kirov 
[ZombineDev] wrote:




Just implementation deficiency. I think it is fixable with some 
refactoring of the GC pipeline. One approach would be, (similar 
to other language implementations - see below), that 
GC-allocated objects with destructors should be placed on a 
queue and their destructors be called when the GC has finished 
the collection. Afterwards, the GC can release their memory 
during the next collection.


As I understand, finalizers in D are run because the GC needs 
more memory *now*. Deferring release of memory until the next 
collection would defeat the purpose. We would need to decouple 
collection cycles from allocation. Am I missing something?






Re: On the D Blog--Symphony of Destruction: Structs, Classes, and the GC

2021-03-18 Thread Petar via Digitalmars-d-announce

On Thursday, 18 March 2021 at 09:21:27 UTC, Per Nordlöw wrote:

On Thursday, 4 March 2021 at 13:54:48 UTC, Mike Parker wrote:

The blog:
https://dlang.org/blog/2021/03/04/symphony-of-destruction-structs-classes-and-the-gc-part-one/


Btw, what is the motive behind D's GC not being able to 
correctly handle GC allocations in  class destructors.


Is it by design or because of limitations in D's current GC 
implementation?


Just implementation deficiency. I think it is fixable with some 
refactoring of the GC pipeline. One approach would be, (similar 
to other language implementations - see below), that GC-allocated 
objects with destructors should be placed on a queue and their 
destructors be called when the GC has finished the collection. 
Afterwards, the GC can release their memory during the next 
collection.


And how does this relate to exception-throwing destructors in 
other managed languages such as C# and Go; are they forbidden 
or allowed and safe thanks to a more resilient GC?


TL;DR
* Go doesn't have exceptions or destructors. You can attach a 
finalizer function to an object via [0] which will be called 
before the object will be collected. After the associated 
finalizer is called, the object is marked as reachable again and 
the finalizer function is unset. Since all finalizers are called 
in a separate goroutine, it is not an issue to allocate memory 
from them, as technically this happens separately from the actual 
garbage collection.


* There is something like destructors (aka finalizers) in C#, but 
they can't be used to implement the RAII design pattern. They are 
even less deterministic than destructors of GC-allocated classes 
in D, as they're only called automatically by the runtime and by 
an arbitrary thread. Their runtime designed in such a way that 
memory allocation in destructors is not a problem at all, however 
the default policy is that thrown exceptions terminate the 
process, though that could be configured differently.


---

Instead of destructors, the recommended idiom in Go is to wrap 
resources in wrapper structs and implement a Close() method for 
those types, which the user of the code must not forget to call 
manually and sometimes check for error. They have `defer`, which 
is similar to D's `scope (exit)`. Similar to C#, finalizers in Go 
are not reliable and should probably be only used as a safety net 
to detect whether an object was forgotten to be closed manually. 
If a finalizer takes a long time to complete a clean-up task, it 
is recommended that it spawns a separate goroutine.


---

C# has 2 concepts: finalizers and the IDisposable interface.

C# finalizers [1][2] are defined using the C++ destructor syntax 
`~T()` (rather than D's `~this()`) which is lowered to a method 
that overrides the Object.Finalize() base method like so:


class Resource { ~Resource() { /* custom code */ } } // user code

// gets lowered to:

class Resource
{
  protected override Finalize()
  {
try { /* custom code */ } finally { base.Finalize(); }
  }
}

Which means that finalization happens automatically from the 
most-derived class to the least derived one. This lowering also 
implies that the implementation is tolerant to exceptions. It is 
an compile-time error to manually define a `Finalize` method. 
Finalizers can only be defined by classes (reference types) and 
not structs (value types). Finalizers are only be called 
automatically (there's no analog to D's `destroy` or C++'s 
`delete`) and the only way to force that is using 
`System.GC.Collect()`, which is almost always bad idea. 
Finalizers used to be called at the end of the application when 
targeting .NET Framework, but the docs say that this is no longer 
the case with the newer the .NET Core, though this may have been 
addressed after the docs were written. The implementation may 
call finalizers from any thread, so your code must be prepared to 
handle that.


Given that finalizers are unsuitable for deterministic resource 
management, it is strongly recommended that class authors should 
implement the IDisposable [3] interface. Users of classes that 
implement IDisposable can either manually call 
IDisposable.Dispose() or they can use the `using` statement [4], 
which is lowered something like this:


using var r1 = new Resource1();
using var r2 = new Resource1();
/* some code */

// vvv

{
Resource1 r1 = new Resource1();
try
{
{
Resource2 r2 = expression;
try
{
/* some code */
}
finally
{
if (r2 != null) ((IDisposable)r2).Dispose();
}
}
}
finally
{
if (r1 != null) ((IDisposable)r1).Dispose();
}
}

IDisposable.Dispose() can be called multiple times (though this 
is discouraged), so your implementation of this interface must be 
able to handle this. Finalizer should call the Dispose() function 
as a safety net.


[0]: 

Re: On the D Blog--Symphony of Destruction: Structs, Classes, and the GC

2021-03-18 Thread Mike Parker via Digitalmars-d-announce

On Thursday, 18 March 2021 at 08:15:01 UTC, Per Nordlöw wrote:



In the mean time a good rule of thumb is to qualify all class 
destructors as @nogc. I suggest you add this advice to the 
article, Mike.




I actually don't agree with that. I'll be discussion the solution 
in the next article:


if(!GC.inFinalizer) {
...
}

It's perfectly fine to perform GC operations in destructors when 
they aren't invoked by the GC.


Re: On the D Blog--Symphony of Destruction: Structs, Classes, and the GC

2021-03-18 Thread Per Nordlöw via Digitalmars-d-announce

On Thursday, 4 March 2021 at 13:54:48 UTC, Mike Parker wrote:

The blog:
https://dlang.org/blog/2021/03/04/symphony-of-destruction-structs-classes-and-the-gc-part-one/


Btw, what is the motive behind D's GC not being able to correctly 
handle GC allocations in  class destructors.


Is it by design or because of limitations in D's current GC 
implementation?


And how does this relate to exception-throwing destructors in 
other managed languages such as C# and Go; are they forbidden or 
allowed and safe thanks to a more resilient GC?


Re: On the D Blog--Symphony of Destruction: Structs, Classes, and the GC

2021-03-18 Thread Per Nordlöw via Digitalmars-d-announce

On Thursday, 4 March 2021 at 13:54:48 UTC, Mike Parker wrote:
The blog:

https://dlang.org/blog/2021/03/04/symphony-of-destruction-structs-classes-and-the-gc-part-one/


Reminds me of longing for an (optional) compiler warning or, even 
better, a deprecation when


- destructors of GC-managed objects that perform any operation 
that can potentially result in a GC allocation request and /or

- allocating structs on the GC heap that have destructors

.

In the mean time a good rule of thumb is to qualify all class 
destructors as @nogc. I suggest you add this advice to the 
article, Mike.


Thanks!


Re: On the D Blog--Symphony of Destruction: Structs, Classes, and the GC

2021-03-05 Thread Steven Schveighoffer via Digitalmars-d-announce

On 3/4/21 6:42 PM, Dukc wrote:

On Thursday, 4 March 2021 at 13:54:48 UTC, Mike Parker wrote:


The blog:
https://dlang.org/blog/2021/03/04/symphony-of-destruction-structs-classes-and-the-gc-part-one/ 



"Some examples: attempting to index an associative array can trigger an 
attempt to allocate a RangeError if the key is not present; a failed 
assert will result in allocation of an AssertError; calling any function 
not annotated with @nogc means GC operations are always possible in the 
call stack. These and any such operations should be avoided in the 
destructors of GC-managed objects."


I don't understand this part. If an assert was failing, the program is 
going to terminate anyway, so InvalidMemoryOperationError is no problem. 
Well, it might obfuscate the underlying error if there is no stack 
trace, but banning `assert`ing in anything that could be called by a 
destructor sounds too drastic to me. Even the lowest level system code 
tends to contain asserts in D, at least in my codebase. If asserting is 
banned, destructors can do faily much nothing. I'd think it's much more 
practical to redefine the assert failure handler if 
InvalidMemoryOperationError due to a failed assert is a problem.


And technically, a range error does not allocate.

https://github.com/dlang/druntime/blob/306bd965ea9d83bad7e5444ff9d5e1af1a6d934a/src/core/exception.d#L472-L485

But there is still a possibility of allocation if you happen to rehash 
an AA.


-Steve


Re: On the D Blog--Symphony of Destruction: Structs, Classes, and the GC

2021-03-05 Thread Steven Schveighoffer via Digitalmars-d-announce

On 3/4/21 6:42 PM, Dukc wrote:

On Thursday, 4 March 2021 at 13:54:48 UTC, Mike Parker wrote:


The blog:
https://dlang.org/blog/2021/03/04/symphony-of-destruction-structs-classes-and-the-gc-part-one/ 



"Some examples: attempting to index an associative array can trigger an 
attempt to allocate a RangeError if the key is not present; a failed 
assert will result in allocation of an AssertError; calling any function 
not annotated with @nogc means GC operations are always possible in the 
call stack. These and any such operations should be avoided in the 
destructors of GC-managed objects."


I don't understand this part. If an assert was failing, the program is 
going to terminate anyway, so InvalidMemoryOperationError is no problem. 
Well, it might obfuscate the underlying error if there is no stack 
trace, but banning `assert`ing in anything that could be called by a 
destructor sounds too drastic to me. Even the lowest level system code 
tends to contain asserts in D, at least in my codebase. If asserting is 
banned, destructors can do faily much nothing. I'd think it's much more 
practical to redefine the assert failure handler if 
InvalidMemoryOperationError due to a failed assert is a problem.


One further note: you want to avoid InvalidMemoryOperationError AT ALL 
COSTS if you can. Why? Because it identifies not the file or line which 
triggered the invalid memory operation, but the place where it's thrown 
in druntime (currently here: 
https://github.com/dlang/druntime/blob/306bd965ea9d83bad7e5444ff9d5e1af1a6d934a/src/core/exception.d#L539) 
with NO stack trace.


So if you get an IMOE, you will have no idea why. This is a defect in D 
in my opinion, and needs fixing. I've spent hours chasing these types of 
things down.


-Steve


Re: On the D Blog--Symphony of Destruction: Structs, Classes, and the GC

2021-03-05 Thread Max Samukha via Digitalmars-d-announce

On Thursday, 4 March 2021 at 13:54:48 UTC, Mike Parker wrote:


The blog:
https://dlang.org/blog/2021/03/04/symphony-of-destruction-structs-classes-and-the-gc-part-one/


"The destructors of all stack-allocated structs in a given scope 
are invoked when the scope exits."


Please add a note that temporaries are scoped to the full 
expression, not the block.


Re: On the D Blog--Symphony of Destruction: Structs, Classes, and the GC

2021-03-04 Thread Mike Parker via Digitalmars-d-announce

On Thursday, 4 March 2021 at 23:42:58 UTC, Dukc wrote:



I don't understand this part. If an assert was failing, the 
program is going to terminate anyway, so 
InvalidMemoryOperationError is no problem. Well, it might 
obfuscate the underlying error if there is no stack trace, but 
banning `assert`ing in anything that could be called by a 
destructor sounds too drastic to me. Even the lowest level 
system code tends to contain asserts in D, at least in my 
codebase. If asserting is banned, destructors can do faily much 
nothing. I'd think it's much more practical to redefine the 
assert failure handler if InvalidMemoryOperationError due to a 
failed assert is a problem.


Yes, this can be worked around. Passing -checkaction=C to the 
compiler will use the C assert handler and no exception will be 
thrown (which, IMO, should be the default behavior of asserts 
anyway). But even then, I believe as a general rule that any code 
which touches an assert has no place in a finalizer. And that's 
because asserts are inherently deterministic.


A properly-written assert is used to verify an expectation that 
the program is in a specific state at a specific point in its 
execution. If the program is not in that state at that point, 
then we know we've got an error in our code. It's because of this 
determinism that we can remove asserts from released code and 
expect that nothing will break, and it's why we don't assert on 
conditions that are beyond our control (like user input).


Because finalizers are non-deterministic, they kill that "at a 
specific point in the program's execution" part, rendering any 
asserts they touch unreliable. It is realistically possible that 
an assert invoked in a finalizer never triggers during 
development, then the program is released with asserts removed, 
and then it breaks out in the wild because a finalizer is invoked 
at a point when the program isn't in the expected state.


This doesn't make *destructors* useless, but *finalizers* really 
are mostly useless most of the time, IMO. As D programmers, we 
need to consciously be aware of the distinction since the 
language isn't.





Re: On the D Blog--Symphony of Destruction: Structs, Classes, and the GC

2021-03-04 Thread H. S. Teoh via Digitalmars-d-announce
On Thu, Mar 04, 2021 at 11:42:58PM +, Dukc via Digitalmars-d-announce wrote:
> On Thursday, 4 March 2021 at 13:54:48 UTC, Mike Parker wrote:
[...]
> If an assert was failing, the program is going to terminate anyway, so
> InvalidMemoryOperationError is no problem. Well, it might obfuscate
> the underlying error if there is no stack trace, but banning
> `assert`ing in anything that could be called by a destructor sounds
> too drastic to me. Even the lowest level system code tends to contain
> asserts in D, at least in my codebase. If asserting is banned,
> destructors can do faily much nothing. I'd think it's much more
> practical to redefine the assert failure handler if
> InvalidMemoryOperationError due to a failed assert is a problem.

This is precisely why Walter (and others) have said that assert failures
should not throw anything, they should simply terminate (perhaps calling
a user-defined panic function right before aborting, if special handling
is needed).

That, or we take Mike's advice to pretend that class dtors don't exist.


T

-- 
The diminished 7th chord is the most flexible and fear-instilling chord. Use it 
often, use it unsparingly, to subdue your listeners into submission!


Re: On the D Blog--Symphony of Destruction: Structs, Classes, and the GC

2021-03-04 Thread Dukc via Digitalmars-d-announce

On Thursday, 4 March 2021 at 13:54:48 UTC, Mike Parker wrote:


The blog:
https://dlang.org/blog/2021/03/04/symphony-of-destruction-structs-classes-and-the-gc-part-one/


"Some examples: attempting to index an associative array can 
trigger an attempt to allocate a RangeError if the key is not 
present; a failed assert will result in allocation of an 
AssertError; calling any function not annotated with @nogc means 
GC operations are always possible in the call stack. These and 
any such operations should be avoided in the destructors of 
GC-managed objects."


I don't understand this part. If an assert was failing, the 
program is going to terminate anyway, so 
InvalidMemoryOperationError is no problem. Well, it might 
obfuscate the underlying error if there is no stack trace, but 
banning `assert`ing in anything that could be called by a 
destructor sounds too drastic to me. Even the lowest level system 
code tends to contain asserts in D, at least in my codebase. If 
asserting is banned, destructors can do faily much nothing. I'd 
think it's much more practical to redefine the assert failure 
handler if InvalidMemoryOperationError due to a failed assert is 
a problem.


On the D Blog--Symphony of Destruction: Structs, Classes, and the GC

2021-03-04 Thread Mike Parker via Digitalmars-d-announce
This post is 3+ years overdue. I initially put it off for the 
lack of a purpose-built tool in the language or the library to 
distinguish between normal destruction and finalization (when the 
GC is invoked by the destructor). After we got the 
`GC.inFinalizer` thing and having made a few stalled attempts to 
get the thing written, I finally sat down a couple of weeks ago 
and forced myself to finish it.


The result is not what I had originally intended, as the topic 
turned out to be much more involved than I had realized. What was 
supposed to be one post very quickly became two, and now it looks 
like there will be at least four before I'm done. (I didn't even 
get to the GC.inFinalizer thing in this first post.) Object 
destruction in D has dark corners that I had never knew existed 
until recently, and I expect that as I experiment with them and 
talk with some battle-hardened warriors like Adam, I'll find 
myself with many more words to write on the topic.


The blog:
https://dlang.org/blog/2021/03/04/symphony-of-destruction-structs-classes-and-the-gc-part-one/

Reddit:
https://www.reddit.com/r/programming/comments/lxkcxp/symphony_of_destruction_structs_classes_and_the/

The GC series to date:
https://dlang.org/blog/the-gc-series/