July 19, 2017
Am Tue, 18 Jul 2017 18:10:58 +0000
schrieb Atila Neves <atila.neves@gmail.com>:
> Now I've read your post properly: there is only one destructor. With the fix I mentioned, just don't defined the `shared` version, there's no need to. Postblit is still a problem, however.
> 
> Atila

The issue is wider than just `shared` by the way:
https://issues.dlang.org/show_bug.cgi?id=13628
Some may jump to say that an immutable struct can't be
destructed, but my perspective here is that immutable only
applies to what the compiler can introspect. A file descriptor
or an opaque struct pointer from a C API are just flat values
and escape the compiler. They can be stored in an immutable
struct and still need `close()` called on them.
Layman's head-const :p

-- 
Marco
July 19, 2017
On Tuesday, 18 July 2017 at 19:24:18 UTC, Jonathan M Davis wrote:
> For full-on value types, it should be a non-issue though.

Not quite. Value types include resource identifiers, which may have threading requirements, e.g. GUI widget handles and OpenGL handles, assuming they are thread-safe and making them implicitly shared would be incorrect.
July 19, 2017
Am Wed, 19 Jul 2017 08:50:11 +0000
schrieb Kagamin <spam@here.lot>:

> On Tuesday, 18 July 2017 at 19:24:18 UTC, Jonathan M Davis wrote:
> > For full-on value types, it should be a non-issue though.
> 
> Not quite. Value types include resource identifiers, which may have threading requirements, e.g. GUI widget handles and OpenGL handles, assuming they are thread-safe and making them implicitly shared would be incorrect.

That's exactly what I was opposing in the other post. These
handles are opaque and never change their value. Within the
Dlang language barrier they can be immutable and as such,
implicitly shared.
Your thinking is less technical, trying to find a best fit
between type system and foreign API, so that only handles with
a thread-safe API may become `shared`. I like the idea, but it
is impractical. It sometimes depends on whether a library was
compiled with multi-threading support or not and a value type
can be copied from and to shared anyways, rendering the safety
argument void:

	int x;
	shared int y = x;
	int z = y;

-- 
Marco

