On Sunday, 9 May 2021 at 03:25:06 UTC, Ali Çehreli wrote:
>That workaround seemed to be sufficient to load the library successfully. Unfortunately, that was not enough to weed out all issues related to libraries because this library itself loads other D libraries. All of this caused sporadic issues. (My brain is too fried to even remember what was a cause, what was a usable workaround, etc. Sometimes I wasted days chasing a solution while using a test, which had nothing to do with the solution. I would change the code, test, no go; repeat, no go. It turns out, my test was unrelated. Argh!)
In my experience, calling D from C/C++ works fine as long as 1) the D runtime is allowed to initialize, and 2) all threads which execute D code are registered with the D runtime.
If C/C++ code is allowed to hold the only reference to an object in the D GC heap, then the second rule needs to be extended to all threads which may hold a reference to said objects, but it may be practical to copy D objects at the C/D barrier either to caller-owned memory, or malloc-allocated memory that the caller can free by calling the standard C free function.
So, we came up with a drastic solution: Since all this code works just fine in a pure D environment, make the library as thin as possible; the library starts a daemon that is written in D with all the functionality. The library merely dispatches the requests to that daemon.
The library starts the daemon with pipeProcess(); pipes are used for dispatching requests and shared memory is used for large data. This idea "worked like a charm." Phew!
However, dispatching of the requests to the daemon is performed by a single library thread in a blocked manner: When a request is written to the pipe, the response is read back (blocked) and the result is returned to the user of the library function.
If the threads don't need to share state, you could just as well spawn one subprocess per thread, and let it do its own data processing.
Another approach would be to listen on a UNIX socket instead of using a pipe, which allows using accept to open new communication channels on-demand.
I feel so hopeless that in the past, I even thought about and experimented with banning the user from starting threads on their own. Rather, they would call my library on a posix compatible thread API and create their threads through me, which happens to be a D thread, so no thread would be a "foreign thread" and everything would work just fine. I haven't deployed this crazy idea (yet).
Perhaps it would be simpler to just write the library part in C / C++ / -betterC D. std.mmfile has many lines, but the work it has to do is actually quite simple. The same is true about std.socket. This will completely avoid your headache with getting the D runtime / GC to play well with the host process's threading model.
Permalink
Reply