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