|
|
@@ -45,7 +45,7 @@ PythonTask(PyObject *func_or_coro, const std::string &name) :
|
|
|
_exc_value(nullptr),
|
|
|
_exc_traceback(nullptr),
|
|
|
_generator(nullptr),
|
|
|
- _future_done(nullptr),
|
|
|
+ _fut_waiter(nullptr),
|
|
|
_ignore_return(false),
|
|
|
_retrieved_exception(false) {
|
|
|
|
|
|
@@ -404,20 +404,58 @@ cancel() {
|
|
|
<< "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.
|
|
|
- _must_cancel = true;
|
|
|
+ if (must_cancel) {
|
|
|
+ _must_cancel = true;
|
|
|
+ }
|
|
|
_state = AsyncTask::S_active;
|
|
|
_chain->_active.push_back(this);
|
|
|
--_chain->_num_awaiting_tasks;
|
|
|
return true;
|
|
|
}
|
|
|
- else if (_future_done != nullptr) {
|
|
|
- // We are polling, waiting for a non-Panda future to be done.
|
|
|
- Py_DECREF(_future_done);
|
|
|
- _future_done = nullptr;
|
|
|
- _must_cancel = 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)) {
|
|
|
@@ -477,17 +515,24 @@ AsyncTask::DoneStatus PythonTask::
|
|
|
do_python_task() {
|
|
|
PyObject *result = nullptr;
|
|
|
|
|
|
- // Are we waiting for a future to finish?
|
|
|
- if (_future_done != nullptr) {
|
|
|
- PyObject *is_done = PyObject_CallNoArgs(_future_done);
|
|
|
- if (!PyObject_IsTrue(is_done)) {
|
|
|
- // Nope, ask again next frame.
|
|
|
+ // 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);
|
|
|
- return DS_cont;
|
|
|
+ Py_DECREF(fut_waiter);
|
|
|
+ _fut_waiter = nullptr;
|
|
|
}
|
|
|
- Py_DECREF(is_done);
|
|
|
- Py_DECREF(_future_done);
|
|
|
- _future_done = nullptr;
|
|
|
}
|
|
|
|
|
|
if (_generator == nullptr) {
|
|
|
@@ -664,7 +709,9 @@ do_python_task() {
|
|
|
task_cat.error()
|
|
|
<< *this << " cannot await itself\n";
|
|
|
}
|
|
|
- Py_DECREF(result);
|
|
|
+ // 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 {
|
|
|
@@ -674,8 +721,9 @@ do_python_task() {
|
|
|
if (check != nullptr && check != Py_None) {
|
|
|
Py_DECREF(check);
|
|
|
// Next frame, check whether this future is done.
|
|
|
- _future_done = PyObject_GetAttrString(result, "done");
|
|
|
- if (_future_done == nullptr || !PyCallable_Check(_future_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;
|
|
|
@@ -686,7 +734,7 @@ do_python_task() {
|
|
|
<< *this << " is now polling " << PyUnicode_AsUTF8(str) << ".done()\n";
|
|
|
Py_DECREF(str);
|
|
|
}
|
|
|
- Py_DECREF(result);
|
|
|
+ _fut_waiter = result;
|
|
|
return DS_cont;
|
|
|
}
|
|
|
PyErr_Clear();
|
|
|
@@ -802,9 +850,10 @@ 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.
|
|
|
- if (_future_done != nullptr) {
|
|
|
- Py_DECREF(_future_done);
|
|
|
- _future_done = nullptr;
|
|
|
+ //TODO: should we call cancel() on it?
|
|
|
+ if (_fut_waiter != nullptr) {
|
|
|
+ Py_DECREF(_fut_waiter);
|
|
|
+ _fut_waiter = nullptr;
|
|
|
}
|
|
|
|
|
|
if (_upon_death != Py_None) {
|