In my test, I have this code:
type
TstMsg = Msg[int]
TstRep = Msg[float]
var dst: QueueID
initThreadID(ThreadID(0))
var m: TstMsg
m.content = 42
when USE_TOPICS:
initTopicID(TopicID(33))
let pid = myProcessID()
dst = queueID(pid, ThreadID(1), TopicID(99))
else:
dst = queueID(myProcessID(), ThreadID(1))
assert(not pendingMsg())
sendMsgNow(dst, addr m)
echo("First msg sent from Thread 0.")
which in sendMsgNow() indirectly accesses this threadvar:
var myPendingRequests {.threadvar.}: HashSet[ptr MsgBase]
myPendingRequests.init()
And it works. However, if I refactor my code like this (moving all code after "var dst: QueueID" to a proc, and calling it):
type
TstMsg = Msg[int]
TstRep = Msg[float]
var dst: QueueID
proc startTest() =
initThreadID(ThreadID(0))
var m: TstMsg
m.content = 42
when USE_TOPICS:
initTopicID(TopicID(33))
let pid = myProcessID()
dst = queueID(pid, ThreadID(1), TopicID(99))
else:
dst = queueID(myProcessID(), ThreadID(1))
assert(not pendingMsg())
sendMsgNow(dst, addr m)
echo("First msg sent from Thread 0.")
startTest()
Then I get this error:
Traceback (most recent call last)
test_kueues.nim(128) receiver
kueues.nim(830) recvMsgs
kueues.nim(785) collectReceived
kueues.nim(771) validateRequest
sets.nim(204) contains
system.nim(3613) failedAssertImpl
system.nim(3605) raiseAssert
system.nim(2724) sysFatal
Error: unhandled exception: isValid(s) The set needs to be initialized. [AssertionError]
Error: execution of an external program failed: '../bin/test_kueues '
The terminal process terminated with exit code: 1
kueues.nim(771) is accessing myPendingRequests.
All the rest of the code is unchanged.
How could myPendingRequests ever not be initialized, and why is that only so if I move some top level code to a proc, which I then call at the exact same place the top level code was before?
Even if myPendingRequests.init() ran only once, and not once per thread (does it? and if yes, how do you say "do this in every thread"?), I still don't think it would explain that the error only comes when the code is not "top level".
sendMsgNow(dst, addr m)
Is this really safe when m is located on the stack of proc startTest() ? I don't know, but would worry.
@Stefan_Salewski You're totally right! This would probably go horribly wrong. But I don't think this explains the error with the uninitialised HashSet. The HashSet itself just contains pointers; it doesn't care if they are still "valid" or not.
I'll change the code so that the HashSet is accessed with a "getter" that does "lazy" initialisation, per thread. If the error goes away, then that will "prove" the HashSet (and presumably every other complex threadvar object) needs to be initialised explicitly in each thread. If that is the case, it would be nice to have some syntax (something like the opposite of "static:") that takes care of that for you.
EDIT: Spoke too soon; just moving "var m: TstMsg" to the top level fixes the error. So I still don't know if "myPendingRequests.init()" is called in every thread. I'll have to write a test to check that.
If the error goes away, then that will "prove" the HashSet (and presumably every other complex threadvar object) needs to be initialised explicitly in each thread. If that is the case, it would be nice to have some syntax (something like the opposite of "static:") that takes care of that for you.
Yeah, that's how thread local storage works, every thread needs to initialize its very own memory. :-) We had something like you propose called onThreadCreation but it just encourages races and so we removed it again.
var myPendingRequests {.threadvar.}: HashSet[ptr MsgBase]
myPendingRequests.init()
The compiler made you write this, it disallows var myPendingRequests {.threadvar.} = initSet[ptr MsgBase](). Ever wondered why? Because it would only run for the main thread. I improved its error message in the hope it clears things up.