I’m attempting to wrap a somewhat complicated C++ library using CxxWrap and am running into issues when it comes to blocking and multi-threaded C++ code. Here’s an abbreviated example of the problem:
# Functions/objects prefixed with cpp_/Cpp are
# trivial wrappers around their C++ equivalents.
function loop(object)
while cpp_is_ready(object)
cpp_pthread_cond_wait(object) # Calls pthread_cond_wait in C++
msg = cpp_get_message(object)
# Do stuff with msg
end
end
function my_callback(msg)
# Do stuff with msg
end
function main()
object = CppObject(@cfunction(my_callback, Nothing, (Cstring, )))
start(object) # Starts a pthread in C++
Threads.@spawn reader(object)
request(object, "foobar")
end
At a high level, this is a simple reader/writer design. start(object)
calls a C++ function that initializes a writer thread (using pthread
) that writes a message to an internal queue for each call to request(object, msg_request)
. reader(object)
waits for a message via cpp_pthread_cond_wait(object)
which internally calls pthread_cond_wait
in C++ land. process_message
then grabs that message from the queue and calls the callback my_function
.
All together, we have three threads:
Thread 1: pthread
created in C++ from call to start(object)
Thread 2: Julia thread created from @spawn
Thread 3: Main Julia thread that makes the call to request(object)
.
and one lock, the pthread_mutex
created in C++ land.
Now this all works fine, until an exception occurs, either from an error or something like Ctrl-C
. When that occurs, the process hangs and the terminal becomes unresponsive to input. Not even Ctrl-C
(SIGINT) or Ctrl-\
(SIGQUIT) works. I have to close the terminal. Note that issue only occurs when mixing Julia and C++, if I recreate the above code block in pure C++ then Ctrl-C
works as expected.
Reading through the Julia manual, I found this discussion next to the docs for @threadcall
:
If a C library performs a blocking operation, that prevents the Julia scheduler from executing any other tasks until the call returns.
Which leads me to suspect that the issue is unrelated to the multi-threading and solely to do with the call to cpp_pthread_cond_wait(object)
. Given that the pure C++ version of the above code works find, I suspect that SIGINT
doesn’t actually get issued so the reader
that’s waiting on cpp_pthread_cond_wait
never wakes up. I though about using @threadcall
, but it is has this caveat:
It is very important that the called function does not call back into Julia, as it will segfault.
Which disqualifies my since the library requires that I pass it a callback. With this in mind, I tried the following modification:
function main(object)
object = CppObject(@cfunction(my_callback, Nothing, (Cstring, )))
try
start(object) # Starts a pthread in C++
Threads.@spawn reader(object)
request(object, "foobar")
catch e
# Issue a signal that wakes up the waiting thread in `cpp_pthread_cond_wait`
issue_signal(object)
end
end
which surprisingly fixes the issue! But it seems like such an ugly hack. So here’s my question:
- Is my deduction that the root cause of the issue is the call to
cpp_pthread_cond_wait
correct/probable? - What’s a better solution to the above problem? Can I make sure that
SIGINT
gets signaled somehow? Would that even solve the issue? What about callingjl_yield
incpp_pthread_cond_wait
from C++?
Thank you for any and all help on an incredibly frustrating problem!