Task aborted with an exception is respawned endlessly

I’ve got an asynchronous taskchain to perform tasks in parallel with the application thread, but if a task raise an exception, it is simply restarted instead of being aborted or the whole application stopped.

Here is a simple example:

#!/usr/bin/env python

from direct.showbase.ShowBase import ShowBase

def processTask(task):
    print("Hello")
    wrong.value = 1
    print("Goodbye")
    return task.done

base = ShowBase()

base.taskMgr.setupTaskChain("chain",
                            numThreads = 1,
                            tickClock = False,
                            threadPriority = None,
                            frameBudget = -1,
                            frameSync = False,
                            timeslicePriority = True)

process_task = base.taskMgr.add(processTask, 'ProcessTask', taskChain="chain")

base.run()

The (truncated) log is :

Hello
:thread(error): Exception occurred within Thread TaskManager_chain_0
Traceback (most recent call last):
  File "tests/test_task.py", line 7, in processTask
    wrong.value = 1
NameError: name 'wrong' is not defined
:task(error): Exception occurred in PythonTask ProcessTask
Hello
:thread(error): Exception occurred within Thread TaskManager_chain_0
Traceback (most recent call last):
  File "tests/test_task.py", line 7, in processTask
    wrong.value = 1
NameError: name 'wrong' is not defined
:task(error): Exception occurred in PythonTask ProcessTask
Hello
:thread(error): Exception occurred within Thread TaskManager_chain_0
Traceback (most recent call last):
  File "tests/test_task.py", line 7, in processTask
    wrong.value = 1
NameError: name 'wrong' is not defined
:task(error): Exception occurred in PythonTask ProcessTask

The workaround is to wrap the actual task code with a try: except and use sys.exit() to stop the application but it’s rather ugly to say the less.

The problem is identical using Python2 or Python3 :slight_smile:

I had some time to look at the problem :

In pythonTask.cxx:665

    } else {
      task_cat.error()
        << "Exception occurred in " << *this << "\n";
    }
    return DS_interrupt;

In case of exception, the task is simply interrupted and not aborted, as according to the definition of DS_interrupt :

 * DS_interrupt: Interrupt the whole AsyncTaskManager.  The task will continue
 * again next epoch, as if it had returned DS_cont.

On the contrary, if a coroutine raises an exception, the code kills the task using DS_done, see line 585

So, I would say the fix is to replace DS_interrupt with DS_done, but maybe this behavior is expected ?

Actually it seems the problem is more in AsyncTaskChain which should be stopped in case a task return DS_Interrupt as

        case AsyncTask::DS_interrupt:
          // The task had an exception and wants to raise a big flag.
          task->_state = AsyncTask::S_active;
          _next_active.push_back(task);
          if (_state == S_started) {
            _state = S_interrupted;
            _cvar.notify_all();
          }

and In AsyncTaskChain::do_start_threads() :

  if (_state == S_interrupted) {
    do_stop_threads();
  }

I’m stopping my investigation here as it is getting way out of my league :slight_smile:

Sorry to bump this again, I’m willing to invest time looking more into the problem, but first I want to be sure there is a problem and not that I’m not using the task and task chains wrongly :slight_smile:

For anyone coming across this thread, this issue is now tracked on GitHub: