| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977 |
- /**
- * PANDA 3D SOFTWARE
- * Copyright (c) Carnegie Mellon University. All rights reserved.
- *
- * All use of this software is subject to the terms of the revised BSD
- * license. You should have received a copy of this license along
- * with this source code in a file named "LICENSE."
- *
- * @file pythonTask.cxx
- * @author drose
- * @date 2008-09-16
- */
- #include "pythonTask.h"
- #include "pnotify.h"
- #include "config_event.h"
- #ifdef HAVE_PYTHON
- #include "py_panda.h"
- #include "pythonThread.h"
- #include "asyncTaskManager.h"
- #include "asyncFuture_ext.h"
- TypeHandle PythonTask::_type_handle;
- #ifndef CPPPARSER
- extern struct Dtool_PyTypedObject Dtool_TypedReferenceCount;
- extern struct Dtool_PyTypedObject Dtool_AsyncFuture;
- extern struct Dtool_PyTypedObject Dtool_PythonTask;
- #endif
- /**
- *
- */
- PythonTask::
- PythonTask(PyObject *func_or_coro, const std::string &name) :
- AsyncTask(name),
- _function(nullptr),
- _args(nullptr),
- _upon_death(nullptr),
- _owner(nullptr),
- _exception(nullptr),
- _exc_value(nullptr),
- _exc_traceback(nullptr),
- _generator(nullptr),
- _fut_waiter(nullptr),
- _ignore_return(false),
- _registered_to_owner(false),
- _retrieved_exception(false) {
- nassertv(func_or_coro != nullptr);
- if (func_or_coro == Py_None || PyCallable_Check(func_or_coro)) {
- _function = func_or_coro;
- Py_INCREF(_function);
- } else if (PyCoro_CheckExact(func_or_coro)) {
- // We also allow passing in a coroutine, because why not.
- _generator = func_or_coro;
- Py_INCREF(_generator);
- } else if (PyGen_CheckExact(func_or_coro)) {
- // Something emulating a coroutine.
- _generator = func_or_coro;
- Py_INCREF(_generator);
- } else {
- nassert_raise("Invalid function passed to PythonTask");
- }
- set_args(Py_None, true);
- set_upon_death(Py_None);
- set_owner(Py_None);
- __dict__ = PyDict_New();
- #if !defined(SIMPLE_THREADS) && defined(WITH_THREAD) && PY_VERSION_HEX < 0x03090000
- // Ensure that the Python threading system is initialized and ready to go.
- // WITH_THREAD symbol defined within Python.h
- // PyEval_InitThreads is now a deprecated no-op in Python 3.9+
- PyEval_InitThreads();
- #endif
- }
- /**
- *
- */
- PythonTask::
- ~PythonTask() {
- // If the coroutine threw an exception, and there was no opportunity to
- // handle it, let the user know.
- if (_exception != nullptr && !_retrieved_exception) {
- task_cat.error()
- << *this << " exception was never retrieved:\n";
- PyErr_Restore(_exception, _exc_value, _exc_traceback);
- PyErr_Print();
- PyErr_Restore(nullptr, nullptr, nullptr);
- _exception = nullptr;
- _exc_value = nullptr;
- _exc_traceback = nullptr;
- }
- Py_XDECREF(_function);
- Py_DECREF(_args);
- Py_DECREF(__dict__);
- Py_XDECREF(_exception);
- Py_XDECREF(_exc_value);
- Py_XDECREF(_exc_traceback);
- Py_XDECREF(_generator);
- Py_XDECREF(_owner);
- Py_XDECREF(_upon_death);
- }
- /**
- * Replaces the function that is called when the task runs. The parameter
- * should be a Python callable object.
- */
- void PythonTask::
- set_function(PyObject *function) {
- Py_XDECREF(_function);
- _function = function;
- Py_INCREF(_function);
- if (_function != Py_None && !PyCallable_Check(_function)) {
- nassert_raise("Invalid function passed to PythonTask");
- }
- }
- /**
- * Replaces the argument list that is passed to the task function. The
- * parameter should be a tuple or list of arguments, or None to indicate the
- * empty list.
- */
- void PythonTask::
- set_args(PyObject *args, bool append_task) {
- Py_XDECREF(_args);
- _args = nullptr;
- if (args == Py_None) {
- // None means no arguments; create an empty tuple.
- _args = PyTuple_New(0);
- } else {
- if (PySequence_Check(args)) {
- _args = PySequence_Tuple(args);
- }
- }
- if (_args == nullptr) {
- nassert_raise("Invalid args passed to PythonTask");
- _args = PyTuple_New(0);
- }
- _append_task = append_task;
- }
- /**
- * Returns the argument list that is passed to the task function.
- */
- PyObject *PythonTask::
- get_args() {
- if (_append_task) {
- // If we want to append the task, we have to create a new tuple with space
- // for one more at the end. We have to do this dynamically each time, to
- // avoid storing the task itself in its own arguments list, and thereby
- // creating a cyclical reference.
- int num_args = PyTuple_GET_SIZE(_args);
- PyObject *with_task = PyTuple_New(num_args + 1);
- for (int i = 0; i < num_args; ++i) {
- PyObject *item = PyTuple_GET_ITEM(_args, i);
- Py_INCREF(item);
- PyTuple_SET_ITEM(with_task, i, item);
- }
- this->ref();
- PyObject *self = DTool_CreatePyInstance(this, Dtool_PythonTask, true, false);
- PyTuple_SET_ITEM(with_task, num_args, self);
- return with_task;
- } else {
- Py_INCREF(_args);
- return _args;
- }
- }
- /**
- * Replaces the function that is called when the task finishes. The parameter
- * should be a Python callable object.
- */
- void PythonTask::
- set_upon_death(PyObject *upon_death) {
- Py_XDECREF(_upon_death);
- _upon_death = upon_death;
- Py_INCREF(_upon_death);
- if (_upon_death != Py_None && !PyCallable_Check(_upon_death)) {
- nassert_raise("Invalid upon_death function passed to PythonTask");
- }
- }
- /**
- * Specifies a Python object that serves as the "owner" for the task. This
- * owner object must have two methods: _addTask() and _clearTask(), which will
- * be called with one parameter, the task object.
- *
- * owner._addTask() is called when the task is added into the active task
- * list, and owner._clearTask() is called when it is removed.
- */
- void PythonTask::
- set_owner(PyObject *owner) {
- #ifndef NDEBUG
- if (owner != Py_None) {
- PyObject *add = PyObject_GetAttrString(owner, "_addTask");
- PyErr_Clear();
- PyObject *clear = PyObject_GetAttrString(owner, "_clearTask");
- PyErr_Clear();
- bool valid_add = false;
- if (add != nullptr) {
- valid_add = PyCallable_Check(add);
- Py_DECREF(add);
- }
- bool valid_clear = false;
- if (clear != nullptr) {
- valid_clear = PyCallable_Check(clear);
- Py_DECREF(clear);
- }
- if (!valid_add || !valid_clear) {
- Dtool_Raise_TypeError("owner object should have _addTask and _clearTask methods");
- return;
- }
- }
- #endif
- if (_owner != nullptr && _owner != Py_None && _state != S_inactive) {
- unregister_from_owner();
- }
- Py_XDECREF(_owner);
- _owner = owner;
- Py_INCREF(_owner);
- if (_owner != Py_None && _state != S_inactive) {
- register_to_owner();
- }
- }
- /**
- * Returns the result of this task's execution, as set by set_result() within
- * the task or returned from a coroutine added to the task manager. If an
- * exception occurred within this task, it is raised instead.
- */
- PyObject *PythonTask::
- get_result() const {
- nassertr(done(), nullptr);
- if (_exception == nullptr) {
- // The result of the call is stored in _exc_value.
- Py_XINCREF(_exc_value);
- return _exc_value;
- } else {
- _retrieved_exception = true;
- Py_INCREF(_exception);
- Py_XINCREF(_exc_value);
- Py_XINCREF(_exc_traceback);
- PyErr_Restore(_exception, _exc_value, _exc_traceback);
- return nullptr;
- }
- }
- /**
- * If an exception occurred during execution of this task, returns it. This
- * is only set if this task returned a coroutine or generator.
- */
- /*PyObject *PythonTask::
- exception() const {
- if (_exception == nullptr) {
- Py_INCREF(Py_None);
- return Py_None;
- } else if (_exc_value == nullptr || _exc_value == Py_None) {
- return PyObject_CallNoArgs(_exception);
- } else if (PyTuple_Check(_exc_value)) {
- return PyObject_Call(_exception, _exc_value, nullptr);
- } else {
- return PyObject_CallOneArg(_exception, _exc_value);
- }
- }*/
- /**
- * Maps from an expression like "task.attr_name = v". This is customized here
- * so we can support some traditional task interfaces that supported directly
- * assigning certain values. We also support adding arbitrary data to the
- * Task object.
- */
- int PythonTask::
- __setattr__(PyObject *self, PyObject *attr, PyObject *v) {
- if (!PyUnicode_Check(attr)) {
- PyErr_Format(PyExc_TypeError,
- "attribute name must be string, not '%.200s'",
- attr->ob_type->tp_name);
- return -1;
- }
- PyObject *descr = _PyType_Lookup(Py_TYPE(self), attr);
- if (descr != nullptr) {
- Py_INCREF(descr);
- descrsetfunc f = descr->ob_type->tp_descr_set;
- if (f != nullptr) {
- return f(descr, self, v);
- }
- }
- if (task_cat.is_debug()) {
- PyObject *str = PyObject_Repr(v);
- task_cat.debug()
- << *this << ": task."
- << PyUnicode_AsUTF8(attr) << " = "
- << PyUnicode_AsUTF8(str) << "\n";
- Py_DECREF(str);
- }
- return PyDict_SetItem(__dict__, attr, v);
- }
- /**
- * Maps from an expression like "del task.attr_name". This is customized here
- * so we can support some traditional task interfaces that supported directly
- * assigning certain values. We also support adding arbitrary data to the
- * Task object.
- */
- int PythonTask::
- __delattr__(PyObject *self, PyObject *attr) {
- if (PyObject_GenericSetAttr(self, attr, nullptr) == 0) {
- return 0;
- }
- if (!PyErr_ExceptionMatches(PyExc_AttributeError)) {
- return -1;
- }
- PyErr_Clear();
- if (PyDict_DelItem(__dict__, attr) == -1) {
- // PyDict_DelItem does not raise an exception.
- PyErr_Format(PyExc_AttributeError,
- "'PythonTask' object has no attribute '%U'",
- attr);
- return -1;
- }
- return 0;
- }
- /**
- * Maps from an expression like "task.attr_name". This is customized here so
- * we can support some traditional task interfaces that supported directly
- * querying certain values. We also support adding arbitrary data to the Task
- * object.
- */
- PyObject *PythonTask::
- __getattribute__(PyObject *self, PyObject *attr) const {
- // We consult the instance dict first, since the user may have overridden a
- // method or something.
- PyObject *item = PyDict_GetItem(__dict__, attr);
- if (item != nullptr) {
- // PyDict_GetItem returns a borrowed reference.
- Py_INCREF(item);
- return item;
- }
- return PyObject_GenericGetAttr(self, attr);
- }
- /**
- * Called by Python to implement cycle detection.
- */
- int PythonTask::
- __traverse__(visitproc visit, void *arg) {
- /*
- Py_VISIT(_function);
- Py_VISIT(_args);
- Py_VISIT(_upon_death);
- Py_VISIT(_owner);
- Py_VISIT(__dict__);
- Py_VISIT(_generator);
- */
- return 0;
- }
- /**
- * Called by Python to implement cycle breaking.
- */
- int PythonTask::
- __clear__() {
- /*
- Py_CLEAR(_function);
- Py_CLEAR(_args);
- Py_CLEAR(_upon_death);
- Py_CLEAR(_owner);
- Py_CLEAR(__dict__);
- Py_CLEAR(_generator);
- */
- return 0;
- }
- /**
- * Cancels this task. This is equivalent to remove(), except for coroutines,
- * for which it will throw an exception into any currently pending await.
- */
- bool PythonTask::
- cancel() {
- AsyncTaskManager *manager = _manager;
- if (manager != nullptr) {
- nassertr(_chain->_manager == manager, false);
- if (task_cat.is_debug()) {
- task_cat.debug()
- << "Cancelling " << *this << "\n";
- }
- bool must_cancel = true;
- if (_fut_waiter != nullptr) {
- // Cancel the future that this task is waiting on. Note that we do this
- // before grabbing the lock, since this operation may also grab it. This
- // means that _fut_waiter is only protected by the GIL.
- #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
- // Use PyGILState to protect this asynchronous call.
- PyGILState_STATE gstate;
- gstate = PyGILState_Ensure();
- #endif
- // Shortcut for unextended AsyncFuture.
- if (Py_TYPE(_fut_waiter) == (PyTypeObject *)&Dtool_AsyncFuture) {
- AsyncFuture *fut = (AsyncFuture *)DtoolInstance_VOID_PTR(_fut_waiter);
- if (!fut->done()) {
- fut->cancel();
- }
- if (fut->done()) {
- // We don't need this anymore.
- Py_DECREF(_fut_waiter);
- _fut_waiter = nullptr;
- }
- }
- else {
- PyObject *result = PyObject_CallMethod(_fut_waiter, "cancel", nullptr);
- Py_XDECREF(result);
- }
- #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
- PyGILState_Release(gstate);
- #endif
- // Keep _fut_waiter in any case, because we may need to cancel it again
- // later if it ignores the cancellation.
- }
- MutexHolder holder(manager->_lock);
- if (_state == S_awaiting) {
- // Reactivate it so that it can receive a CancelledException.
- if (must_cancel) {
- _must_cancel = true;
- }
- _state = AsyncTask::S_active;
- _chain->_active.push_back(this);
- --_chain->_num_awaiting_tasks;
- return true;
- }
- else if (must_cancel || _fut_waiter != nullptr) {
- // We may be polling an external future, so we still need to throw a
- // CancelledException and allow it to be caught.
- if (must_cancel) {
- _must_cancel = true;
- }
- return true;
- }
- else if (_chain->do_remove(this, true)) {
- return true;
- }
- else {
- if (task_cat.is_debug()) {
- task_cat.debug()
- << " (unable to cancel " << *this << ")\n";
- }
- return false;
- }
- }
- return false;
- }
- /**
- * Override this function to return true if the task can be successfully
- * executed, false if it cannot. Mainly intended as a sanity check when
- * attempting to add the task to a task manager.
- *
- * This function is called with the lock held.
- */
- bool PythonTask::
- is_runnable() {
- return _function != Py_None;
- }
- /**
- * Override this function to do something useful for the task.
- *
- * This function is called with the lock *not* held.
- */
- AsyncTask::DoneStatus PythonTask::
- do_task() {
- #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
- // Use PyGILState to protect this asynchronous call.
- PyGILState_STATE gstate;
- gstate = PyGILState_Ensure();
- #endif
- DoneStatus result = do_python_task();
- #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
- PyGILState_Release(gstate);
- #endif
- return result;
- }
- /**
- * The Python calls that implement do_task(). This function is separate so we
- * can acquire the Python interpretor lock while it runs.
- */
- AsyncTask::DoneStatus PythonTask::
- do_python_task() {
- PyObject *result = nullptr;
- // Are we waiting for a future to finish? Short-circuit all the logic below
- // by simply calling done().
- {
- PyObject *fut_waiter = _fut_waiter;
- if (fut_waiter != nullptr) {
- PyObject *is_done = PyObject_CallMethod(fut_waiter, "done", nullptr);
- if (is_done == nullptr) {
- return DS_interrupt;
- }
- if (!PyObject_IsTrue(is_done)) {
- // Nope, ask again next frame.
- Py_DECREF(is_done);
- return DS_cont;
- }
- Py_DECREF(is_done);
- Py_DECREF(fut_waiter);
- _fut_waiter = nullptr;
- }
- }
- if (_generator == nullptr) {
- // We are calling the function directly.
- nassertr(_function != nullptr, DS_interrupt);
- PyObject *args = get_args();
- result = PythonThread::call_python_func(_function, args);
- Py_DECREF(args);
- if (result != nullptr && PyGen_Check(result)) {
- // The function has yielded a generator. We will call into that
- // henceforth, instead of calling the function from the top again.
- if (task_cat.is_debug()) {
- PyObject *str = PyObject_ASCII(_function);
- task_cat.debug()
- << PyUnicode_AsUTF8(str) << " in " << *this
- << " yielded a generator.\n";
- Py_DECREF(str);
- }
- _generator = result;
- result = nullptr;
- } else if (result != nullptr && Py_TYPE(result)->tp_as_async != nullptr) {
- // The function yielded a coroutine, or something of the sort.
- if (task_cat.is_debug()) {
- PyObject *str = PyObject_ASCII(_function);
- PyObject *str2 = PyObject_ASCII(result);
- task_cat.debug()
- << PyUnicode_AsUTF8(str) << " in " << *this
- << " yielded an awaitable: " << PyUnicode_AsUTF8(str2) << "\n";
- Py_DECREF(str);
- Py_DECREF(str2);
- }
- if (PyCoro_CheckExact(result)) {
- // If a coroutine, am_await is possible but senseless, since we can
- // just call send(None) on the coroutine itself.
- _generator = result;
- } else {
- unaryfunc await = Py_TYPE(result)->tp_as_async->am_await;
- _generator = await(result);
- Py_DECREF(result);
- }
- result = nullptr;
- }
- }
- if (_generator != nullptr) {
- if (!_must_cancel) {
- // We are calling a generator. Use "send" rather than PyIter_Next since
- // we need to be able to read the value from a StopIteration exception.
- PyObject *func = PyObject_GetAttrString(_generator, "send");
- nassertr(func != nullptr, DS_interrupt);
- result = PyObject_CallOneArg(func, Py_None);
- Py_DECREF(func);
- } else {
- // Throw a CancelledError into the generator.
- _must_cancel = false;
- PyObject *exc = PyObject_CallNoArgs(Extension<AsyncFuture>::get_cancelled_error_type());
- PyObject *func = PyObject_GetAttrString(_generator, "throw");
- result = PyObject_CallFunctionObjArgs(func, exc, nullptr);
- Py_DECREF(func);
- Py_DECREF(exc);
- }
- if (result == nullptr) {
- // An error happened. If StopIteration, that indicates the task has
- // returned. Otherwise, we need to save it so that it can be re-raised
- // in the function that awaited this task.
- Py_DECREF(_generator);
- _generator = nullptr;
- #if PY_VERSION_HEX >= 0x030D0000 // Python 3.13
- // Python 3.13 does not support _PyGen_FetchStopIterationValue anymore.
- if (PyErr_ExceptionMatches(PyExc_StopIteration)) {
- PyObject *exc = PyErr_GetRaisedException();
- result = ((PyStopIterationObject *)exc)->value;
- if (result == nullptr) {
- result = Py_None;
- }
- Py_INCREF(result);
- Py_DECREF(exc);
- #else
- if (_PyGen_FetchStopIterationValue(&result) == 0) {
- #endif
- PyErr_Clear();
- if (_must_cancel) {
- // Task was cancelled right before finishing. Make sure it is not
- // getting rerun or marked as successfully completed.
- _state = S_servicing_removed;
- }
- // If we passed a coroutine into the task, eg. something like:
- // taskMgr.add(my_async_function())
- // then we cannot rerun the task, so the return value is always
- // assumed to be DS_done. Instead, we pass the return value to the
- // result of the `await` expression.
- if (_function == nullptr) {
- if (task_cat.is_debug()) {
- task_cat.debug()
- << *this << " received StopIteration from coroutine.\n";
- }
- // Store the result in _exc_value because that's not used anyway.
- Py_XDECREF(_exc_value);
- _exc_value = result;
- return DS_done;
- }
- } else if (PyErr_ExceptionMatches(Extension<AsyncFuture>::get_cancelled_error_type())) {
- // Someone cancelled the coroutine, and it did not bother to handle it,
- // so we should consider it cancelled.
- if (task_cat.is_debug()) {
- task_cat.debug()
- << *this << " was cancelled and did not catch CancelledError.\n";
- }
- _state = S_servicing_removed;
- PyErr_Clear();
- return DS_done;
- } else if (_function == nullptr) {
- // We got an exception. If this is a scheduled coroutine, we will
- // keep it and instead throw it into whatever 'awaits' this task.
- // Otherwise, fall through and handle it the regular way.
- Py_XDECREF(_exception);
- Py_XDECREF(_exc_value);
- Py_XDECREF(_exc_traceback);
- PyErr_Fetch(&_exception, &_exc_value, &_exc_traceback);
- _retrieved_exception = false;
- if (task_cat.is_debug()) {
- if (_exception != nullptr && Py_TYPE(_exception) == &PyType_Type) {
- task_cat.debug()
- << *this << " received " << ((PyTypeObject *)_exception)->tp_name << " from coroutine.\n";
- } else {
- task_cat.debug()
- << *this << " received exception from coroutine.\n";
- }
- }
- // Tell the task chain we want to kill ourselves. We indicate this is
- // a "clean exit" because we still want to run the done callbacks on
- // exception.
- return DS_done;
- }
- } else if (result == Py_None) {
- // Bare yield means to continue next frame.
- Py_DECREF(result);
- return DS_cont;
- } else if (DtoolInstance_Check(result)) {
- // We are waiting for an AsyncFuture (eg. other task) to finish.
- AsyncFuture *fut = (AsyncFuture *)DtoolInstance_UPCAST(result, Dtool_AsyncFuture);
- if (fut != nullptr) {
- // Suspend execution of this task until this other task has completed.
- if (fut != (AsyncFuture *)this && !fut->done()) {
- if (fut->is_task()) {
- // This is actually a task, do we need to schedule it with the
- // manager? This allows doing something like
- // await Task.pause(1.0)
- // directly instead of having to do:
- // await taskMgr.add(Task.pause(1.0))
- AsyncTask *task = (AsyncTask *)fut;
- if (!task->is_alive()) {
- _manager->add(task);
- }
- }
- if (fut->add_waiting_task(this)) {
- if (task_cat.is_debug()) {
- task_cat.debug()
- << *this << " is now awaiting <" << *fut << ">.\n";
- }
- } else {
- // The task is already done. Continue at next opportunity.
- if (task_cat.is_debug()) {
- task_cat.debug()
- << *this << " would await <" << *fut << ">, were it not already done.\n";
- }
- Py_DECREF(result);
- return DS_cont;
- }
- } else {
- // This is an error. If we wanted to be fancier we could also
- // detect deeper circular dependencies.
- task_cat.error()
- << *this << " cannot await itself\n";
- }
- // Store the Python object in case we need to cancel it (it may be a
- // subclass of AsyncFuture that overrides cancel() from Python)
- _fut_waiter = result;
- return DS_await;
- }
- } else {
- // We are waiting for a non-Panda future to finish. We currently
- // implement this by checking every frame whether the future is done.
- PyObject *check = PyObject_GetAttrString(result, "_asyncio_future_blocking");
- if (check != nullptr && check != Py_None) {
- Py_DECREF(check);
- // Next frame, check whether this future is done.
- PyObject *fut_done = PyObject_GetAttrString(result, "done");
- if (fut_done == nullptr || !PyCallable_Check(fut_done)) {
- Py_XDECREF(fut_done);
- task_cat.error()
- << "future.done is not callable\n";
- return DS_interrupt;
- }
- if (task_cat.is_debug()) {
- PyObject *str = PyObject_ASCII(result);
- task_cat.debug()
- << *this << " is now polling " << PyUnicode_AsUTF8(str) << ".done()\n";
- Py_DECREF(str);
- }
- _fut_waiter = result;
- return DS_cont;
- }
- PyErr_Clear();
- Py_XDECREF(check);
- }
- }
- if (result == nullptr) {
- if (PyErr_Occurred() && PyErr_ExceptionMatches(PyExc_SystemExit)) {
- // Don't print an error message for SystemExit. Or rather, make it a
- // debug message.
- if (task_cat.is_debug()) {
- task_cat.debug()
- << "SystemExit occurred in " << *this << "\n";
- }
- } else {
- task_cat.error()
- << "Exception occurred in " << *this << "\n";
- }
- return DS_interrupt;
- }
- if (result == Py_None || _ignore_return) {
- Py_DECREF(result);
- return DS_done;
- }
- if (PyLong_Check(result)) {
- long retval = PyLong_AS_LONG(result);
- switch (retval) {
- case DS_again:
- Py_XDECREF(_generator);
- _generator = nullptr;
- // Fall through.
- case DS_done:
- case DS_cont:
- case DS_pickup:
- case DS_exit:
- case DS_pause:
- // Legitimate value.
- Py_DECREF(result);
- return (DoneStatus) retval;
- case -1:
- // Legacy value.
- Py_DECREF(result);
- return DS_done;
- default:
- // Unexpected value.
- break;
- }
- }
- // This is unfortunate, but some are returning task.done, which nowadays
- // conflicts with the AsyncFuture method. Check if that is being returned.
- PyMethodDef *meth = nullptr;
- if (PyCFunction_Check(result)) {
- meth = ((PyCFunctionObject *)result)->m_ml;
- } else if (Py_TYPE(result) == &PyMethodDescr_Type) {
- meth = ((PyMethodDescrObject *)result)->d_method;
- }
- if (meth != nullptr && strcmp(meth->ml_name, "done") == 0) {
- Py_DECREF(result);
- return DS_done;
- }
- std::ostringstream strm;
- PyObject *str = PyObject_ASCII(result);
- if (str == nullptr) {
- str = PyUnicode_FromString("<repr error>");
- }
- strm
- << *this << " returned " << PyUnicode_AsUTF8(str);
- Py_DECREF(str);
- Py_DECREF(result);
- std::string message = strm.str();
- nassert_raise(message);
- return DS_interrupt;
- }
- /**
- * Override this function to do something useful when the task has been added
- * to the active queue.
- *
- * This function is called with the lock *not* held.
- */
- void PythonTask::
- upon_birth(AsyncTaskManager *manager) {
- AsyncTask::upon_birth(manager);
- register_to_owner();
- }
- /**
- * Override this function to do something useful when the task has been
- * removed from the active queue. The parameter clean_exit is true if the
- * task has been removed because it exited normally (returning DS_done), or
- * false if it was removed for some other reason (e.g.
- * AsyncTaskManager::remove()). By the time this method is called, _manager
- * has been cleared, so the parameter manager indicates the original
- * AsyncTaskManager that owned this task.
- *
- * The normal behavior is to throw the done_event only if clean_exit is true.
- *
- * This function is called with the lock *not* held.
- */
- void PythonTask::
- upon_death(AsyncTaskManager *manager, bool clean_exit) {
- AsyncTask::upon_death(manager, clean_exit);
- // If we were polling something when we were removed, get rid of it.
- //TODO: should we call cancel() on it?
- if (_fut_waiter != nullptr) {
- Py_DECREF(_fut_waiter);
- _fut_waiter = nullptr;
- }
- if (_upon_death != Py_None) {
- #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
- // Use PyGILState to protect this asynchronous call.
- PyGILState_STATE gstate;
- gstate = PyGILState_Ensure();
- #endif
- call_function(_upon_death);
- #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
- PyGILState_Release(gstate);
- #endif
- }
- unregister_from_owner();
- }
- /**
- * Tells the owner we are now his task.
- */
- void PythonTask::
- register_to_owner() {
- if (_owner != Py_None && !_registered_to_owner) {
- #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
- // Use PyGILState to protect this asynchronous call.
- PyGILState_STATE gstate;
- gstate = PyGILState_Ensure();
- #endif
- _registered_to_owner = true;
- call_owner_method("_addTask");
- #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
- PyGILState_Release(gstate);
- #endif
- }
- }
- /**
- * Tells the owner we are no longer his task.
- */
- void PythonTask::
- unregister_from_owner() {
- // make sure every call to _clearTask corresponds to a call to _addTask
- if (_owner != Py_None && _registered_to_owner) {
- #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
- // Use PyGILState to protect this asynchronous call.
- PyGILState_STATE gstate;
- gstate = PyGILState_Ensure();
- #endif
- _registered_to_owner = false;
- call_owner_method("_clearTask");
- #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
- PyGILState_Release(gstate);
- #endif
- }
- }
- /**
- * Calls the indicated method name on the given object, if defined, passing in
- * the task object as the only parameter.
- */
- void PythonTask::
- call_owner_method(const char *method_name) {
- if (_owner != Py_None) {
- PyObject *func = PyObject_GetAttrString(_owner, (char *)method_name);
- if (func == nullptr) {
- task_cat.error()
- << "Owner object added to " << *this << " has no method "
- << method_name << "().\n";
- } else {
- call_function(func);
- Py_DECREF(func);
- }
- }
- }
- /**
- * Calls the indicated Python function, passing in the task object as the only
- * parameter.
- */
- void PythonTask::
- call_function(PyObject *function) {
- if (function != Py_None) {
- this->ref();
- PyObject *self = DTool_CreatePyInstance(this, Dtool_PythonTask, true, false);
- PyObject *result = PyObject_CallOneArg(function, self);
- Py_XDECREF(result);
- Py_DECREF(self);
- }
- }
- #endif // HAVE_PYTHON
|