Ben
Ben

Reputation: 9703

tbb::parallel_for_each not executing while canceling

I'm running tbb::parallel_for_each deep within a task_group. The task_group is being canceled and that seems to cause tbb::parallel_for_each to exit without satisfying its postcondition. Here's minimal test case:

tbb::task_group g;
std::vector<int> x { 0, 0, 0, 0 };
std::atomic<std::size_t> counter {0};
g.run([&x, &counter]() {
    std::cout << "in run()\n" << std::flush;
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    std::cout << "in run(): slept\n" << std::flush;
    assert(tbb::task::self().is_cancelled());

    tbb::parallel_for_each(x, [&counter](int& y) {
        std::cout << "in run(): in parallel_for_each\n" << std::flush;
        ++y;
        ++counter;
    });
    assert(counter == x.size());
});
std::cout << "canceling\n" << std::flush;
g.cancel();
std::cout << "canceled " << g.is_canceling() << " " << tbb::task::self().is_cancelled() << std::endl;
assert(g.is_canceling());
std::cout << "canceled " << g.is_canceling() << " " << tbb::task::self().is_cancelled() << std::endl;
g.wait();
std::cout << "canceled " << g.is_canceling() << " " << tbb::task::self().is_cancelled() << std::endl;

That is: It has tbb::task_group call run on a function that waits for 0.1 s, then loops over a vector. It counts how many iterations that loop does. It then cancel()s the task group. The output is

canceling
in run()
canceled 1 0
canceled 1 0
in run(): slept
Assertion failed: (counter == x.size()), function operator(), file test.cpp

That's to say that the inner loop never happens. My surprise, though, is that tbb::parallel_for_each is called and exits without exception, but its post-condition (of doing the loop) isn't met!

  1. Is that the expected behavior? The docs don't give any caveats: https://software.intel.com/en-us/node/506160
  2. How do I check for this behavior? Do I always have to check tbb::task::self().is_cancelled() after calling tbb::parallel_for_each if I want to be sure it actually did its job?
  3. Shouldn't tbb::parallel_for_each throw an exception in this case (or at least return bool)?

I can "fix" this by using an isolated context (https://software.intel.com/en-us/node/506075) like this:

    tbb::task_group_context root(tbb::task_group_context::isolated);
    tbb::parallel_for_each(x, [&counter](int& y) {
        std::cout << "in run(): in parallel_for_each\n" << std::flush;
        ++y;
        ++counter;
    }, root);

but at the moment, I'm not sure when I can ever trust tbb::parallel_for_each to do its job.

Upvotes: 1

Views: 581

Answers (1)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275385

Within a task group, calls to tbb::parallel_for_each are subordinate to the task group's context.

Cancelling that task group cancels it, and all subordinate task groups. I cannot find where in the docs "subordinate task group" is defined, but it appears to be groups under the group in the "task tree".

A cancelled tbb::parallel_for_each does not guarantee that all of its iterations are complete. No post condition was violated.

You cancelled the task group, which cancels the subordinate tbb::parallel_for_each's implicit task group, so it doesn't do the operations.

Your isolated context prevents the cancellation from propagating into the parallel for each's task group. You can replicate the effect in that code by canceling root prior to calling parallel_for_each.

Upvotes: 1

Related Questions