Thread overview
synchronized/shared associative array .require error
Sep 02, 2022
cc
Sep 03, 2022
Loara
Sep 03, 2022
frame
Sep 06, 2022
Loara
Sep 06, 2022
frame
Sep 05, 2022
cc
September 02, 2022
synchronized class SyncTable(KEY, VAL) {
	private VAL[KEY] table;
	auto require(KEY key) {
		return table.require(key);
	}
}

auto table = new shared SyncTable!(string, string);
table.require("abc");

Fails to compile:

// Error: none of the overloads of template `object.require` are callable using argument types `!()(shared(string[string]), string)`

Tried casting away shared as a workaround but I assume that will cause some kind of TLS catastrophe.

September 03, 2022

On Friday, 2 September 2022 at 19:15:45 UTC, cc wrote:

>
synchronized class SyncTable(KEY, VAL) {
	private VAL[KEY] table;
	auto require(KEY key) {
		return table.require(key);
	}
}

auto table = new shared SyncTable!(string, string);
table.require("abc");

Fails to compile:

// Error: none of the overloads of template `object.require` are callable using argument types `!()(shared(string[string]), string)`

Tried casting away shared as a workaround but I assume that will cause some kind of TLS catastrophe.

In current version of D language synchronized and shared are independent. In particular shared should be used only for basic types like integers for which atomic operations are well defined, and not for classes.

Anyway if you must send a reference of a synchronized class to a different thread then it's safe to cast shared and then remove it later:

synchronized class A{
...
}

void sendTo(Tid to, A a){
  to.send(cast(shared A) a);
}

A receiveA(){
  A a;
  receive( (shared A sa) { a = cast(A) sa; });
  return a;
}

Unfortunately there isn't any traits that tells you if a class is synchronized or not, so you can't do a safe template function for this.

September 03, 2022

On Saturday, 3 September 2022 at 09:49:54 UTC, Loara wrote:

>

In current version of D language synchronized and shared are independent. In particular shared should be used only for basic types like integers for which atomic operations are well defined, and not for classes.

Not exactly, a synchronized class member function becomes automatically a shared one.

September 03, 2022

On 9/2/22 3:15 PM, cc wrote:

>

Tried casting away shared as a workaround but I assume that will cause some kind of TLS catastrophe.

I think it will be fine, but you may have an issue. You are returning a non-shared VAL, but your class is shared, which means table, and all the VAL and KEY inside must also be shared.

If you cast away shared you have to put it back upon return.

TLS should not be involved here at all, so there is no problem there.

-Steve

September 05, 2022

On Saturday, 3 September 2022 at 14:37:16 UTC, Steven Schveighoffer wrote:

>

On 9/2/22 3:15 PM, cc wrote:

>

Tried casting away shared as a workaround but I assume that will cause some kind of TLS catastrophe.

I think it will be fine, but you may have an issue. You are returning a non-shared VAL, but your class is shared, which means table, and all the VAL and KEY inside must also be shared.

If you cast away shared you have to put it back upon return.

TLS should not be involved here at all, so there is no problem there.

-Steve

Alright, so this is safe then?

alias VAL[KEY] T;
auto require(KEY key) {
	auto unsharedT = cast(T) table;
	auto r = unsharedT.require(key);
	table = cast(shared) unsharedT;
	return cast(shared) r;
}

Was a bit surprised to see mutating unsharedT left table unchanged and needed reassigning.

September 05, 2022

On 9/4/22 11:24 PM, cc wrote:

>

On Saturday, 3 September 2022 at 14:37:16 UTC, Steven Schveighoffer wrote:

>

On 9/2/22 3:15 PM, cc wrote:

>

Tried casting away shared as a workaround but I assume that will cause some kind of TLS catastrophe.

I think it will be fine, but you may have an issue. You are returning a non-shared VAL, but your class is shared, which means table, and all the VAL and KEY inside must also be shared.

If you cast away shared you have to put it back upon return.

TLS should not be involved here at all, so there is no problem there.

Alright, so this is safe then?

alias VAL[KEY] T;
auto require(KEY key) {
     auto unsharedT = cast(T) table;
     auto r = unsharedT.require(key);
     table = cast(shared) unsharedT;
     return cast(shared) r;
}

I think that is fine-ish. You still don't have a shared KEY there. But it really depends on what KEY is. Most likely it's fine (e.g. if KEY is string). If you don't ever really fetch anything out of the key, and just use it to map to your values, I think it should be fine.

>

Was a bit surprised to see mutating unsharedT left table unchanged and needed reassigning.

Yes, because before an AA contains an element, it is a null AA. When you add the first element, it's allocated. When you make a copy of a null AA, it doesn't affect the original.

You can fix this by reinterpret casting the AA instead of copying it:

auto r = .require(*(cast(T*)&table), key);
// I think this might also work:
auto r = (cast()table).require(key);

-Steve

September 06, 2022

On Saturday, 3 September 2022 at 14:07:58 UTC, frame wrote:

>

Not exactly, a synchronized class member function becomes automatically a shared one.

This is not present in official documentation so other compilers different from dmd aren't forced to assume it. This should be consider an experimental feature of D that users should be able to turn off if they want.

September 06, 2022

On Tuesday, 6 September 2022 at 10:28:53 UTC, Loara wrote:

>

On Saturday, 3 September 2022 at 14:07:58 UTC, frame wrote:

>

Not exactly, a synchronized class member function becomes automatically a shared one.

This is not present in official documentation so other compilers different from dmd aren't forced to assume it. This should be consider an experimental feature of D that users should be able to turn off if they want.

Hmm.. LDC does the same to me.

I think the whole synchronization class is an experimental feature :D