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.