Deadlock when using nested tasks + semaphore
Author: olologinCreated Sep 12, 2023Updated Apr 9, 2025
Labelsquestion
Describe the bug With some low probability the following unittest deadlocks instead of completing. It seems tf somehow "forgets" to execute the most nested task which is supposed to do the actual initialization of lazy value, and the rest of threads are either waiting for a task, or just get stuck.
To Reproduce Just paste the following snippet into any uniitest file, build and run it, normally the issue shows up on iteration# 200-400.
TEST_CASE("LazyInit_Deadlocks.16threads")
{
using namespace std::chrono_literals;
for (size_t i = 0; i < 10000; ++i)
{
//std::cout << i << std::endl;
tf::Executor executor(16);
tf::Semaphore semaphore(1);
std::atomic_bool has_value = false;
// The following lambda simulates lazy initializer of some complex and slow to initialize variable
auto lazy_data = [&]()
{
// If we already have a value, return it
if (!has_value.load(std::memory_order_acquire))
{
// Otherwise, calculate it in a task
tf::Taskflow taskflow2;
// Let's imagine the following task is just some slow and complex initialization of some meaningful variable
auto task = taskflow2.emplace([&]() {
if (!has_value.load(std::memory_order_acquire))
{
tf::Taskflow taskflow3;
taskflow3.emplace([&] { std::this_thread::sleep_for(1ms); });
executor.corun(taskflow3);
has_value.store(true, std::memory_order_release);
}
});
task.acquire(semaphore);
task.release(semaphore);
executor.corun(taskflow2);
}
return 99;
};
tf::Taskflow taskflow1;
for (size_t k = 0; k < 16; ++k)
{
taskflow1.emplace(
[&]
{
lazy_data();
});
}
// If the following line fails - you are expiriencing deadlock
REQUIRE(executor.run(taskflow1).wait_for(5s) == std::future_status::ready);
}
}Desktop (please complete the following information): taskflow 3.6, Windows 10, VS2019, x64 build and Ryzen 3800x CPU.
Additional context No other information available.
Source: taskflow/taskflow