I have written a small async_task
class that keeps a thread warm so that I can perform a background calculation. The task could be triggered by multiple threads, but only one instance of the task should be running at any one time. On one of my CI servers (a very old and slow mac mini circa 2011 - intel penryn processor) my unit test sometimes fails with a SIGABRT (compiling with Clang 9.0 - not AppleClang - on macOS 10.13). It's never failed on the Windows 10 build machine - intel i9 processor.
Here is a minimum representation of the code under test and the unit test extracted into a stand alone C++ application:
#include <thread>
#include <condition_variable>
#include <mutex>
#include <atomic>
#include <functional>
#include <iostream>
// class under test...
class async_task final
{
std::thread thread_;
std::condition_variable run_request_;
std::mutex run_request_mutex_;
std::atomic<bool> quit_{ false };
std::atomic<bool> run_{ false };
public:
async_task(std::function<void()> task) :
thread_{ [this, task] { thread_proc(task); }}
{
}
~async_task()
{
{
std::unique_lock<std::mutex> lock(run_request_mutex_);
quit_ = true;
}
run_request_.notify_one();
thread_.join();
}
void run()
{
{
std::unique_lock<std::mutex> lock(run_request_mutex_);
run_ = true;
}
run_request_.notify_one();
}
private:
void thread_proc(std::function<void()> task)
{
while (!quit_)
{
{
std::unique_lock<std::mutex> lock{ run_request_mutex_ };
run_request_.wait(lock, [this] { return quit_ || run_; });
}
bool run = false;
if (run_.exchange(false))
{
run = !quit_;
}
if (run)
{
task();
}
}
}
};
// exercising code...
int main()
{
std::condition_variable condition;
std::mutex mutex;
std::atomic<bool> value = false;
async_task task{ [&value, &mutex, &condition]()
{
{
std::unique_lock<std::mutex> lock(mutex);
value = true;
}
condition.notify_one();
} };
task.run();
{
using namespace std::chrono_literals;
std::unique_lock<std::mutex> lock(mutex);
if (!value)
{
condition.wait_for(lock, 5s, [&value] { return value.load(); });
}
}
return EXIT_SUCCESS;
}
There must be a race condition, but I can't for the life of me spot what might cause a SIGABRT. Can anyone spot the problem?
UPDATE: added mutex to destructor to protect quit_
as this has been pointed out as a secondary problem - although not the cause of the issue in question.