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
24TypeHandle PythonTask::_type_handle;
25
26#ifndef CPPPARSER
27extern struct Dtool_PyTypedObject Dtool_TypedReferenceCount;
28extern struct Dtool_PyTypedObject Dtool_AsyncFuture;
29extern struct Dtool_PyTypedObject Dtool_PythonTask;
30#endif
31
32/**
33 *
34 */
35PythonTask::
36PythonTask(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 */
86PythonTask::
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 */
116void PythonTask::
117set_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 */
132void PythonTask::
133set_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 */
157PyObject *PythonTask::
158get_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 */
188void PythonTask::
189set_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 */
207void PythonTask::
208set_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 */
240PyObject *PythonTask::
241get_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::
263exception() 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 */
282int 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 */
317int 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 */
352PyObject *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 */
383int 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 */
399int 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 */
419bool PythonTask::
420is_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 */
429AsyncTask::DoneStatus PythonTask::
430do_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 */
450AsyncTask::DoneStatus PythonTask::
451do_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 */
764void PythonTask::
765upon_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 */
783void PythonTask::
784upon_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 */
812void PythonTask::
813register_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 */
833void PythonTask::
834unregister_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 */
856void PythonTask::
857call_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 */
876void PythonTask::
877call_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.