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