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

Spawning recycled task


I changed the example from TBB Tutorial section 11.5.4 so that FibTask::execute() spawns the recycled task instead of returning this pointer. When the code is compiled and run in debug mode it causes assertion failure with the message task in READY state upon return from method execute(). Can someone help me to figure out what it is going on? Thank you.

Below is the complete example.

// example from TBB tutorial section 11.5.4
#include "tbb/task.h"

const long CutOff = 5;

long SerialFib(long n)
    return n;
    return SerialFib(n-1)+SerialFib(n-2);

struct FibContinuation : public tbb::task
  long* const sum;
  long x, y;
  FibContinuation(long* sum_): sum(sum_) {}
  task* execute() {
    *sum = x + y;
    return NULL;

struct FibTask : public tbb::task
  long n;
  long* sum;
  FibTask(long n_, long* sum_): n(n_), sum(sum_) {}
  task* execute() {

0 Kudos
2 Replies
Valued Contributor II
Looks like you've set up your recycled task to be spawned twice: once with the recycle_as_child_of call and once with the explicit spawn. The spawn sets the task to the READY state which the scheduler reacts to when it tries to perform the recycle_....
0 Kudos

This is an unfortunate trap that exists in TBB when recycling tasks. There is a poorly documented restriction that while t.execute() is running, task t must not be spawned. The reason is after t.execute() returns, the scheduler inspects some state internal to t. But if t has been spawned in the meantime, all kinds of asynchronous actions may be occuring to t, including deletion!

We did not notice the trap for a few years because we were always using thetrick where t.execute() returns a pointer to the recycled task instead of spawning it.(The trick exists to simulate tail-call). Then after a few releases, some users were falling into a similar trap where a task t recycled as a continuation started running before t.execute() returned. Hence the invention of task::recycle_as_safe_continuation(), which avoids the problem in that case. In theory we could add a similar task::recycle_as_safe_child_of(), and an internal rule that if such a recycled task is spawned, it is not really spawned until t.execute() returns. But that burdens everyone (performance wise) with yet-another-internal-branch and complication to explain, and wouldn't solve the problem completely because users could still trap themselves by spawning such a recycled task and then waiting for it to complete.

So the basic rule is that a task object t cannot be put in jeopardy of having two simultaneous invocations of t.execute(). The assertion detects a case of such jeopardy.

0 Kudos