Panda3D
Loading...
Searching...
No Matches
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 >= 0x030D0000 // Python 3.13
549 // Python 3.13 does not support _PyGen_FetchStopIterationValue anymore.
550 if (PyErr_ExceptionMatches(PyExc_StopIteration)) {
551 PyObject *exc = PyErr_GetRaisedException();
552 result = ((PyStopIterationObject *)exc)->value;
553 if (result == nullptr) {
554 result = Py_None;
555 }
556 Py_INCREF(result);
557 Py_DECREF(exc);
558#elif PY_VERSION_HEX >= 0x03030000
559 if (_PyGen_FetchStopIterationValue(&result) == 0) {
560#else
561 if (PyErr_ExceptionMatches(PyExc_StopIteration)) {
562 result = Py_None;
563 Py_INCREF(result);
564#endif
565 PyErr_Restore(nullptr, nullptr, nullptr);
566
567 // If we passed a coroutine into the task, eg. something like:
568 // taskMgr.add(my_async_function())
569 // then we cannot rerun the task, so the return value is always
570 // assumed to be DS_done. Instead, we pass the return value to the
571 // result of the `await` expression.
572 if (_function == nullptr) {
573 if (task_cat.is_debug()) {
574 task_cat.debug()
575 << *this << " received StopIteration from coroutine.\n";
576 }
577 // Store the result in _exc_value because that's not used anyway.
578 Py_XDECREF(_exc_value);
579 _exc_value = result;
580 return DS_done;
581 }
582 } else if (_function == nullptr) {
583 // We got an exception. If this is a scheduled coroutine, we will
584 // keep it and instead throw it into whatever 'awaits' this task.
585 // Otherwise, fall through and handle it the regular way.
586 Py_XDECREF(_exception);
587 Py_XDECREF(_exc_value);
588 Py_XDECREF(_exc_traceback);
589 PyErr_Fetch(&_exception, &_exc_value, &_exc_traceback);
590 _retrieved_exception = false;
591
592 if (task_cat.is_debug()) {
593 if (_exception != nullptr && Py_TYPE(_exception) == &PyType_Type) {
594 task_cat.debug()
595 << *this << " received " << ((PyTypeObject *)_exception)->tp_name << " from coroutine.\n";
596 } else {
597 task_cat.debug()
598 << *this << " received exception from coroutine.\n";
599 }
600 }
601
602 // Tell the task chain we want to kill ourselves. We indicate this is
603 // a "clean exit" because we still want to run the done callbacks on
604 // exception.
605 return DS_done;
606 }
607
608#if PY_VERSION_HEX >= 0x03050000
609 } else if (result == Py_None && PyCoro_CheckExact(_generator)) {
610 // Bare yield from a coroutine means to try again next frame.
611 Py_DECREF(result);
612 return DS_cont;
613#endif
614
615 } else if (DtoolInstance_Check(result)) {
616 // We are waiting for an AsyncFuture (eg. other task) to finish.
617 AsyncFuture *fut = (AsyncFuture *)DtoolInstance_UPCAST(result, Dtool_AsyncFuture);
618 if (fut != nullptr) {
619 // Suspend execution of this task until this other task has completed.
620 if (fut != (AsyncFuture *)this && !fut->done()) {
621 if (fut->is_task()) {
622 // This is actually a task, do we need to schedule it with the
623 // manager? This allows doing something like
624 // await Task.pause(1.0)
625 // directly instead of having to do:
626 // await taskMgr.add(Task.pause(1.0))
627 AsyncTask *task = (AsyncTask *)fut;
628 if (!task->is_alive()) {
629 _manager->add(task);
630 }
631 }
632 if (fut->add_waiting_task(this)) {
633 if (task_cat.is_debug()) {
634 task_cat.debug()
635 << *this << " is now awaiting <" << *fut << ">.\n";
636 }
637 } else {
638 // The task is already done. Continue at next opportunity.
639 if (task_cat.is_debug()) {
640 task_cat.debug()
641 << *this << " would await <" << *fut << ">, were it not already done.\n";
642 }
643 Py_DECREF(result);
644 return DS_cont;
645 }
646 } else {
647 // This is an error. If we wanted to be fancier we could also
648 // detect deeper circular dependencies.
649 task_cat.error()
650 << *this << " cannot await itself\n";
651 }
652 Py_DECREF(result);
653 return DS_await;
654 }
655 } else {
656 // We are waiting for a non-Panda future to finish. We currently
657 // implement this by checking every frame whether the future is done.
658 PyObject *check = PyObject_GetAttrString(result, "_asyncio_future_blocking");
659 if (check != nullptr && check != Py_None) {
660 Py_DECREF(check);
661 // Next frame, check whether this future is done.
662 _future_done = PyObject_GetAttrString(result, "done");
663 if (_future_done == nullptr || !PyCallable_Check(_future_done)) {
664 task_cat.error()
665 << "future.done is not callable\n";
666 return DS_interrupt;
667 }
668#if PY_MAJOR_VERSION >= 3
669 if (task_cat.is_debug()) {
670 PyObject *str = PyObject_ASCII(result);
671 task_cat.debug()
672 << *this << " is now polling " << PyUnicode_AsUTF8(str) << ".done()\n";
673 Py_DECREF(str);
674 }
675#endif
676 Py_DECREF(result);
677 return DS_cont;
678 }
679 PyErr_Clear();
680 Py_XDECREF(check);
681 }
682 }
683
684 if (result == nullptr) {
685 if (PyErr_Occurred() && PyErr_ExceptionMatches(PyExc_SystemExit)) {
686 // Don't print an error message for SystemExit. Or rather, make it a
687 // debug message.
688 if (task_cat.is_debug()) {
689 task_cat.debug()
690 << "SystemExit occurred in " << *this << "\n";
691 }
692 } else {
693 task_cat.error()
694 << "Exception occurred in " << *this << "\n";
695 }
696 return DS_interrupt;
697 }
698
699 if (result == Py_None || _ignore_return) {
700 Py_DECREF(result);
701 return DS_done;
702 }
703
704#if PY_MAJOR_VERSION >= 3
705 if (PyLong_Check(result)) {
706 long retval = PyLong_AS_LONG(result);
707#else
708 if (PyInt_Check(result)) {
709 long retval = PyInt_AS_LONG(result);
710#endif
711
712 switch (retval) {
713 case DS_again:
714 Py_XDECREF(_generator);
715 _generator = nullptr;
716 // Fall through.
717
718 case DS_done:
719 case DS_cont:
720 case DS_pickup:
721 case DS_exit:
722 case DS_pause:
723 // Legitimate value.
724 Py_DECREF(result);
725 return (DoneStatus) retval;
726
727 case -1:
728 // Legacy value.
729 Py_DECREF(result);
730 return DS_done;
731
732 default:
733 // Unexpected value.
734 break;
735 }
736 }
737
738 // This is unfortunate, but some are returning task.done, which nowadays
739 // conflicts with the AsyncFuture method. Check if that is being returned.
740 PyMethodDef *meth = nullptr;
741 if (PyCFunction_Check(result)) {
742 meth = ((PyCFunctionObject *)result)->m_ml;
743#if PY_MAJOR_VERSION >= 3
744 } else if (Py_TYPE(result) == &PyMethodDescr_Type) {
745#else
746 } else if (strcmp(Py_TYPE(result)->tp_name, "method_descriptor") == 0) {
747#endif
748 meth = ((PyMethodDescrObject *)result)->d_method;
749 }
750
751 if (meth != nullptr && strcmp(meth->ml_name, "done") == 0) {
752 Py_DECREF(result);
753 return DS_done;
754 }
755
756 std::ostringstream strm;
757#if PY_MAJOR_VERSION >= 3
758 PyObject *str = PyObject_ASCII(result);
759 if (str == nullptr) {
760 str = PyUnicode_FromString("<repr error>");
761 }
762 strm
763 << *this << " returned " << PyUnicode_AsUTF8(str);
764#else
765 PyObject *str = PyObject_Repr(result);
766 if (str == nullptr) {
767 str = PyString_FromString("<repr error>");
768 }
769 strm
770 << *this << " returned " << PyString_AsString(str);
771#endif
772 Py_DECREF(str);
773 Py_DECREF(result);
774 std::string message = strm.str();
775 nassert_raise(message);
776
777 return DS_interrupt;
778}
779
780/**
781 * Override this function to do something useful when the task has been added
782 * to the active queue.
783 *
784 * This function is called with the lock *not* held.
785 */
786void PythonTask::
787upon_birth(AsyncTaskManager *manager) {
788 AsyncTask::upon_birth(manager);
789 register_to_owner();
790}
791
792/**
793 * Override this function to do something useful when the task has been
794 * removed from the active queue. The parameter clean_exit is true if the
795 * task has been removed because it exited normally (returning DS_done), or
796 * false if it was removed for some other reason (e.g.
797 * AsyncTaskManager::remove()). By the time this method is called, _manager
798 * has been cleared, so the parameter manager indicates the original
799 * AsyncTaskManager that owned this task.
800 *
801 * The normal behavior is to throw the done_event only if clean_exit is true.
802 *
803 * This function is called with the lock *not* held.
804 */
805void PythonTask::
806upon_death(AsyncTaskManager *manager, bool clean_exit) {
807 AsyncTask::upon_death(manager, clean_exit);
808
809 // If we were polling something when we were removed, get rid of it.
810 if (_future_done != nullptr) {
811 Py_DECREF(_future_done);
812 _future_done = nullptr;
813 }
814
815 if (_upon_death != Py_None) {
816#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
817 // Use PyGILState to protect this asynchronous call.
818 PyGILState_STATE gstate;
819 gstate = PyGILState_Ensure();
820#endif
821
822 call_function(_upon_death);
823
824#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
825 PyGILState_Release(gstate);
826#endif
827 }
828 unregister_from_owner();
829}
830
831/**
832 * Tells the owner we are now his task.
833 */
834void PythonTask::
835register_to_owner() {
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 = true;
844 call_owner_method("_addTask");
845
846#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
847 PyGILState_Release(gstate);
848#endif
849 }
850}
851
852/**
853 * Tells the owner we are no longer his task.
854 */
855void PythonTask::
856unregister_from_owner() {
857 // make sure every call to _clearTask corresponds to a call to _addTask
858 if (_owner != Py_None && _registered_to_owner) {
859#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
860 // Use PyGILState to protect this asynchronous call.
861 PyGILState_STATE gstate;
862 gstate = PyGILState_Ensure();
863#endif
864
865 _registered_to_owner = false;
866 call_owner_method("_clearTask");
867
868#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
869 PyGILState_Release(gstate);
870#endif
871 }
872}
873
874/**
875 * Calls the indicated method name on the given object, if defined, passing in
876 * the task object as the only parameter.
877 */
878void PythonTask::
879call_owner_method(const char *method_name) {
880 if (_owner != Py_None) {
881 PyObject *func = PyObject_GetAttrString(_owner, (char *)method_name);
882 if (func == nullptr) {
883 task_cat.error()
884 << "Owner object added to " << *this << " has no method "
885 << method_name << "().\n";
886
887 } else {
888 call_function(func);
889 Py_DECREF(func);
890 }
891 }
892}
893
894/**
895 * Calls the indicated Python function, passing in the task object as the only
896 * parameter.
897 */
898void PythonTask::
899call_function(PyObject *function) {
900 if (function != Py_None) {
901 this->ref();
902 PyObject *self = DTool_CreatePyInstance(this, Dtool_PythonTask, true, false);
903 PyObject *result = PyObject_CallFunctionObjArgs(function, self, nullptr);
904 Py_XDECREF(result);
905 Py_DECREF(self);
906 }
907}
908
909#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.