Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix from_thread.run(_sync)? not setting sniffio on asyncio #524

Merged
merged 7 commits into from
May 11, 2023

Conversation

gschaffner
Copy link
Collaborator

closes #523.

@gschaffner
Copy link
Collaborator Author

(rebased onto master to fix the pre-commit isort failure)

This also fixes a deadlock when using start_blocking_portal("asyncio")
on a non-asyncio async backend known to sniffio.

Fixes agronholm#523.
@gschaffner gschaffner force-pushed the fix-from_thread.run-sniffio branch 3 times, most recently from 0da45b0 to bf35657 Compare March 14, 2023 06:58
@gschaffner
Copy link
Collaborator Author

CI is passing, but tox is failing with ValueError: Plugin already registered: pytest_timeout=<module 'pytest_timeout' from '/tmp/tmp.JgkOIWx83R/anyio/.tox/py311/lib/python3.11/site-packages/pytest_timeout.py'>.

setting PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 in tox config would fix this, but that would mean downstream distro packagers have to use PYTEST_DISABLE_PLUGIN_AUTOLOAD too. i'm not sure why pytest is fine with pytest-mock being both autoloaded and explicitly loaded but is not happy when the same is done with pytest-timeout.

@agronholm agronholm added this to the 4.0 milestone May 2, 2023
Copy link
Owner

@agronholm agronholm left a comment

Choose a reason for hiding this comment

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

Let's get this done for v4.0!

Comment on lines 75 to 84
@pytest.fixture
def portal_backend_name(portal_backend: Any) -> str:
return anyio_backend_name.__wrapped__(portal_backend) # type: ignore[attr-defined]


@pytest.fixture
def portal_backend_options(portal_backend: Any) -> dict[str, Any]:
return anyio_backend_options.__wrapped__( # type: ignore[attr-defined]
portal_backend
)
Copy link
Owner

Choose a reason for hiding this comment

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

Do the tests specifically need to replicate the exact options for each backend? If not, you could just parametrize the tests based on anyio.get_all_backends().

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

sounds good! yeah, this parameterization is probably overly defensive.

i suspect it's super unlikely1 that e.g. from_thread.run(requires_sniffio) could simultaneously work between two CPython asyncio threads (a test case covered by get_all_backends()) but not work between e.g. a CPython asyncio thread and a uvloop asyncio thread.

will implement your suggestion.

Footnotes

  1. and maybe impossible, given how sniffio is agnostic to CPython asyncio vs. uvloop asyncio

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

the parametrization is now

TestBlockingPortal::test_from_async[asyncio-asyncio]
TestBlockingPortal::test_from_async[asyncio-trio]
TestBlockingPortal::test_from_async[asyncio+uvloop-asyncio]
TestBlockingPortal::test_from_async[asyncio+uvloop-trio]
TestBlockingPortal::test_from_async[trio-asyncio]
TestBlockingPortal::test_from_async[trio-trio]

which is asymmetric, but that should be fine.

Comment on lines 566 to 581
@pytest.mark.timeout(1)
async def test_from_async(
self,
anyio_backend_name: str,
portal_backend_name: str,
portal_backend_options: dict[str, Any],
) -> None:
"""Test that portals don't deadlock when started/used from async code."""

if anyio_backend_name == "trio" and portal_backend_name == "trio":
pytest.xfail("known bug (#525)")

with start_blocking_portal(
portal_backend_name, portal_backend_options
) as portal:
portal.call(checkpoint)
Copy link
Owner

Choose a reason for hiding this comment

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

This is an annoying problem. Perhaps this could be removed from this PR, to be figured out later? I don't want pytest-timeout to block this from being merged.

Copy link
Owner

Choose a reason for hiding this comment

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

I think I'll just sidestep the issue of pytest-timeout for now and just rely on the timeout we have on GitHub. I want this merged for v3.7.0.

Copy link
Collaborator Author

@gschaffner gschaffner May 10, 2023

Choose a reason for hiding this comment

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

sounds good. the only side effect of removing the timeout decorator here is that it will make pytest timeout if this test fails, but as long as folks (continue to) interpret a pytest timeout as a test failure then it shouldn't be too bad in the interim.

Copy link
Owner

Choose a reason for hiding this comment

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

That's just for GitHub Actions, right? Locally it would just hang forever, yes?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

pytest has its own per-test timeout by default, IIRC

Copy link
Owner

Choose a reason for hiding this comment

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

Really? I haven't seen any such thing. All my searches point to pytest-timeout as the provider of any sort of a timeout effect.

Copy link
Owner

Choose a reason for hiding this comment

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

At any rate, if you could remove pytest-timeout for the time being, and make the other change I suggested, I could merge this and get it into v3.7.0. I think this is the last fix slated for that minor release.

Copy link
Collaborator Author

@gschaffner gschaffner May 10, 2023

Choose a reason for hiding this comment

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

i will double check—i faintly recall trio CI having weird failures on PyPy caused by pytest's timeout fault handler segfaulting, but my memory is flaky there.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

right—i misremembered this a bit. pytest has no per-test timeout by default. however one can set faulthandler_timeout to enable one. if a test takes longer than that, though, pytest doesn't fail it—it just dumps tracebacks.

regardless, i'll implement your suggestion here—we can figure out what's going with pytest-timeout autoloading later.

@agronholm agronholm removed this from the 4.0 milestone May 10, 2023
@gschaffner gschaffner requested a review from agronholm May 11, 2023 10:29
@agronholm agronholm merged commit e579d2c into agronholm:master May 11, 2023
12 checks passed
@agronholm
Copy link
Owner

Thanks!

gschaffner added a commit to gschaffner/anyio that referenced this pull request Jan 24, 2024
With pytest.mark.xfail, the test is still run and will emit xpass
if/when it stops failing, i.e. it will detect when the mark.xfail should
be removed. In contrast, pytest.xfail immediately force-skips the test,
skipping the xpass check.

The only remaining use of pytest.xfail (in
TestBlockingPortal.test_from_async) needs to remain a pytest.xfail until
someone gets around to agronholm#524 (comment)
because it will deadlock otherwise.
gschaffner added a commit to gschaffner/anyio that referenced this pull request Jan 24, 2024
With pytest.mark.xfail, the test is still run and will emit xpass
if/when it stops failing, i.e. it will detect when the mark.xfail should
be removed. In contrast, pytest.xfail immediately force-skips the test,
skipping the xpass check.

The only remaining use of pytest.xfail (in
TestBlockingPortal.test_from_async) needs to remain a pytest.xfail until
someone gets around to agronholm#524 (comment)
because it will deadlock otherwise.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

from_thread.run(_sync)? does not set sniffio on asyncio
2 participants