Skip to content

unskip and fix some passing tests#5429

Merged
scoder merged 9 commits intocython:masterfrom
mattip:pypy-tests
May 24, 2023
Merged

unskip and fix some passing tests#5429
scoder merged 9 commits intocython:masterfrom
mattip:pypy-tests

Conversation

@mattip
Copy link
Copy Markdown
Contributor

@mattip mattip commented May 11, 2023

PyPy3.9 passes these tests, once the sys.getrefcount part is skipped. I am working on getting test_asyncgen to pass so grpcio work with PyPY, see pypy issue 3280 and pypy issue 3935

@mattip
Copy link
Copy Markdown
Contributor Author

mattip commented May 11, 2023

cool, pypy3.9 tests pass on CI.

@mattip
Copy link
Copy Markdown
Contributor Author

mattip commented May 11, 2023

The pure-python test_asyncgen.py passes pypy -m pytest test_asyncgen.py but runtests.py is failing. There are two out-of-bound indexes errors and five badly filled in return value failures


o->ag_hooks_inited = 1;

#if CYTHON_COMPILING_IN_CPYTHON
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not completely sure, but I wonder if CYTHON_USE_TYPE_SLOTS would be the more appropriate check here (and later)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure, I thought CYTHON_USE_TYPE_SLOTS is for the documented dunder slots like tp_str -> __str__ as generated by the typeslots.py code generator.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... which generates these slot functions

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CYTHON_USE_TYPE_SLOTS is for code that directly uses the tp_* slot functions of types, not for attributes on the thread state. There's CYTHON_FAST_THREAD_STATE, but I don't think that applies here, either. That guard controls the way we get at the thread state, more that what we do with it (although that might still depend on how fast we can get our hands on it when we need it).

If this it PyPy specific code, why not use CYTHON_COMPILING_IN_PYPY?

@mattip
Copy link
Copy Markdown
Contributor Author

mattip commented May 14, 2023

There are a number of failing tests in code like

ZERO = 0

async def gen():
    yield 123
    1 / ZERO

def test_last_yield(g):
    ai = g.__aiter__()
    an = ai.__anext__()
    try:
        next(an)          #  <---- this becomes __Pyx_PyIter_Next2()
    except StopIteration as ex:
        assert ex.args[1] == 123      # <- fails when not using CYTHON_USE_TYPE_SLOTS
    else:
        assert False, "no exception raised

I think the failure is due to a difference between PyIter_Next and tp_iternext(iterator) in __Pyx_PyIter_Next2. On the last iteration, PyIter_Next returns NULL while clearing the StopIteration error, but tp_iternext(iterator) does not clear the error. The StopIteration exception is regenerated, since NULL has been returned, but clearing the error removes the yield value from the exception, and so the args tuple is empty.

@scoder
Copy link
Copy Markdown
Contributor

scoder commented May 17, 2023

I think the failure is due to a difference between PyIter_Next and tp_iternext(iterator) in __Pyx_PyIter_Next2. On the last iteration, PyIter_Next returns NULL while clearing the StopIteration error, but tp_iternext(iterator) does not clear the error. The StopIteration exception is regenerated, since NULL has been returned, but clearing the error removes the yield value from the exception, and so the args tuple is empty.

So, you're saying that calling PyIter_Next() (when not using type slots) is wrong here, because it swallows the StopIteration exception? I don't think there is a way to use this protocol without either using type slots or calling PyIter_Next()

Does PyPy fill the tp_iternext type slot?

@mattip
Copy link
Copy Markdown
Contributor Author

mattip commented May 17, 2023

Does PyPy fill the tp_iternext type slot?

Yes. I added a fix in 859a9a5 to use the type slot on PyPy, with a workaround for PyPy not having _PyObject_NextNotImplemented (I think the code intention there is to avoid calling _PyObject_NextNotImplemented since it knows what the outcome will be)

Copy link
Copy Markdown
Contributor

@scoder scoder left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of this is good to have. I left comments on changes that I found questionable.


o->ag_hooks_inited = 1;

#if CYTHON_COMPILING_IN_CPYTHON
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CYTHON_USE_TYPE_SLOTS is for code that directly uses the tp_* slot functions of types, not for attributes on the thread state. There's CYTHON_FAST_THREAD_STATE, but I don't think that applies here, either. That guard controls the way we get at the thread state, more that what we do with it (although that might still depend on how fast we can get our hands on it when we need it).

If this it PyPy specific code, why not use CYTHON_COMPILING_IN_PYPY?


