-
Notifications
You must be signed in to change notification settings - Fork 141
test: Fix multi-threading + exception handling #189
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
If a unit test throws an exception, usually the test framework will catch it cleanly. However, for multi-threaded tests, the test code needs to catch exceptions explicitly, because apparently the Boost Unit Test Framework is not thread-safe. Here I've seen the multithreaded_async_pipe unit test triggering multiple exceptions in parallel (because the async_pipe constructor failed), which resulted in multiple parallel std::terminate() calls. Then each std::terminate() triggered a SIGABRT for that thread, causing each thread to invoke the Boost Unit Test Framework's signal handler (boost_execution_monitor_jumping_signal_handler()), which however is not thread-safe (doing the same longjmp() in all threads, resulting in stack corruption, SIGSEGV, sometimes even hangs). As solution, I think using std::future should work – it supports transporting the exceptions from the thread to the parent and will re-throw them there (but note that obviously we'll only see the 1st, not all exceptions from all threads). Although I've only had problems with the multithreaded_async_pipe test, and that was essentially only triggered by coincidence (it tries to create N_cores * 100 pipes, which fails here due to hitting the maximum number of open files soft-limit (ulimit -n, 1024)), I still think it's worth it to fix the test suite behaviour in this case, especially because it sometimes ended up hanging to the stack smashing. Thus I've also converted all other unit tests from std::thread to std::future + std::async.
eb4f66b to
0b3346c
Compare
|
What is this addressing? A false negative or just a crash on test failure? |
|
The main problem is undefined behaviour in the test suite, in case of multi-threading and exceptions. It could cause the test suite to randomly pass, fail, print garbage messages, crash, or even hang. Obviously that's not great for debugging, regardless of whether the actual error that has happened is a false-positive or real bug. In this patch I tried to fix all potential cases which could run into that problem, to ensure the test suite has consistent behaviour. |
|
I think adding noexcept to the threads is better, causing a reliable |
|
Interesting idea; if it works, it should be applied to all the threads in the test suite, not only in However I'm not sure about |
Squash after invalid branch & merge conflict. * Fixed file_descriptor move assignment operator to return a reference to 'this'. Issue # 219 * Returning *this instead of erroneous *this. Issue # 219 * Removed unneeded WNOHANG. * Closes #190 * Closes #121 * Attempting to fix wchar_t build error on circle. * Closes #197. * Changed child(pid_t) signature. * Multiple fixes. * Closes #189. * Closes #191. * Added missing work guard on windows. * Trying to catch windows early complete. * Increased log level on windows. * Multiple windows test fixes * Removed overly constraint tests. * fix missing headers * Closes klemens-morgenstern/boost-process#218 * Update executor.hpp explicit cast to int to silence this: `error: non-constant-expression cannot be narrowed from type 'unsigned long' to 'int' in initializer list [-Wc++11-narrowing]` * Fix posix implementation of move constructor/assignment in file_descriptor * Adjust docs `@boost` relative paths * Fixed UB for large environment names. * Closes #207. * Drone setup * Added include for filesystem::fstream. * Disabled useless tests. * Fixed environment length checks. * Pipe test & warning fixes. * Disabled warnings & added windows include fix. * More test fixes. * Removed some tests from apple build. * Removed some tests from apple build. * Disabled OSX tests via build script & fixed windows examples. * TSA fix attempt. Co-authored-by: James Baker <[email protected]> Co-authored-by: silent <[email protected]> Co-authored-by: ikrijan <[email protected]> Co-authored-by: Shauren <[email protected]> Co-authored-by: alandefreitas <[email protected]>
If a unit test throws an exception, usually the test framework will catch it cleanly. However, for multi-threaded tests, the test code needs to catch exceptions explicitly, because apparently the Boost Unit Test Framework is not thread-safe.
Here I've seen the multithreaded_async_pipe unit test triggering multiple exceptions in parallel (because the async_pipe constructor failed), which resulted in multiple parallel std::terminate() calls. Then each std::terminate() triggered a SIGABRT for that thread, causing each thread to invoke the Boost Unit Test Framework's signal handler (boost_execution_monitor_jumping_signal_handler()), which however is not thread-safe (doing the same longjmp() in all threads, resulting in stack corruption, SIGSEGV, sometimes even hangs).
As solution, I think using std::future should work – it supports transporting the exceptions from the thread to the parent and will re-throw them there (but note that obviously we'll only see the 1st, not all exceptions from all threads).
Although I've only had problems with the multithreaded_async_pipe test, and that was essentially only triggered by coincidence (it tries to create N_cores * 100 pipes, which fails here due to hitting the maximum number of open files soft-limit (ulimit -n, 1024)), I still think it's worth it to fix the test suite behaviour in this case, especially because it sometimes ended up hanging to the stack smashing. Thus I've also converted all other unit tests from std::thread to std::future + std::async.