FIX Check if future is done before trying to set a result on it (#4837)

Otherwise, the future raises invalid state error
This commit is contained in:
Hood Chatham 2024-06-05 21:56:01 -07:00 committed by GitHub
parent 97eda9d0bf
commit 43cce6e5ff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 54 additions and 3 deletions

View File

@ -24,9 +24,14 @@ myst:
stack switching is disabled.
{pr}`4817`
- {{ Fix }} Resolved an issue where string keys in `PyProxyJsonAdaptor` were unexpectedly cast to numbers.
- {{ Fix }} Resolved an issue where string keys in `PyProxyJsonAdaptor` were
unexpectedly cast to numbers.
{pr}`4825`
- {{ Fix }} When a `Future` connected to a `Promise` is cancelled, don't raise
`InvalidStateError`.
{pr}`4837`
### Packages
- New Packages: `pytest-asyncio` {pr}`4819`

View File

@ -2637,6 +2637,8 @@ wrap_promise(JsVal promise, JsVal done_callback, PyObject* js2py_converter)
{
bool success = false;
PyObject* loop = NULL;
PyObject* helpers_mod = NULL;
PyObject* helpers = NULL;
PyObject* set_result = NULL;
PyObject* set_exception = NULL;
@ -2648,9 +2650,15 @@ wrap_promise(JsVal promise, JsVal done_callback, PyObject* js2py_converter)
result = _PyObject_CallMethodIdNoArgs(loop, &PyId_create_future);
FAIL_IF_NULL(result);
set_result = _PyObject_GetAttrId(result, &PyId_set_result);
helpers_mod = PyImport_ImportModule("_pyodide._future_helper");
FAIL_IF_NULL(helpers_mod);
_Py_IDENTIFIER(get_future_resolvers);
helpers = _PyObject_CallMethodIdOneArg(
helpers_mod, &PyId_get_future_resolvers, result);
FAIL_IF_NULL(helpers);
set_result = Py_XNewRef(PyTuple_GetItem(helpers, 0));
FAIL_IF_NULL(set_result);
set_exception = _PyObject_GetAttrId(result, &PyId_set_exception);
set_exception = Py_XNewRef(PyTuple_GetItem(helpers, 1));
FAIL_IF_NULL(set_exception);
promise = JsvPromise_Resolve(promise);
@ -2663,6 +2671,8 @@ wrap_promise(JsVal promise, JsVal done_callback, PyObject* js2py_converter)
success = true;
finally:
Py_CLEAR(loop);
Py_CLEAR(helpers_mod);
Py_CLEAR(helpers);
Py_CLEAR(set_result);
Py_CLEAR(set_exception);
if (!success) {

View File

@ -0,0 +1,14 @@
def set_result(fut, val):
if fut.done():
return
fut.set_result(val)
def set_exception(fut, val):
if fut.done():
return
fut.set_exception(val)
def get_future_resolvers(fut):
return (set_result.__get__(fut), set_exception.__get__(fut))

View File

@ -2,6 +2,7 @@ import asyncio
import time
import pytest
from pytest_pyodide import run_in_pyodide
from pyodide.code import eval_code_async
@ -421,3 +422,24 @@ def test_await_pyproxy_async_def(selenium):
return (!!packages.packages) && (!!packages.info);
"""
)
@run_in_pyodide
async def inner_test_cancellation(selenium):
from asyncio import ensure_future, sleep
from js import fetch
async def f():
while True:
await fetch("/")
fut = ensure_future(f())
await sleep(0.01)
fut.cancel()
await sleep(0.1)
def test_cancellation(selenium):
inner_test_cancellation(selenium)
assert "InvalidStateError" not in selenium.logs