finalizer = tstate->async_gen_finalizer;
#else
finalizer = _PyEval_GetAsyncGenFinalizer();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These were removed from CPython a year ago: python/cpython#32017
That probably means they are not future proof.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code-path is for non-CPython compilation. Is there a better way to work around the opaque PyThreadState fields?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code-path is for non-CPython compilation.

Right, but the fact that CPython had them and removed them means that other implementation won't necessarily have them either. As long as only PyPy is known to have them, I'd use the PYPY guard instead of the CPYTHON guard here.

Thefunctions were removed from CPython in favour of sys.get_asyncgen_hooks(), which sounds rather annoying to call from C, but we could do that here.

Is there a better way to work around the opaque PyThreadState fields?

It's an opaque struct in the Limited API, not in regular CPython.

@mattip mattip marked this pull request as ready for review May 21, 2023 20:44
@mattip
Copy link
Copy Markdown
Contributor Author

mattip commented May 21, 2023

I related to the review comments. Still not resolved: using getter functions _PyEval_GetAsyncGenFinalizer and _PyEval_GetAsyncGenFirstiter rather than direct acess to the fields.

I had to reskip the test: there is too much missing in PyPy to support yield and exception handlers in coroutines.

@mattip
Copy link
Copy Markdown
Contributor Author

mattip commented May 22, 2023

CI is passing. I will revert using a nightly build in CI (commit fcf1695) once PyPy releases a version that supports _PyEval_GetAsyncGenFinalizer and _PyEval_GetAsyncGenFirstiter, which in turn are needed to support compilation of grpcio.

Copy link
Copy Markdown
Contributor

@scoder scoder left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As long as we keep this PyPy specific, it should be safe to merge. As it stands, it also impacts other implementations.


finalizer = tstate->async_gen_finalizer;
#else
finalizer = _PyEval_GetAsyncGenFinalizer();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code-path is for non-CPython compilation.

Right, but the fact that CPython had them and removed them means that other implementation won't necessarily have them either. As long as only PyPy is known to have them, I'd use the PYPY guard instead of the CPYTHON guard here.

Thefunctions were removed from CPython in favour of sys.get_asyncgen_hooks(), which sounds rather annoying to call from C, but we could do that here.

Is there a better way to work around the opaque PyThreadState fields?

It's an opaque struct in the Limited API, not in regular CPython.

@mattip
Copy link
Copy Markdown
Contributor Author

mattip commented May 22, 2023

Thefunctions were removed from CPython in favour of sys.get_asyncgen_hooks(), which sounds rather annoying to call from C, but we could do that here.

That is actually what the PyPy implementation does. Would it be OK if I make the code path with the pypy-specific functions pypy-only?

@mattip
Copy link
Copy Markdown
Contributor Author

mattip commented May 23, 2023

close/reopen to trigger CI

@mattip mattip closed this May 23, 2023
@mattip mattip reopened this May 23, 2023
Copy link
Copy Markdown
Contributor

@scoder scoder left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM now. I'll wait for CI and then merge this.

@scoder scoder added this to the 3.0 milestone May 24, 2023
@scoder scoder merged commit f892686 into cython:master May 24, 2023
@mattip
Copy link
Copy Markdown
Contributor Author

mattip commented May 24, 2023

Thanks!

@mattip
Copy link
Copy Markdown
Contributor Author

mattip commented Jun 2, 2023

As commented in pypy issue 3280

the above patch (mattip: commit 00113c3) on top of 0.29.35 allows GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip install 'grpcio<1.43' --no-build-isolation to build and our rpc tests pass with this grpcio module.

Would it be OK to backport this to the 0.29.x branch?

@scoder
Copy link
Copy Markdown
Contributor

scoder commented Jun 3, 2023 via email

mattip added a commit to mattip/cython that referenced this pull request Jun 4, 2023
Currently require a nightly PyPy build for Py3.9 to support async iteration and finalisation.

Avoid PyIter_Next() and call tp_iternext() instead because PyIter_Next() swallows StopIteration exceptions and thus looses the return value.

See https://foss.heptapod.net/pypy/pypy/-/issues/3280
See https://foss.heptapod.net/pypy/pypy/-/issues/3935
da-woods pushed a commit that referenced this pull request Jun 11, 2023
* Fixes for PyPy (GH-5429)

Currently require a nightly PyPy build for Py3.9 to support async iteration and finalisation.

Avoid PyIter_Next() and call tp_iternext() instead because PyIter_Next() swallows StopIteration exceptions and thus looses the return value.

See https://foss.heptapod.net/pypy/pypy/-/issues/3280
See https://foss.heptapod.net/pypy/pypy/-/issues/3935

* change MACOSX_DEPLOYMENT_TARGET to 11.0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants