November 12, 2014
On Monday, 10 November 2014 at 14:19:26 UTC, Steven Schveighoffer wrote:

> Is the resource a GC resource? If so, don't worry about it.

I might be wrong but my view is that in presence of a GC and undre the abstraction of R Chen, it is wrong to think about memory as being a resource anymore.

If the abstraction is that of a machine with infinite memory, then the very mechanism of freeing memory shall be abstracted for.

This asks for decoupling the memory management of the management of the other resources and, in particular, RAII-like management.

The management of memory and any other resources is pretty much identical under C++ since they share the same paradigm. In GC languages, this is no longer the case.

So, the real question (that will also cover destructors throwing exceptions and so on) is not what is allowed inside a destructor (aka finalizer) but *what is allowed in a constructor* of a GC-managed object.

Since for symmetry purposes the destructor should undo what the constructor did, this cuts the problem as follows:

 1) in GC-entities, the destructor should not deal with releasing memory. Not only this job is no longer his, but *there is no need for this kind of job*. Memory is infinite.

 2) from symmetry, the constructor should not allocate memory as it would care about its lifetime.

 3) the question in that case is how the other resources are managed?

 3a) If they are to be released in the destructor, then the destructor's job should be only this (no memory dealloc). In this case, the resources should be taken in the constructor.

 3b) if the destructor disappears completely, then the place to acquire resources is no longer in the constructor anymore, since those will never be released.

 Ideally, a transparent mechanism to allocate memory would be needed, without explicit allocation. In this case, the symmetry would be conserved: the constructor will only acqire resources (but not memory!) and those resources are released, symmetrically, in the destructor, at the end of the lifetime.
November 12, 2014
On 12/11/14 11:29, "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm@gmx.net>" wrote:

> Supposedly, a struct destructor will only access resources that the
> struct itself manages. As long as that's the case, it will be safe. In
> practice, there's still a lot that can go wrong.

Either a struct's destructor can be run from the context of a GC, in which case it should run when the struct is directly allocated on the heap, or it is not, in which case the fact it is run when the struct is inside a class should be considered a bug.


Today it happens for structs nested in classes, but not allocated directly. I don't see any situation in which this is not a bug.

Shachar
November 12, 2014
On Wednesday, 12 November 2014 at 13:56:08 UTC, Shachar Shemesh wrote:
> On 12/11/14 11:29, "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm@gmx.net>" wrote:
>
>> Supposedly, a struct destructor will only access resources that the
>> struct itself manages. As long as that's the case, it will be safe. In
>> practice, there's still a lot that can go wrong.
>
> Either a struct's destructor can be run from the context of a GC, in which case it should run when the struct is directly allocated on the heap, or it is not, in which case the fact it is run when the struct is inside a class should be considered a bug.
>
>
> Today it happens for structs nested in classes, but not allocated directly. I don't see any situation in which this is not a bug.
>
> Shachar

I think it's helpful to ask the question who's responsible for destroying an object. If it's the GC, then it's finalization, if it's no the GC, it's destruction. Both a destructor and a finalizer need to clean up themselves, and any other object they own. This includes embedded structs, but not GC managed objects created by the constructor.

This applies to both structs and classes as the owning objects, and manual/automatic management as well as GC.

But indeed, what's implemented today is inconsistent.
November 12, 2014
On 11/12/14 12:05 AM, Shachar Shemesh wrote:
> On 11/11/14 22:41, Steven Schveighoffer wrote:
>>
>> At this point, I am not super-concerned about this. I cannot think of
>> any bullet-proof way to ensure that struct dtors for structs that were
>> meant only for stack variables can be called correctly from the GC.
> Isn't "structs meant only for stack variables" a semantic thing? The D
> compiler cannot possibly know. Shouldn't that be the programmer's choice?
>
>> This
>> pull doesn't change that, and it does have some nice new features that
>> we do need for other reasons.
>>
>> In other words, putting a struct in the GC heap that was written to be
>> scope-destroyed is an error before and after this pull. Before the pull,
>> the dtor doesn't run, which is wrong, and after the pull the dtor may
>> cause race issues, which is wrong. So either way, it's wrong :)
> I disagree.
>
> The first is wrong. The second is a corner case the programmer needs to
> be aware of, and account for.

The programmer being the user of the struct or the designer? It's impossible to force the user to avoid using a struct on the GC, it would be enforcement by comment.

But even then, in your dtor, there are issues with accessing what a dtor would normally access. Tell me how you implement reference counting smart pointer when you can't access the reference count in the dtor...

> The difference is that, in the first case,
> the programmer is left with no tools to fix the problem, while in the
> second case this is simply a bug in the program (which, like I said in
> another email, also happens with the current implementation when the
> struct is inside a class).

Sure there are tools, you can wrap the struct in a class if you like pain and suffering. Having the struct dtor called without the wrapper is the same issue.

