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

Reply via email to