On Tuesday, 12 January 2021 at 01:49:11 UTC, tsbockman wrote:
The compiler and the physical CPU are both allowed to change
the order in which instructions are executed to something
different from what your code specifies, as long as the
visible, "official" results and effects of the chosen order of
execution are the same as those of your specified code, FROM
THE PERSPECTIVE OF THE EXECUTING THREAD.
This is allowed so that the compiler can optimize to minimize
negative "unofficial" effects such as the passage of time and
memory consumption.
However, this re-ordering IS permitted to freely alter the
behavior of your code from the perspective of OTHER threads. A
likely cause of your bug is that the write to db by the
constructor's thread is being committed to memory after the
read of db by the MessageService thread.
In order to RELIABLY fix this kind of problem, you must
correctly use the only commands which the compiler and CPU are
NOT allowed to reorder with respect to other threads, namely
atomic operations, memory barriers and synchronization
primitives. A wide selection of these tools may be found in
these D runtime library modules:
core.sync:
http://dpldocs.info/experimental-docs/core.sync.html
core.atomic:
http://dpldocs.info/experimental-docs/core.atomic.html
core.thread:
http://dpldocs.info/experimental-docs/core.thread.html
(I recommend using Adam D. Ruppe's unofficial but superior
rendering of the D runtime documentation at dpldocs.info rather
than the official dlang.org rendering, as I found some
necessary pieces of the documentation are just mysteriously
missing from the offical version.)
Be warned that most models of multi-threaded programming are
difficult to implement correctly, as opposed to ALMOST
correctly with subtle heisen-bugs. You should either stick to
one of the known simple models like immutable message passing
with GC, or do some studying before writing too much code.
Here are some resources which I have found very helpful in
learning to understand this topic, and to avoid its pitfalls:
Short educational game: https://deadlockempire.github.io/
Tech talk by C++ expert Herb Sutter (D's core.atomic uses
the C++ memory model):
https://channel9.msdn.com/Shows/Going+Deep/Cpp-and-Beyond-2012-Herb-Sutter-atomic-Weapons-1-of-2
https://channel9.msdn.com/Shows/Going+Deep/Cpp-and-Beyond-2012-Herb-Sutter-atomic-Weapons-2-of-2
If you want to seriously dig into this, I suggest reviewing
some or all of the content at the links above. If you're still
confused about how to apply it in D, feel free to come back and
ask for examples or code reviews. I'd rather not start with
examples, though, because if you don't understand the rules and
principles behind them, it's really easy to unknowingly
introduce bugs into otherwise correct examples with seemingly
innocent changes.
Fantastic response, thank you! I did some more digging and
properly narrowed down where the issue is and created a test
script that demonstrates the problem. Let me know what you think
and if it could still be a similar problem to what you have
stated above. I'll still read that info you sent to sharpen up on
these concepts.
Basically, the program calls a function which modifies a document
in the database. If it is called form it's own class'
constructor, it works fine. If it is called by a thread, it never
returns. I don't think that a member variable is going null or
anything. But a strange problem that I can't seem to debug. The
output is at the bottom.
----------------------------------------------------------------
import vibe.db.mongo.mongo;
import core.thread;
import std.stdio;
void main(){
auto callable = new Callable();
while(true){}
}
class Caller : Thread{
void delegate() mFunc;
this(void delegate() func){
mFunc = func;
super(&loop);
start();
}
void loop(){
while(true){
mFunc();
}
}
}
class Callable{
MongoClient db;
Caller caller;
this(){
db = connectMongoDB("127.0.0.1");
foo();
caller = new Caller(&foo);
}
~this(){
db.cleanupConnections();
}
void foo(){
writeln("Started");
auto result =
db.getCollection("test.collection").findAndModify([
"state": "running"],
["$set": ["state": "stopped"]
]);
writeln(result);
writeln("Finished");
}
}
----------------------------------------------------------------
Output:
Started
{"_id":"5ff6705e21e91678c737533f","state":"running","knowledge":true}
Finished
Started