Thread overview
aa.keys, synchronized and shared
Nov 10, 2022
torhu
Nov 11, 2022
torhu
Nov 11, 2022
cc
Nov 11, 2022
Kagamin
Nov 11, 2022
torhu
Nov 14, 2022
Kagamin
Nov 14, 2022
torhu
Nov 11, 2022
Kagamin
November 10, 2022

I'm trying to make a more thread-safe wrapper for AA's:

synchronized final class SyncAA(K, V) ///
{
    ///
    V opIndex(K key) { return data_[key]; }

    ///
    V opIndexAssign(V value, K key) { return data_[key] = value; }

    ///
    K[] keys() const { return data_.keys; }

    ///
    void remove(K key) { data_.remove(key); }

    /// There is no `in` operator, it would not be thread-safe.
    V get(K key, lazy V defaultValue=V.init)
    {
        auto p = key in data_;
        return p ? *p : defaultValue;
    }

    ///
    int opApply(scope int delegate(inout ref V) dg) const
    {
        int result = 0;
        foreach (value; data_) {
            result = dg(value);
            if (result)
                break;
        }
        return result;
    }

    ///
    int opApply(scope int delegate(K, inout ref V) dg) const
    {
        int result = 0;
        foreach (key, value; data_) {
            result = dg(key, value);
            if (result)
                break;
        }
        return result;
    }

private:
    V[K] data_;
}

In another file:
__gshared serverListCache = new SyncAA!(string, ServerList);

I'm using keys in a regular function, this is the error i get:

C:\prog\dmd\windows\bin....\src\druntime\import\object.d(3245,36): Error: cannot implicitly convert expression aa of type shared(const(ServerList[string])) to const(shared(ServerList)[string])
src\syncaa.d(17,36): Error: template instance object.keys!(shared(const(ServerList[string])), shared(const(ServerList)), string) error instantiating
src\serveractions.d(45,33): instantiated from here: SyncAA!(string, ServerList)
src\serveractions.d(50,17): Error: shared const method syncaa.SyncAA!(string, ServerList).SyncAA.keys is not callable using a non-shared mutable object
Error C:\prog\dmd\windows\bin\dmd.exe failed with exit code 1.

November 11, 2022

On Thursday, 10 November 2022 at 21:55:26 UTC, torhu wrote:

>

I'm trying to make a more thread-safe wrapper for AA's:

synchronized final class SyncAA(K, V) ///

I chose to fix this by just using synchronized (this) inside each method instead, for now. Still interested in cleaner solutions, but I guess synchronized/shared is a bit of a rabbit hole...

November 11, 2022

On Friday, 11 November 2022 at 01:09:54 UTC, torhu wrote:

>

On Thursday, 10 November 2022 at 21:55:26 UTC, torhu wrote:

>

I'm trying to make a more thread-safe wrapper for AA's:

synchronized final class SyncAA(K, V) ///

I chose to fix this by just using synchronized (this) inside each method instead, for now. Still interested in cleaner solutions, but I guess synchronized/shared is a bit of a rabbit hole...

That's about what I ended up with, and just declaring my references __gshared. I don't know what's going on with shared/synchronized but it sounds like it's unfinished and people can't agree on what they're actually supposed to mean. __gshared, it just works.
Previous thread: synchronized/shared associative array .require error
Also: synchronized - shared but actually useful

class SyncTable(KEY, VAL) {
	private VAL[KEY] table;
	auto opIndexAssign(VAL value, KEY key) {
		synchronized(this) {
			return table[key] = value;
		}
	}
	int opApply(int delegate(ref KEY, ref VAL) dg) {
		synchronized(this) {
			foreach (key, val; table) {
				if (dg(key, val)) return 1;
			}
			return 0;
		}
	}
	auto opBinaryRight(string op)(KEY key) if (op == "in") {
		synchronized(this) {
			return key in table;
		}
	}
	auto opDispatch(string s, SA...)(SA sargs) {
		synchronized(this) {
			static if (SA.length == 0) {
				mixin(format("return table.%s;", s));
			} else {
				mixin(format("return table.%s(%s);", s, sargs.stringof[6 .. $-1])); // tuple(_param_0)
			}
		}
	}
}

With synchronized on the class, I had to do something like:

synchronized class SyncTable(KEY, VAL) {
	// Anything that mutates must reassign unshared back to table!
	auto opIndexAssign(VAL value, KEY key) {
		auto unshared = cast(T) table;
		unshared[key] = value;
		table = cast(shared) unshared;
		return value;
	}
	auto require(KEY key) {
		auto unshared = cast(T) table;
		auto r = unshared.require(key);
		table = cast(shared) unshared;
		return r;
	}
	/* ... */
}

