Skip to content

Commit

Permalink
FIX Check if future is done before trying to set a result on it (pyod…
Browse files Browse the repository at this point in the history
…ide#4837)

Otherwise, the future raises invalid state error
  • Loading branch information
hoodmane committed Jun 7, 2024
1 parent 91ec87b commit c4afd17
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 3 deletions.
7 changes: 6 additions & 1 deletion docs/project/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`

## Version 0.26.0

_May 27, 2024_
Expand Down
14 changes: 12 additions & 2 deletions src/core/jsproxy.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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);
Expand All @@ -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) {
Expand Down
14 changes: 14 additions & 0 deletions src/py/_pyodide/_future_helper.py
Original file line number Diff line number Diff line change
@@ -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))
22 changes: 22 additions & 0 deletions src/tests/test_asyncio.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import time

import pytest
from pytest_pyodide import run_in_pyodide

from pyodide.code import eval_code_async

Expand Down Expand Up @@ -421,3 +422,24 @@ async def temp():
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

0 comments on commit c4afd17

Please sign in to comment.