JonathanWilbur / csprng-d

Cryptographically-secure pseudo-random number generation in D
MIT License
3 stars 1 forks source link

This library is not thread-safe #6

Open ArthaTi opened 2 years ago

ArthaTi commented 2 years ago

The class destructor is accessing a thread-local variable openInstances. The same class defines invariants, which access said variable. Those get executed before the destructor. The GC can run object finalizers on any thread however (spec), so it may end up accessing an invalid state. This causes the invariants to fail, when they shouldn't.

I'll try to provide an MRE soon, if I remember about it.

ArthaTi commented 2 years ago
import std;
import core.thread;
import core.memory;
import csprng.system;

void finalizerThread() {

    Thread.sleep(100.msecs);

    // Start the GC from a thread the generators are not located in
    GC.collect();

    ownerTid.send("done");

}

void main() {

    // Sequentially run two number generators to make one reuse the stack of the other
    doRNG();
    doRNG();

    // Spawn the other thread
    spawn(&finalizerThread);

    // This is to make sure we get to know about any potential errors in the child thread
    receiveOnly!string;

}

void doRNG() {

    auto rng = new CSPRNG;
    writefln!"%(%.2x%)"(cast(ubyte[]) rng.getBytes(16));

}

When analyzed by GDB, the deadlock can be seen in thread 2 (finalizerThread). If compiled with druntime debug symbols, a _d_throwdwarf frame at (druntime) src/rt/dwarfeh.d:315 appears to contain an assert error raised specifically at csprng/source/csprng/system.d:497.

Note the error is the most important part of this; the deadlock itself is caused by a runtime bug. https://issues.dlang.org/show_bug.cgi?id=23253