Intel® oneAPI Threading Building Blocks
Ask questions and share information about adding parallelism to your applications when using this threading library.

Invalid task_group_context in Task Hierarchy Across Classes

Karim_N_
New Contributor I
740 Views

What I'm trying to do is create a task hierarchy by creating a task in one class that will eventually call tasks in another class.  Initially I set every task to be a root task and then used "spawn_and_wait" which enabled me to pass a task_group_context around to cancel execution.  As soon as I converted to only using one root along with continuation and child tasks, I'm consistently getting invalid task_group_context errors:

Module: tbb_debug.dll
File: ../../src/tbb/scheduler.h
Line: 458

Expression: !(ctx&~(3|7(<<task_group_context::traits_ofset)))
task_group_context is invalid

I have tried both of the following scenarios. Scenario 1 is faster but neither works with a task_group_context created at the start.

Scenario 1: Two Root Tasks of Different Type
============================================
Class A
* creates a task_group_context
* spawns a task 1 (allocate_root) passing in context
* execute method calls a function in Class B passing the same context

Class B
* spawns a task 2 (allocate_root) using the passed in context <== second root task
* execute of task 2 spawns a task 3 as a continuation and task 4 as a child

Scenario 2: Two Continuation Tasks of Different Type
=====================================================
Class A
* creates a task_group_context
* spawns a task 1 (allocate_root) passing in context
* execute method calls a function in Class B passing a "this" pointer

Class B
* spawns a task 2 (allocate_continuation) <== now a continuation of task 1 rather than a separate root
* execute of task 2 spawns a task 3 also as a continuation and task 4 as a child

 

I suppose my questions boil down to the following:

  1. Is it wise to have multiple tasks allocated as root in a task hierarchy?
  2. If the approach is to only have one root task, what is the best way to spawn continuation and child tasks in a different class that shares the same task_group_context?

Any guidance is appreciated.  Thanks.

0 Kudos
3 Replies
Karim_N_
New Contributor I
740 Views

Okay... I've stumbled on a solution but I'm not entirely sure why it works.  A tbb::task_group_context is never explicitly created.  Instead, the implicit task_group_context of the initial root task in Class A is passed to the second root task in Class B.  That root task then uses it when allocating itself as a root task.

ClassA:

auto fn = std::bind(&ClassA::initialize, this, std::placeholders_1);
Task1* t1 = new(tbb::allocate_root()) Task1(fn)
tbb::task::spawn(*t1)

In the execute() method of Task1:
tbb::task* execute()
{
    _fn(this->group);  <== pass the implicit context? to Class B
    return nullptr;
}


Class B:

Task2* t2 = new(tbb::allocate_root(*context)) Task2(); <== context passed in from Class A
tbb::task::spawn(*t2);

In the execute() method of Task 2:
tbb::task* execute()
{
   Task3* t3 = new(tbb::task::allocate_continuation()) Task3();
   Task4* t4 = new(t3->allocate_child()) Task4();
   t3->set_ref_count(1);
   t3->spawn(*t4);
   return nullptr;
}

Task 3 and 4 and now able to respond to cancellation requests via this->group().

However, if I derive from tbb::task_group_context to create a CustomContext, I need to set it in Class A Task1 when allocating root.  In that case, I can't seem to get the context over to Class B.
 

 

0 Kudos
Karim_N_
New Contributor I
740 Views

Things seem to be working with the following code:

class taskGroupContext : public tbb::task_group_context
{
public:
   taskGroupContext()
   {
      _paused = false;
   }

   ~taskGroupContext()
   {
   }

   bool is_group_execution_paused() const { return _paused; }
   void pause_group_execution(const bool pause) { _paused = pause; }

private:
   tbb::atomic<bool> _paused;
};


void ClassA::TASK_createTask1(Message msg)
{
   auto context = new taskGroupContext();
   auto fn = std::bind(&ClassA::initialize, this, std::placeholders::_1, std::placeholders::_2,
      std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6);
   Task1* t = new(tbb::task::allocate_root(*context)) Task1(fn, msg, context);
   _tasks.push_back(t);
   tbb::task::spawn(*t);
}

//Task1's execute method
tbb::task* execute()
{
   _fnc(_msg.p1, _msg.p2, _msg.p3, _msg.p4, _msg.p5, _context);
   _finished = true;
   return nullptr;
}

bool ClassA::initialize(int p1, int p2, int p3, int p4, int p5, taskGroupContext* context)
{
   //do some additional work
   return _classB_instance->getStuff(p1, p2, context);
}

bool ClassB::getStuff(int p1, int p2, taskGroupContext* context)
{
   auto callback = std::bind(&ClassB::onDataAvailable, this, std::placeholders::_1, std::placeholders::_2);
   GetDataTask* t = new(tbb::task::allocate_root(*context)) GetDataTask(p1, callback, context);
   tbb::task::spawn(*t);
         
   return true;
}

//GetDataTask's execute method
tbb::task* execute()
{
   BroadcastDataTask* bt = new(tbb::task::allocate_continuation()) BroadcastDataTask(_p1, _container1, _container2, _callback, _context);
   GenerateRandomDataTask* gt = new(bt->allocate_child()) GenerateRandomDataTask(_container1, _container2, _context);
   bt->set_ref_count(1);
   bt->spawn(*gt);
   return nullptr;
}

//Then in both the continuation and child task, I have code as follows:
if (_context->is_group_execution_cancelled())
{
   _context->reset();
   return nullptr;
}

while (_context->is_group_execution_paused())
{
   if (_context->is_group_execution_cancelled())
   {
       _context->reset();
       return nullptr;
   }
}

I'm am sharing the derived task_group_context between two root tasks and then passing a pointer to that context to the continuation and child tasks.  Things appear to be working as I am able to pause and cancel tasks on demand.

0 Kudos
Karim_N_
New Contributor I
740 Views

While the above code works for cancellation using a CustomContext derived from tbb::task_group_context, there is a memory leak on Line 23: auto context = new taskGroupContext() is never actually deallocated.

To manage this, I have created a TaskManager class that owns a derived context as unique_ptr in a vector.  The entire task group has a raw pointer to this context.  When the innermost child task is finished, a flag is set.  When this flag is set, the TaskManager knows it is safe to remove the context from the vector.

However, by doing so, I'm running into the same error:

Module: tbb_debug.dll
File: ../../src/tbb/scheduler.h
Line: 458

Expression: !(ctx&~(3|7(<<task_group_context::traits_ofset)))
task_group_context is invalid

I do not get much of a callstack from which to debug.  I see all of my task destructors getting hit, the CustomContext destructor getting hit, but eventually I get the assertion being thrown.

Since I'm using a derived class for the context, I'm guessing the base context is already cleaned up somewhere by tbb?  When I remove my derived context from the container, its destructor gets hit (which is fine) but then it tries to deallocate the base context? 

Not quite sure what is going on... Any suggestions on cleaning up a derived task_group_context is appreciated.

0 Kudos
Reply