and it just doesn't feel right.

For mutexes without synchronized, there's also:

struct Lock {
	private shared Mutex _mtx;
	this(shared Mutex mtx) {
		_mtx = mtx;
		_mtx.lock();
	}
	this(this) @disable;
	~this() {
		if (_mtx)
			_mtx.unlock();
		_mtx = null;
	}
}
class SyncTable(KEY, VAL) {
	private VAL[KEY] table;
	shared Mutex mtx;
	this() {
		mtx = new shared Mutex;
	}
	auto opIndexAssign(VAL value, KEY key) {
		auto lock = Lock(mtx);
		return table[key] = value;
	}
	/* ... */
}
November 11, 2022

Try this:

synchronized final class SyncAA(K, V)
{
	V opIndex(K key) { return sharedTable[key]; }
	V opIndexAssign(V value, K key) { return sharedTable[key]=value; }
	const(K[]) keys() const { return unsharedTable.keys; }
	void remove(K key) { sharedTable.remove(key); }
	V get(K key, lazy V defaultValue=V.init)
	{
		auto p = key in sharedTable;
		return p ? *p : defaultValue;
	}
private:
	V[K] sharedTable;
	ref inout(V[K]) unsharedTable() inout
	{
		return *cast(inout(V[K])*)&sharedTable;
	}
}
void f(shared SyncAA!(string,string) a)
{
	a.keys();
	a["12"]="34";
	a.remove("12");
}
November 11, 2022

With allocation:

synchronized final class SyncAA(K, V)
{
	V opIndex(K key) { return sharedTable[key]; }
	V opIndexAssign(V value, K key) { return sharedTable[key]=value; }
	const(K[]) keys() const { return unsharedTable.keys; }
	void remove(K key) { sharedTable.remove(key); }
	V get(K key, lazy V defaultValue=V.init)
	{
		auto p = key in sharedTable;
		return p ? *p : defaultValue;
	}
private:
	V[K] sharedTable;
	ref inout(V[K]) unsharedTable() inout
	{
		return *cast(inout(V[K])*)&sharedTable;
	}
}
shared SyncAA!(string,string) saa;
void f()
{
	saa=new shared SyncAA!(string,string);
	saa.keys();
	saa["12"]="34";
	saa.remove("12");
}
November 11, 2022

On Friday, 11 November 2022 at 14:19:31 UTC, Kagamin wrote:

>

Try this:

>

private:
V[K] sharedTable;
ref inout(V[K]) unsharedTable() inout
{
return cast(inout(V[K]))&sharedTable;
}

Thanks, that worked! Feels like programming in C, though. If I could figure out how to initialize the AA explicitly, I could also remove the ref here. If I just remove the ref, the AA is always null. If I try to initialize it in the constructor, I get this:

src\syncaa.d(11,5): Error: _d_monitorenter cannot be interpreted at compile time, because it has no available source code

No idea why, it seems to happen if I try to use the AA in the constructor at all. Even when I just do data_.remove(K.init);

I also tried DMD 2.101.0-rc.1, using the new new V[K] syntax, same error there.

November 14, 2022

This works for me:

synchronized final class SyncAA(K, V)
{
	this(K key, V val) { sharedTable[key]=val; }
	V opIndex(K key) { return sharedTable[key]; }
	V opIndexAssign(V value, K key) { return sharedTable[key]=value; }
	const(K[]) keys() const { return unsharedTable.keys; }
	void remove(K key) { sharedTable.remove(key); }
	V get(K key, lazy V defaultValue=V.init)
	{
		auto p = key in sharedTable;
		return p ? *p : defaultValue;
	}
private:
	V[K] sharedTable;
	inout(V[K]) unsharedTable() inout
	{
		return cast(inout(V[K]))sharedTable;
	}
}
shared SyncAA!(string,string) saa;
void f()
{
	saa=new shared SyncAA!(string,string)("1","2");
	saa.keys();
	saa["12"]="34";
	saa.remove("12");
}
November 14, 2022

On Monday, 14 November 2022 at 07:57:16 UTC, Kagamin wrote:

>

This works for me:

shared SyncAA!(string,string) saa;
void f()
{
	saa=new shared SyncAA!(string,string)("1","2");
	saa.keys();
	saa["12"]="34";
	saa.remove("12");
}

The strange error message I got was because I initialized the variable at module level, that doesn't work when you have a constructor. It worked when I moved it into a module constructor.