On Thursday, 27 July 2023 at 21:31:02 UTC, Jonathan M Davis wrote:
What should normally be happening is that you use shared, and then when you've protected the object so that you know that it can only be accessed on the current thread by the section of code that you're in (e.g. by locking a mutex), you temporarily cast away shared to operate on the object via a thread-local reference. Then, before exiting that section of code and removing the protections that are preventing other threads from accessing the object (e.g. by unlocking the mutex), you make sure that you've gotten rid of all of the thread-local references to the object so that only the shared reference exists. That way, you don't accidentally mutate the object while it's not protected from access by other threads.

Doing this doesn't help, unfortunately.
This is an abstract version what the problematic code looks like now:
```d
import std.stdio: writeln;
import core.sync.mutex;
import core.thread;
import core.time;
static import core.stdc.stdlib;

struct Pos{
        long x,y;
}

shared Obj obj;

final class Obj{
        CacheData[Pos] cache;
        CacheData* getCache(Pos key){
                if(auto item = key in cache){
                        if(++item.useCount >= 8){
                                cache.remove(key);
//I thought this ^ might cause a use-after-free issue, but apparently not.
                        }
                        return item;
                }
                return null;
        }
        struct CacheData{
                double[1000] data;
                uint useCount = 1;
        }
        
        double[1000] doStuff(Pos pos){
                immutable data = (){
                        if(auto data = getCache(pos)){
                                return *data;
                        }else{
                                double[1000] data;
                                //initialisses it with something, this is 
arbirray though:
                                foreach(ref item; data){
                                        import std.random;
                                        item = uniform01();
                                }
                                cache[pos] = CacheData(data);
                                return CacheData(data);
                        }
                }();
                
                //do stuff with data
                
                return data.data;
        }       
}

__gshared OtherObj otherObj;

final class OtherObj{
        shared Mutex mutex;
        __gshared ubyte[2^^18] data;
        
        this(long n){
                obj = cast(shared Obj)alloc!Obj(n);
                mutex = new shared Mutex;
        }
        
        void callObj(Pos pos){
                double[1000] data;
                
                {
                        auto objRef = cast(Obj)obj;
                        data = objRef.doStuff(pos);
                }
                
                //do things with returned value...
        }
}

void thread(){
        bool run = true;
        Pos pos;
        while(run){
                otherObj.mutex.lock();
                foreach(i; 0..100){
                        otherObj.callObj(pos);
                        //this is pretty arbirary:
                        import std.random;
                        if(uniform01() > 0.9){
                                auto v = uniform01();
                                if(v < 0.25) pos.x--;
                                else if(v < 0.5) pos.y++;
                                else if(v < 0.75) pos.y--;
                                else pos.x++;
                        }
                }
                otherObj.mutex.unlock();
                
                Thread.sleep(1.seconds / 20);
        }
}

void main(){
        otherObj = alloc!OtherObj(-7);
        assert(otherObj !is null);
        
        auto otherThread = new Thread(&thread);
        otherThread.start();
        
        bool run = true;
        while(run){
                if(!otherThread.isRunning()) otherThread.join();
                otherObj.mutex.lock();
                foreach(val; otherObj.data){
                        //do stuff
                }
                otherObj.mutex.unlock();
                Thread.sleep(1.seconds / 80);
        }
}

T alloc(T, A...)(auto ref A args){
        enum classSize = __traits(classInstanceSize, T);
        void* mem = core.stdc.stdlib.malloc(classSize);
        scope(failure) core.stdc.stdlib.free(mem);
        assert(mem !is null, "Out of memory");
        mem[0..classSize] = __traits(initSymbol, T);
        T inst = cast(T)mem;
        static if(__traits(hasMember, T, "__ctor")){
                
                inst.__ctor(__traits(parameters));
        }
        return inst;
}
```
Issue is, this code I posted actually runs just fine, unlike the real code. My actual code does this HIGHLY SUSPICIOUS thing when printing their length each time before using them:
```
766
766
765
766
767
768
768
768
768
768
768
768
768
768
768
768
768
768
768
128000
Error Program exited with code -11
(backtrace:)
#0 0x0000555555670c46 in rt.aaA.Impl.findSlotLookup(ulong, scope const(void*), scope const(TypeInfo)) inout ()
#1  0x0000555555661592 in _aaInX ()
```
Therefore I must logically conclude that DRuntime's AAs are cursed! Unless this is a well-known issue or something... Thinking back, I've actually had them cause segfaults in non-threaded code, maybe `__gshared` was never the cause at all.

Reply via email to