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