July 19, 2017
On Tuesday, 18 July 2017 at 19:24:18 UTC, Jonathan M Davis wrote:
> On Tuesday, July 18, 2017 18:06:56 Atila Neves via Digitalmars-d wrote:
>> On Tuesday, 18 July 2017 at 15:03:07 UTC, Kagamin wrote:
>> > On Tuesday, 18 July 2017 at 11:47:37 UTC, Petar Kirov
>> >
>> > [ZombineDev] wrote:
>> >> I think Atila was talking about this one:
>> >> struct A
>> >> {
>> >>
>> >>    ~this() {}
>> >>
>> >> }
>> >>
>> >> void main()
>> >> {
>> >>
>> >>    auto a = A();
>> >>    shared b = A();
>> >>
>> >> }
>> >
>> > This is strange. There's nothing that suggests that struct A and its destructor is thread-safe, yet compiler assumes it is.
>>
>> Except for a programmer explicitly and manually calling the destructor (in which case, don't), the destructor is only ever called by one thread.
>
> It could still be a problem if the struct has a member variable that is a reference type, because then something else could refer to that object, and if it's shared, then you would need to protect it, and the operations that shared prevents should still be prevented. For full-on value types, it should be a non-issue though.
>
> - Jonathan M Davis

Mmm, I guess so. As Marco pointed out, it's a similar problem with immutable (because the compiler casts it away before calling the destructor).

Although I dare say that anybody writing code that depends on such locking and destruction when shared is unlikely to get it right in the first place.

Atila
July 19, 2017
On Tuesday, 18 July 2017 at 11:47:37 UTC, Petar Kirov [ZombineDev] wrote:
> I think Atila was talking about this one:
> struct A
> {
> 	~this() {}
> }
>
> void main()
> {
> 	auto a = A();
> 	shared b = A();
> }
>

Shouldn't it be :
struct A
{
    ~this() shared {}
}

void main()
{
    auto a = A();
    shared b = A();
}
?

Because handling theard-local data as shared is safe as far as I remember, but not the other way round.

And if you want a destructor which works with both immutable and normal, shouldn't it be a const destructor?
July 19, 2017
On Wednesday, 19 July 2017 at 14:56:58 UTC, Dukc wrote:
> Shouldn't it be :
> struct A
> {
>     ~this() shared {}
> }
>
> void main()
> {
>     auto a = A();
>     shared b = A();
> }
> ?
>
> Because handling theard-local data as shared is safe as far as I remember, but not the other way round.

Non-shared structs/classes can't call shared methods.

Unless you're saying that the above should work even though it currently doesn't. Even then, I don't know about that. If your type is complex enough to need a shared dtor then the dtor probably needs to do some locking or extra checks. You don't want to impose that cost on a struct instance which isn't shared.

July 19, 2017
On Wednesday, 19 July 2017 at 15:38:08 UTC, Jack Stouffer wrote:
> Unless you're saying that the above should work even though it currently doesn't. Even then, I don't know about that. If your type is complex enough to need a shared dtor then the dtor probably needs to do some locking or extra checks. You don't want to impose that cost on a struct instance which isn't shared.

Yes, just what I meant. And perfectly explained why we need something better.
July 19, 2017
On Wednesday, July 19, 2017 2:29:04 PM MDT Atila Neves via Digitalmars-d wrote:
> On Tuesday, 18 July 2017 at 19:24:18 UTC, Jonathan M Davis wrote:
> > On Tuesday, July 18, 2017 18:06:56 Atila Neves via
> >> Except for a programmer explicitly and manually calling the destructor (in which case, don't), the destructor is only ever called by one thread.
> >
> > It could still be a problem if the struct has a member variable that is a reference type, because then something else could refer to that object, and if it's shared, then you would need to protect it, and the operations that shared prevents should still be prevented. For full-on value types, it should be a non-issue though.
> >
> > - Jonathan M Davis
>
> Mmm, I guess so. As Marco pointed out, it's a similar problem with immutable (because the compiler casts it away before calling the destructor).
>
> Although I dare say that anybody writing code that depends on such locking and destruction when shared is unlikely to get it right in the first place.

Well, consider that something like a reference counted type would have to get this right. Now, a reference counted type that worled with shared would need to be written that way - simply slapping shared on such a smart pointer type isn't going to work - but it would then be really bad for the destructor to be treated as thread-local.

It really looks to me like having a thread-local dstructor for shared data is just begging for problems. It may work in the simple cases, but it'll fall apart in the more complicated ones, and I don't see how that's acceptable.

Only really basic types are going to work as shared without being specifically designed for it, so I'm inclined to think that it would be far better for the language to be changed so that it supports having both a shared and non-shared destructor rather than having shared objects work with non-shared destructors.

- Jonathan M Davis

July 19, 2017
On Wednesday, 19 July 2017 at 20:23:18 UTC, Jonathan M Davis wrote:
> On Wednesday, July 19, 2017 2:29:04 PM MDT Atila Neves via Digitalmars-d wrote:
>> On Tuesday, 18 July 2017 at 19:24:18 UTC, Jonathan M Davis wrote:
>> > On Tuesday, July 18, 2017 18:06:56 Atila Neves via
>> >> Except for a programmer explicitly and manually calling the destructor (in which case, don't), the destructor is only ever called by one thread.
>> >
>> > It could still be a problem if the struct has a member variable that is a reference type, because then something else could refer to that object, and if it's shared, then you would need to protect it, and the operations that shared prevents should still be prevented. For full-on value types, it should be a non-issue though.
>> >
>> > - Jonathan M Davis
>>
>> Mmm, I guess so. As Marco pointed out, it's a similar problem with immutable (because the compiler casts it away before calling the destructor).
>>
>> Although I dare say that anybody writing code that depends on such locking and destruction when shared is unlikely to get it right in the first place.
>
> Well, consider that something like a reference counted type would have to get this right. Now, a reference counted type that worled with shared would need to be written that way - simply slapping shared on such a smart pointer type isn't going to work - but it would then be really bad for the destructor to be treated as thread-local.

Not necessarily - the reference counted smart pointer doesn't have to be `shared` itself to have a `shared` payload. I'm not even entirely sure what the advantage of it being `shared` would be, or even what that would really mean.

The way `automem.RefCounted` works right now is by doing atomic reference count manipulations by using reflection to know if the payload is `shared`.

> It really looks to me like having a thread-local dstructor for shared data is just begging for problems. It may work in the simple cases, but it'll fall apart in the more complicated ones, and I don't see how that's acceptable.

You've definitely made me wonder about complicated cases, but I'd argue that they'd be rare. Destructors are (bar manually calling them) run in one thread. I'm having trouble imagining a situation where two threads have references to a `shared` object/value that is going to be destroyed deterministically.

>
> Only really basic types are going to work as shared without being specifically designed for it, so I'm inclined to think that it would be far better for the language to be changed so that it supports having both a shared and non-shared destructor rather than having shared objects work with non-shared destructors.

Perhaps.

Atila



July 19, 2017
On Wednesday, 19 July 2017 at 20:59:03 UTC, Atila Neves wrote:
> On Wednesday, 19 July 2017 at 20:23:18 UTC, Jonathan M Davis wrote:
>> On Wednesday, July 19, 2017 2:29:04 PM MDT Atila Neves via Digitalmars-d wrote:
>>> On Tuesday, 18 July 2017 at 19:24:18 UTC, Jonathan M Davis wrote:
>>> > On Tuesday, July 18, 2017 18:06:56 Atila Neves via
>>> >> Except for a programmer explicitly and manually calling the destructor (in which case, don't), the destructor is only ever called by one thread.
>>> >
>>> > It could still be a problem if the struct has a member variable that is a reference type, because then something else could refer to that object, and if it's shared, then you would need to protect it, and the operations that shared prevents should still be prevented. For full-on value types, it should be a non-issue though.
>>> >
>>> > - Jonathan M Davis
>>>
>>> Mmm, I guess so. As Marco pointed out, it's a similar problem with immutable (because the compiler casts it away before calling the destructor).
>>>
>>> Although I dare say that anybody writing code that depends on such locking and destruction when shared is unlikely to get it right in the first place.
>>
>> Well, consider that something like a reference counted type would have to get this right. Now, a reference counted type that worled with shared would need to be written that way - simply slapping shared on such a smart pointer type isn't going to work - but it would then be really bad for the destructor to be treated as thread-local.
>
> Not necessarily - the reference counted smart pointer doesn't have to be `shared` itself to have a `shared` payload.

Agreed. I'm exploring doing the same for std.stdio.File.

> I'm not even entirely sure what the advantage of it being `shared` would be, or even what that would really mean.
>

There are plenty of cases where you need the smart pointer to be shared itself -
e.g. member composition and global variables. See also: [0]

Note that this doesn't play well with regular [1] value types becuase e.g. you don't have control over the synthesized bit-blit for this(this) and so you can't assume that structs with a single pointer member are updated atomically, even if would write the opAssign that way. In C++17 atomic_shared_ptr has it's copy-constructor and assign operator deleted. You can only do atomic<T> like ops with it and derive a plain shared_ptr<T> from it, kind-of like core.atomic's HeadUnshared(T).

[0]: https://www.justsoftwaresolutions.co.uk/threading/why-do-we-need-atomic_shared_ptr.html
[1]: http://stepanovpapers.com/DeSt98.pdf (definition of regular types).