Panda3D
pythonTask.cxx
Go to the documentation of this file.
1 /**
2  * PANDA 3D SOFTWARE
3  * Copyright (c) Carnegie Mellon University. All rights reserved.
4  *
5  * All use of this software is subject to the terms of the revised BSD
6  * license. You should have received a copy of this license along
7  * with this source code in a file named "LICENSE."
8  *
9  * @file pythonTask.cxx
10  * @author drose
11  * @date 2008-09-16
12  */
13 
14 #include "pythonTask.h"
15 #include "pnotify.h"
16 #include "config_event.h"
17 
18 #ifdef HAVE_PYTHON
19 #include "py_panda.h"
20 
21 #include "pythonThread.h"
22 #include "asyncTaskManager.h"
23 
24 TypeHandle PythonTask::_type_handle;
25 
26 #ifndef CPPPARSER
27 extern struct Dtool_PyTypedObject Dtool_TypedReferenceCount;
28 extern struct Dtool_PyTypedObject Dtool_AsyncFuture;
29 extern struct Dtool_PyTypedObject Dtool_PythonTask;
30 #endif
31 
32 /**
33  *
34  */
35 PythonTask::
36 PythonTask(PyObject *func_or_coro, const std::string &name) :
37  AsyncTask(name),
38  _function(nullptr),
39  _args(nullptr),
40  _upon_death(nullptr),
41  _owner(nullptr),
42  _registered_to_owner(false),
43  _exception(nullptr),
44  _exc_value(nullptr),
45  _exc_traceback(nullptr),
46  _generator(nullptr),
47  _future_done(nullptr),
48  _ignore_return(false),
49  _retrieved_exception(false) {
50 
51  nassertv(func_or_coro != nullptr);
52  if (func_or_coro == Py_None || PyCallable_Check(func_or_coro)) {
53  _function = func_or_coro;
54  Py_INCREF(_function);
55 #if PY_VERSION_HEX >= 0x03050000
56  } else if (PyCoro_CheckExact(func_or_coro)) {
57  // We also allow passing in a coroutine, because why not.
58  _generator = func_or_coro;
59  Py_INCREF(_generator);
60 #endif
61  } else if (PyGen_CheckExact(func_or_coro)) {
62  // Something emulating a coroutine.
63  _generator = func_or_coro;
64  Py_INCREF(_generator);
65  } else {
66  nassert_raise("Invalid function passed to PythonTask");
67  }
68 
69  set_args(Py_None, true);
70  set_upon_death(Py_None);
71  set_owner(Py_None);
72 
73  __dict__ = PyDict_New();
74 
75 #ifndef SIMPLE_THREADS
76  // Ensure that the Python threading system is initialized and ready to go.
77 #ifdef WITH_THREAD // This symbol defined within Python.h
78  PyEval_InitThreads();
79 #endif
80 #endif
81 }
82 
83 /**
84  *
85  */
86 PythonTask::
87 ~PythonTask() {
88 #ifndef NDEBUG
89  // If the coroutine threw an exception, and there was no opportunity to
90  // handle it, let the user know.
91  if (_exception != nullptr && !_retrieved_exception) {
92  task_cat.error()
93  << *this << " exception was never retrieved:\n";
94  PyErr_Restore(_exception, _exc_value, _exc_traceback);
95  PyErr_Print();
96  PyErr_Restore(nullptr, nullptr, nullptr);
97  _exception = nullptr;
98  _exc_value = nullptr;
99  _exc_traceback = nullptr;
100  }
101 #endif
102 
103  Py_XDECREF(_function);
104  Py_DECREF(_args);
105  Py_DECREF(__dict__);
106  Py_XDECREF(_exception);
107  Py_XDECREF(_exc_value);
108  Py_XDECREF(_exc_traceback);
109  Py_XDECREF(_generator);
110  Py_XDECREF(_owner);
111  Py_XDECREF(_upon_death);
112 }
113 
114 /**
115  * Replaces the function that is called when the task runs. The parameter
116  * should be a Python callable object.
117  */
118 void PythonTask::
119 set_function(PyObject *function) {
120  Py_XDECREF(_function);
121 
122  _function = function;
123  Py_INCREF(_function);
124  if (_function != Py_None && !PyCallable_Check(_function)) {
125  nassert_raise("Invalid function passed to PythonTask");
126  }
127 }
128 
129 /**
130  * Replaces the argument list that is passed to the task function. The
131  * parameter should be a tuple or list of arguments, or None to indicate the
132  * empty list.
133  */
134 void PythonTask::
135 set_args(PyObject *args, bool append_task) {
136  Py_XDECREF(_args);
137  _args = nullptr;
138 
139  if (args == Py_None) {
140  // None means no arguments; create an empty tuple.
141  _args = PyTuple_New(0);
142  } else {
143  if (PySequence_Check(args)) {
144  _args = PySequence_Tuple(args);
145  }
146  }
147 
148  if (_args == nullptr) {
149  nassert_raise("Invalid args passed to PythonTask");
150  _args = PyTuple_New(0);
151  }
152 
153  _append_task = append_task;
154 }
155 
156 /**
157  * Returns the argument list that is passed to the task function.
158  */
159 PyObject *PythonTask::
160 get_args() {
161  if (_append_task) {
162  // If we want to append the task, we have to create a new tuple with space
163  // for one more at the end. We have to do this dynamically each time, to
164  // avoid storing the task itself in its own arguments list, and thereby
165  // creating a cyclical reference.
166 
167  int num_args = PyTuple_GET_SIZE(_args);
168  PyObject *with_task = PyTuple_New(num_args + 1);
169  for (int i = 0; i < num_args; ++i) {
170  PyObject *item = PyTuple_GET_ITEM(_args, i);
171  Py_INCREF(item);
172  PyTuple_SET_ITEM(with_task, i, item);
173  }
174 
175  this->ref();
176  PyObject *self = DTool_CreatePyInstance(this, Dtool_PythonTask, true, false);
177  PyTuple_SET_ITEM(with_task, num_args, self);
178  return with_task;
179 
180  } else {
181  Py_INCREF(_args);
182  return _args;
183  }
184 }
185 
186 /**
187  * Replaces the function that is called when the task finishes. The parameter
188  * should be a Python callable object.
189  */
190 void PythonTask::
191 set_upon_death(PyObject *upon_death) {
192  Py_XDECREF(_upon_death);
193 
194  _upon_death = upon_death;
195  Py_INCREF(_upon_death);
196  if (_upon_death != Py_None && !PyCallable_Check(_upon_death)) {
197  nassert_raise("Invalid upon_death function passed to PythonTask");
198  }
199 }
200 
201 /**
202  * Specifies a Python object that serves as the "owner" for the task. This
203  * owner object must have two methods: _addTask() and _clearTask(), which will
204  * be called with one parameter, the task object.
205  *
206  * owner._addTask() is called when the task is added into the active task
207  * list, and owner._clearTask() is called when it is removed.
208  */
209 void PythonTask::
210 set_owner(PyObject *owner) {
211 #ifndef NDEBUG
212  if (owner != Py_None) {
213  PyObject *add = PyObject_GetAttrString(owner, "_addTask");
214  PyObject *clear = PyObject_GetAttrString(owner, "_clearTask");
215 
216  if (add == nullptr || !PyCallable_Check(add) ||
217  clear == nullptr || !PyCallable_Check(clear)) {
218  Dtool_Raise_TypeError("owner object should have _addTask and _clearTask methods");
219  return;
220  }
221  }
222 #endif
223 
224  if (_owner != nullptr && _owner != Py_None && _state != S_inactive) {
225  unregister_from_owner();
226  }
227 
228  Py_XDECREF(_owner);
229  _owner = owner;
230  Py_INCREF(_owner);
231 
232  if (_owner != Py_None && _state != S_inactive) {
233  register_to_owner();
234  }
235 }
236 
237 /**
238  * Returns the result of this task's execution, as set by set_result() within
239  * the task or returned from a coroutine added to the task manager. If an
240  * exception occurred within this task, it is raised instead.
241  */
242 PyObject *PythonTask::
243 get_result() const {
244  nassertr(done(), nullptr);
245 
246  if (_exception == nullptr) {
247  // The result of the call is stored in _exc_value.
248  Py_XINCREF(_exc_value);
249  return _exc_value;
250  } else {
251  _retrieved_exception = true;
252  Py_INCREF(_exception);
253  Py_XINCREF(_exc_value);
254  Py_XINCREF(_exc_traceback);
255  PyErr_Restore(_exception, _exc_value, _exc_traceback);
256  return nullptr;
257  }
258 }
259 
260 /**
261  * If an exception occurred during execution of this task, returns it. This
262  * is only set if this task returned a coroutine or generator.
263  */
264 /*PyObject *PythonTask::
265 exception() const {
266  if (_exception == nullptr) {
267  Py_INCREF(Py_None);
268  return Py_None;
269  } else if (_exc_value == nullptr || _exc_value == Py_None) {
270  return _PyObject_CallNoArg(_exception);
271  } else if (PyTuple_Check(_exc_value)) {
272  return PyObject_Call(_exception, _exc_value, nullptr);
273  } else {
274  return PyObject_CallFunctionObjArgs(_exception, _exc_value, nullptr);
275  }
276 }*/
277 
278 /**
279  * Maps from an expression like "task.attr_name = v". This is customized here
280  * so we can support some traditional task interfaces that supported directly
281  * assigning certain values. We also support adding arbitrary data to the
282  * Task object.
283  */
284 int PythonTask::
285 __setattr__(PyObject *self, PyObject *attr, PyObject *v) {
286  if (PyObject_GenericSetAttr(self, attr, v) == 0) {
287  return 0;
288  }
289 
290  if (!PyErr_ExceptionMatches(PyExc_AttributeError)) {
291  return -1;
292  }
293 
294  PyErr_Clear();
295 
296  if (task_cat.is_debug()) {
297  PyObject *str = PyObject_Repr(v);
298  task_cat.debug()
299  << *this << ": task."
300 #if PY_MAJOR_VERSION >= 3
301  << PyUnicode_AsUTF8(attr) << " = "
302  << PyUnicode_AsUTF8(str) << "\n";
303 #else
304  << PyString_AsString(attr) << " = "
305  << PyString_AsString(str) << "\n";
306 #endif
307  Py_DECREF(str);
308  }
309 
310  return PyDict_SetItem(__dict__, attr, v);
311 }
312 
313 /**
314  * Maps from an expression like "del task.attr_name". This is customized here
315  * so we can support some traditional task interfaces that supported directly
316  * assigning certain values. We also support adding arbitrary data to the
317  * Task object.
318  */
319 int PythonTask::
320 __delattr__(PyObject *self, PyObject *attr) {
321  if (PyObject_GenericSetAttr(self, attr, nullptr) == 0) {
322  return 0;
323  }
324 
325  if (!PyErr_ExceptionMatches(PyExc_AttributeError)) {
326  return -1;
327  }
328 
329  PyErr_Clear();
330 
331  if (PyDict_DelItem(__dict__, attr) == -1) {
332  // PyDict_DelItem does not raise an exception.
333 #if PY_MAJOR_VERSION < 3
334  PyErr_Format(PyExc_AttributeError,
335  "'PythonTask' object has no attribute '%.400s'",
336  PyString_AS_STRING(attr));
337 #else
338  PyErr_Format(PyExc_AttributeError,
339  "'PythonTask' object has no attribute '%U'",
340  attr);
341 #endif
342  return -1;
343  }
344 
345  return 0;
346 }
347 
348 /**
349  * Maps from an expression like "task.attr_name". This is customized here so
350  * we can support some traditional task interfaces that supported directly
351  * querying certain values. We also support adding arbitrary data to the Task
352  * object.
353  */
354 PyObject *PythonTask::
355 __getattr__(PyObject *attr) const {
356  // Note that with the new Interrogate behavior, this method behaves more
357  // like the Python __getattr__ rather than being directly assigned to the
358  // tp_getattro slot (a la __getattribute__). So, we won't get here when the
359  // attribute has already been found via other methods.
360 
361  PyObject *item = PyDict_GetItem(__dict__, attr);
362 
363  if (item == nullptr) {
364  // PyDict_GetItem does not raise an exception.
365 #if PY_MAJOR_VERSION < 3
366  PyErr_Format(PyExc_AttributeError,
367  "'PythonTask' object has no attribute '%.400s'",
368  PyString_AS_STRING(attr));
369 #else
370  PyErr_Format(PyExc_AttributeError,
371  "'PythonTask' object has no attribute '%U'",
372  attr);
373 #endif
374  return nullptr;
375  }
376 
377  // PyDict_GetItem returns a borrowed reference.
378  Py_INCREF(item);
379  return item;
380 }
381 
382 /**
383  * Called by Python to implement cycle detection.
384  */
385 int PythonTask::
386 __traverse__(visitproc visit, void *arg) {
387 /*
388  Py_VISIT(_function);
389  Py_VISIT(_args);
390  Py_VISIT(_upon_death);
391  Py_VISIT(_owner);
392  Py_VISIT(__dict__);
393  Py_VISIT(_generator);
394 */
395  return 0;
396 }
397 
398 /**
399  * Called by Python to implement cycle breaking.
400  */
401 int PythonTask::
402 __clear__() {
403 /*
404  Py_CLEAR(_function);
405  Py_CLEAR(_args);
406  Py_CLEAR(_upon_death);
407  Py_CLEAR(_owner);
408  Py_CLEAR(__dict__);
409  Py_CLEAR(_generator);
410 */
411  return 0;
412 }
413 
414 /**
415  * Override this function to return true if the task can be successfully
416  * executed, false if it cannot. Mainly intended as a sanity check when
417  * attempting to add the task to a task manager.
418  *
419  * This function is called with the lock held.
420  */
421 bool PythonTask::
422 is_runnable() {
423  return _function != Py_None;
424 }
425 
426 /**
427  * Override this function to do something useful for the task.
428  *
429  * This function is called with the lock *not* held.
430  */
431 AsyncTask::DoneStatus PythonTask::
432 do_task() {
433 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
434  // Use PyGILState to protect this asynchronous call.
435  PyGILState_STATE gstate;
436  gstate = PyGILState_Ensure();
437 #endif
438 
439  DoneStatus result = do_python_task();
440 
441 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
442  PyGILState_Release(gstate);
443 #endif
444 
445  return result;
446 }
447 
448 /**
449  * The Python calls that implement do_task(). This function is separate so we
450  * can acquire the Python interpretor lock while it runs.
451  */
452 AsyncTask::DoneStatus PythonTask::
453 do_python_task() {
454  PyObject *result = nullptr;
455 
456  // Are we waiting for a future to finish?
457  if (_future_done != nullptr) {
458  PyObject *is_done = PyObject_CallObject(_future_done, nullptr);
459  if (!PyObject_IsTrue(is_done)) {
460  // Nope, ask again next frame.
461  Py_DECREF(is_done);
462  return DS_cont;
463  }
464  Py_DECREF(is_done);
465  Py_DECREF(_future_done);
466  _future_done = nullptr;
467  }
468 
469  if (_generator == nullptr) {
470  // We are calling the function directly.
471  nassertr(_function != nullptr, DS_interrupt);
472 
473  PyObject *args = get_args();
474  result = PythonThread::call_python_func(_function, args);
475  Py_DECREF(args);
476 
477  if (result != nullptr && PyGen_Check(result)) {
478  // The function has yielded a generator. We will call into that
479  // henceforth, instead of calling the function from the top again.
480  if (task_cat.is_debug()) {
481 #if PY_MAJOR_VERSION >= 3
482  PyObject *str = PyObject_ASCII(_function);
483  task_cat.debug()
484  << PyUnicode_AsUTF8(str) << " in " << *this
485  << " yielded a generator.\n";
486 #else
487  PyObject *str = PyObject_Repr(_function);
488  task_cat.debug()
489  << PyString_AsString(str) << " in " << *this
490  << " yielded a generator.\n";
491 #endif
492  Py_DECREF(str);
493  }
494  _generator = result;
495  result = nullptr;
496 
497 #if PY_VERSION_HEX >= 0x03050000
498  } else if (result != nullptr && Py_TYPE(result)->tp_as_async != nullptr) {
499  // The function yielded a coroutine, or something of the sort.
500  if (task_cat.is_debug()) {
501  PyObject *str = PyObject_ASCII(_function);
502  PyObject *str2 = PyObject_ASCII(result);
503  task_cat.debug()
504  << PyUnicode_AsUTF8(str) << " in " << *this
505  << " yielded an awaitable: " << PyUnicode_AsUTF8(str2) << "\n";
506  Py_DECREF(str);
507  Py_DECREF(str2);
508  }
509  if (PyCoro_CheckExact(result)) {
510  // If a coroutine, am_await is possible but senseless, since we can
511  // just call send(None) on the coroutine itself.
512  _generator = result;
513  } else {
514  unaryfunc await = Py_TYPE(result)->tp_as_async->am_await;
515  _generator = await(result);
516  Py_DECREF(result);
517  }
518  result = nullptr;
519 #endif
520  }
521  }
522 
523  if (_generator != nullptr) {
524  // We are calling a generator. Use "send" rather than PyIter_Next since
525  // we need to be able to read the value from a StopIteration exception.
526  PyObject *func = PyObject_GetAttrString(_generator, "send");
527  nassertr(func != nullptr, DS_interrupt);
528  result = PyObject_CallFunctionObjArgs(func, Py_None, nullptr);
529  Py_DECREF(func);
530 
531  if (result == nullptr) {
532  // An error happened. If StopIteration, that indicates the task has
533  // returned. Otherwise, we need to save it so that it can be re-raised
534  // in the function that awaited this task.
535  Py_DECREF(_generator);
536  _generator = nullptr;
537 
538 #if PY_VERSION_HEX >= 0x03030000
539  if (_PyGen_FetchStopIterationValue(&result) == 0) {
540 #else
541  if (PyErr_ExceptionMatches(PyExc_StopIteration)) {
542  result = Py_None;
543  Py_INCREF(result);
544 #endif
545  PyErr_Restore(nullptr, nullptr, nullptr);
546 
547  // If we passed a coroutine into the task, eg. something like:
548  // taskMgr.add(my_async_function())
549  // then we cannot rerun the task, so the return value is always
550  // assumed to be DS_done. Instead, we pass the return value to the
551  // result of the `await` expression.
552  if (_function == nullptr) {
553  if (task_cat.is_debug()) {
554  task_cat.debug()
555  << *this << " received StopIteration from coroutine.\n";
556  }
557  // Store the result in _exc_value because that's not used anyway.
558  Py_XDECREF(_exc_value);
559  _exc_value = result;
560  return DS_done;
561  }
562  } else if (_function == nullptr) {
563  // We got an exception. If this is a scheduled coroutine, we will
564  // keep it and instead throw it into whatever 'awaits' this task.
565  // Otherwise, fall through and handle it the regular way.
566  Py_XDECREF(_exception);
567  Py_XDECREF(_exc_value);
568  Py_XDECREF(_exc_traceback);
569  PyErr_Fetch(&_exception, &_exc_value, &_exc_traceback);
570  _retrieved_exception = false;
571 
572  if (task_cat.is_debug()) {
573  if (_exception != nullptr && Py_TYPE(_exception) == &PyType_Type) {
574  task_cat.debug()
575  << *this << " received " << ((PyTypeObject *)_exception)->tp_name << " from coroutine.\n";
576  } else {
577  task_cat.debug()
578  << *this << " received exception from coroutine.\n";
579  }
580  }
581 
582  // Tell the task chain we want to kill ourselves. We indicate this is
583  // a "clean exit" because we still want to run the done callbacks on
584  // exception.
585  return DS_done;
586  }
587 
588  } else if (DtoolInstance_Check(result)) {
589  // We are waiting for an AsyncFuture (eg. other task) to finish.
590  AsyncFuture *fut = (AsyncFuture *)DtoolInstance_UPCAST(result, Dtool_AsyncFuture);
591  if (fut != nullptr) {
592  // Suspend execution of this task until this other task has completed.
593  if (fut != (AsyncFuture *)this && !fut->done()) {
594  if (fut->is_task()) {
595  // This is actually a task, do we need to schedule it with the
596  // manager? This allows doing something like
597  // await Task.pause(1.0)
598  // directly instead of having to do:
599  // await taskMgr.add(Task.pause(1.0))
600  AsyncTask *task = (AsyncTask *)fut;
601  if (!task->is_alive()) {
602  _manager->add(task);
603  }
604  }
605  if (fut->add_waiting_task(this)) {
606  if (task_cat.is_debug()) {
607  task_cat.debug()
608  << *this << " is now awaiting <" << *fut << ">.\n";
609  }
610  } else {
611  // The task is already done. Continue at next opportunity.
612  if (task_cat.is_debug()) {
613  task_cat.debug()
614  << *this << " would await <" << *fut << ">, were it not already done.\n";
615  }
616  Py_DECREF(result);
617  return DS_cont;
618  }
619  } else {
620  // This is an error. If we wanted to be fancier we could also
621  // detect deeper circular dependencies.
622  task_cat.error()
623  << *this << " cannot await itself\n";
624  }
625  Py_DECREF(result);
626  return DS_await;
627  }
628  } else {
629  // We are waiting for a non-Panda future to finish. We currently
630  // implement this by checking every frame whether the future is done.
631  PyObject *check = PyObject_GetAttrString(result, "_asyncio_future_blocking");
632  if (check != nullptr && check != Py_None) {
633  Py_DECREF(check);
634  // Next frame, check whether this future is done.
635  _future_done = PyObject_GetAttrString(result, "done");
636  if (_future_done == nullptr || !PyCallable_Check(_future_done)) {
637  task_cat.error()
638  << "future.done is not callable\n";
639  return DS_interrupt;
640  }
641 #if PY_MAJOR_VERSION >= 3
642  if (task_cat.is_debug()) {
643  PyObject *str = PyObject_ASCII(result);
644  task_cat.debug()
645  << *this << " is now polling " << PyUnicode_AsUTF8(str) << ".done()\n";
646  Py_DECREF(str);
647  }
648 #endif
649  Py_DECREF(result);
650  return DS_cont;
651  }
652  PyErr_Clear();
653  Py_XDECREF(check);
654  }
655  }
656 
657  if (result == nullptr) {
658  if (PyErr_Occurred() && PyErr_ExceptionMatches(PyExc_SystemExit)) {
659  // Don't print an error message for SystemExit. Or rather, make it a
660  // debug message.
661  if (task_cat.is_debug()) {
662  task_cat.debug()
663  << "SystemExit occurred in " << *this << "\n";
664  }
665  } else {
666  task_cat.error()
667  << "Exception occurred in " << *this << "\n";
668  }
669  return DS_interrupt;
670  }
671 
672  if (result == Py_None || _ignore_return) {
673  Py_DECREF(result);
674  return DS_done;
675  }
676 
677 #if PY_MAJOR_VERSION >= 3
678  if (PyLong_Check(result)) {
679  long retval = PyLong_AS_LONG(result);
680 #else
681  if (PyInt_Check(result)) {
682  long retval = PyInt_AS_LONG(result);
683 #endif
684 
685  switch (retval) {
686  case DS_again:
687  Py_XDECREF(_generator);
688  _generator = nullptr;
689  // Fall through.
690 
691  case DS_done:
692  case DS_cont:
693  case DS_pickup:
694  case DS_exit:
695  case DS_pause:
696  // Legitimate value.
697  Py_DECREF(result);
698  return (DoneStatus) retval;
699 
700  case -1:
701  // Legacy value.
702  Py_DECREF(result);
703  return DS_done;
704 
705  default:
706  // Unexpected value.
707  break;
708  }
709  }
710 
711  // This is unfortunate, but some are returning task.done, which nowadays
712  // conflicts with the AsyncFuture method. Check if that is being returned.
713  PyMethodDef *meth = nullptr;
714  if (PyCFunction_Check(result)) {
715  meth = ((PyCFunctionObject *)result)->m_ml;
716 #if PY_MAJOR_VERSION >= 3
717  } else if (Py_TYPE(result) == &PyMethodDescr_Type) {
718 #else
719  } else if (strcmp(Py_TYPE(result)->tp_name, "method_descriptor") == 0) {
720 #endif
721  meth = ((PyMethodDescrObject *)result)->d_method;
722  }
723 
724  if (meth != nullptr && strcmp(meth->ml_name, "done") == 0) {
725  Py_DECREF(result);
726  return DS_done;
727  }
728 
729  std::ostringstream strm;
730 #if PY_MAJOR_VERSION >= 3
731  PyObject *str = PyObject_ASCII(result);
732  if (str == nullptr) {
733  str = PyUnicode_FromString("<repr error>");
734  }
735  strm
736  << *this << " returned " << PyUnicode_AsUTF8(str);
737 #else
738  PyObject *str = PyObject_Repr(result);
739  if (str == nullptr) {
740  str = PyString_FromString("<repr error>");
741  }
742  strm
743  << *this << " returned " << PyString_AsString(str);
744 #endif
745  Py_DECREF(str);
746  Py_DECREF(result);
747  std::string message = strm.str();
748  nassert_raise(message);
749 
750  return DS_interrupt;
751 }
752 
753 /**
754  * Override this function to do something useful when the task has been added
755  * to the active queue.
756  *
757  * This function is called with the lock *not* held.
758  */
759 void PythonTask::
760 upon_birth(AsyncTaskManager *manager) {
761  AsyncTask::upon_birth(manager);
762  register_to_owner();
763 }
764 
765 /**
766  * Override this function to do something useful when the task has been
767  * removed from the active queue. The parameter clean_exit is true if the
768  * task has been removed because it exited normally (returning DS_done), or
769  * false if it was removed for some other reason (e.g.
770  * AsyncTaskManager::remove()). By the time this method is called, _manager
771  * has been cleared, so the parameter manager indicates the original
772  * AsyncTaskManager that owned this task.
773  *
774  * The normal behavior is to throw the done_event only if clean_exit is true.
775  *
776  * This function is called with the lock *not* held.
777  */
778 void PythonTask::
779 upon_death(AsyncTaskManager *manager, bool clean_exit) {
780  AsyncTask::upon_death(manager, clean_exit);
781 
782  if (_upon_death != Py_None) {
783 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
784  // Use PyGILState to protect this asynchronous call.
785  PyGILState_STATE gstate;
786  gstate = PyGILState_Ensure();
787 #endif
788 
789  call_function(_upon_death);
790 
791 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
792  PyGILState_Release(gstate);
793 #endif
794  }
795  unregister_from_owner();
796 }
797 
798 /**
799  * Tells the owner we are now his task.
800  */
801 void PythonTask::
802 register_to_owner() {
803  if (_owner != Py_None && !_registered_to_owner) {
804 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
805  // Use PyGILState to protect this asynchronous call.
806  PyGILState_STATE gstate;
807  gstate = PyGILState_Ensure();
808 #endif
809 
810  _registered_to_owner = true;
811  call_owner_method("_addTask");
812 
813 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
814  PyGILState_Release(gstate);
815 #endif
816  }
817 }
818 
819 /**
820  * Tells the owner we are no longer his task.
821  */
822 void PythonTask::
823 unregister_from_owner() {
824  // make sure every call to _clearTask corresponds to a call to _addTask
825  if (_owner != Py_None && _registered_to_owner) {
826 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
827  // Use PyGILState to protect this asynchronous call.
828  PyGILState_STATE gstate;
829  gstate = PyGILState_Ensure();
830 #endif
831 
832  _registered_to_owner = false;
833  call_owner_method("_clearTask");
834 
835 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
836  PyGILState_Release(gstate);
837 #endif
838  }
839 }
840 
841 /**
842  * Calls the indicated method name on the given object, if defined, passing in
843  * the task object as the only parameter.
844  */
845 void PythonTask::
846 call_owner_method(const char *method_name) {
847  if (_owner != Py_None) {
848  PyObject *func = PyObject_GetAttrString(_owner, (char *)method_name);
849  if (func == nullptr) {
850  task_cat.error()
851  << "Owner object added to " << *this << " has no method "
852  << method_name << "().\n";
853 
854  } else {
855  call_function(func);
856  Py_DECREF(func);
857  }
858  }
859 }
860 
861 /**
862  * Calls the indicated Python function, passing in the task object as the only
863  * parameter.
864  */
865 void PythonTask::
866 call_function(PyObject *function) {
867  if (function != Py_None) {
868  this->ref();
869  PyObject *self = DTool_CreatePyInstance(this, Dtool_PythonTask, true, false);
870  PyObject *result = PyObject_CallFunctionObjArgs(function, self, nullptr);
871  Py_XDECREF(result);
872  Py_DECREF(self);
873  }
874 }
875 
876 #endif // HAVE_PYTHON
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This class represents a thread-safe handle to a promised future result of an asynchronous operation,...
Definition: asyncFuture.h:61
A class to manage a loose queue of isolated tasks, which can be performed either synchronously (in th...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
is_alive
Returns true if the task is currently active or sleeping on some task chain, meaning that it will be ...
Definition: asyncTask.h:104
bool add_waiting_task(AsyncTask *task)
Indicates that the given task is waiting for this future to complete.
bool done() const
Returns true if the future is done or has been cancelled.
Definition: asyncFuture.I:29
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PyObject * DTool_CreatePyInstance(const T *obj, bool memory_rules)
These functions wrap a pointer for a class that defines get_type_handle().
Definition: py_panda.I:111
This class represents a concrete task performed by an AsyncManager.
Definition: asyncTask.h:32
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81