> In other words, the second case exposes a second (more direct and more
> likely to be handled) path to an already existing problem, while the
> first puts the programmer up against a new problem with no work around.

This means all struct dtors need to be thread aware, and this is not what you want in a language where the type information dictates whether it can be shared or not.

-Steve
November 12, 2014
On 11/11/14 11:59 PM, Shachar Shemesh wrote:
> On 10/11/14 16:19, Steven Schveighoffer wrote:
>>
>> Only classes call dtors from the GC. Structs do not. There are many
>> hairy issues with structs calling dtors from GC. Most struct dtors
>> expect to be called synchronously, and are not expecting to deal with
>> multithreading issues.
>>
>> Note that structs inside classes WILL call dtors.
>
> How is this any different? If one should not be allowed, how is the
> other okay?

I'm not defending the status quo, I'm just saying what happens today.

But adding struct dtor calls to the GC will not solve the problems identified here.

-Steve
November 12, 2014
If we will have something like *scoped destructor* (that will be executed at scope exit) could it help to release some resources? I worry about this problem too because even using class to hold resource I expirience some *delays* in relesing them. For example I have database connection opened. And I want to close it when I finished my job. Relying on GC I sometimes experiece problems like *too many DB connections*, because GC frees it not enough quickly.
November 12, 2014
On Wednesday, 12 November 2014 at 20:00:31 UTC, Uranuz wrote:
> If we will have something like *scoped destructor* (that will be executed at scope exit) could it help to release some resources?

Don't know what you mean here. Isn't that just a normal destructor?

> I worry about this problem too because even using class to hold resource I expirience some *delays* in relesing them. For example I have database connection opened. And I want to close it when I finished my job. Relying on GC I sometimes experiece problems like *too many DB connections*, because GC frees it not enough quickly.

I'd say database connections and file descriptors simply shouldn't be managed by the GC. It is good at managing memory, but not for other things. It's better to have a connection pool, and from that pool take a reference to the DB connection, use it as long as you need it, and then give it back. This can be implemented nicely with scope(exit).
November 13, 2014
On 11/12/14 3:00 PM, Uranuz wrote:
> If we will have something like *scoped destructor* (that will be
> executed at scope exit) could it help to release some resources? I worry
> about this problem too because even using class to hold resource I
> expirience some *delays* in relesing them. For example I have database
> connection opened. And I want to close it when I finished my job.
> Relying on GC I sometimes experiece problems like *too many DB
> connections*, because GC frees it not enough quickly.

GC I would use as a last resort.

Let's say you leak the object, forget to dispose it. Do you want it to leak the DB resource too?

Basically, you want a close() or dispose() method on your object, then you can let the GC clean the memory, and synchronously close the DB connection.

-Steve
November 14, 2014

On 11.11.2014 21:41, Steven Schveighoffer wrote:
> In other words, putting a struct in the GC heap that was written to be
> scope-destroyed is an error before and after this pull. Before the pull,
> the dtor doesn't run, which is wrong, and after the pull the dtor may
> cause race issues, which is wrong. So either way, it's wrong :)

I think if someone uses "new" to allocate a struct on the GC heap, he must be aware of the consequences of using the GC. What happens within destruction/finalization is just the same as if it had been wrapped in a class, and that's what everyone(?) expects.
November 14, 2014
On 11/14/14 6:25 AM, Rainer Schuetze wrote:
>
>
> On 11.11.2014 21:41, Steven Schveighoffer wrote:
>> In other words, putting a struct in the GC heap that was written to be
>> scope-destroyed is an error before and after this pull. Before the pull,
>> the dtor doesn't run, which is wrong, and after the pull the dtor may
>> cause race issues, which is wrong. So either way, it's wrong :)
>
> I think if someone uses "new" to allocate a struct on the GC heap, he
> must be aware of the consequences of using the GC. What happens within
> destruction/finalization is just the same as if it had been wrapped in a
> class, and that's what everyone(?) expects.

The issue I have is, how do you enforce that as the author of the struct?

Basically, I think you have 2 situations with struct dtors:
1. the struct author intends a struct to strictly be used on the stack (or on some other heap, such as C heap), and never expects to be GC destructed.
2. the struct author allows it to be GC stored, and faces the consequences of that.

Even with these 2 options, allowing GC storage precludes certain things. For example, the RefCounted struct goes through great pains to ensure its payload is NOT on the GC heap, because the dtor will not work if the payload is GC based (it may be already deallocated). The issues are so numerous and hairy, that option 2 should really only be left to "experts."

I think we need a better story for GC-stored structs than just letting people do it.

All this being said, having the GC perform what it is supposed to is also good. Bottom line -- I think the pull is the right thing to do (clearly, not calling dtors in some cases is not correct), but the problems are not all solved. We need to continue working on this until it's quite simple to make a struct that behaves correctly no matter where you put it.

-Steve