python-trio / trio

Trio – a friendly Python library for async concurrency and I/O
https://trio.readthedocs.io
Other
5.98k stars 325 forks source link

Remaining test issues with Python 3.13.0b1: not ki_protected, pathlib.Path.resolve siganture #3004

Closed hroncok closed 2 weeks ago

hroncok commented 1 month ago

Hello. After https://github.com/python-trio/trio/pull/2959 I still see the following test failures with Python 3.13.0b1:

[trio (master)]$ python3.13 -m venv venv
[trio (master)]$ . venv/bin/activate
(venv) [trio (master)]$ pip install . pytest
...
(venv) [trio (master)]$ pytest --pyargs trio --skip-optional-imports
============================= test session starts ==============================
platform linux -- Python 3.13.0b1, pytest-8.2.1, pluggy-1.5.0
rootdir: .../trio
configfile: pyproject.toml
collected 698 items / 4 skipped

venv/lib64/python3.13/site-packages/trio/_core/_tests/test_asyncgen.py . [  0%]
......                                                                   [  1%]
venv/lib64/python3.13/site-packages/trio/_core/_tests/test_exceptiongroup_gc.py . [  1%]
.                                                                        [  1%]
venv/lib64/python3.13/site-packages/trio/_core/_tests/test_guest_mode.py . [  1%]
.............                                                            [  3%]
venv/lib64/python3.13/site-packages/trio/_core/_tests/test_instrumentation.py . [  3%]
.......                                                                  [  4%]
venv/lib64/python3.13/site-packages/trio/_core/_tests/test_io.py ....... [  5%]
.....................                                                    [  8%]
venv/lib64/python3.13/site-packages/trio/_core/_tests/test_ki.py .F..s.. [  9%]
F...                                                                     [ 10%]
venv/lib64/python3.13/site-packages/trio/_core/_tests/test_local.py .... [ 10%]
                                                                         [ 10%]
venv/lib64/python3.13/site-packages/trio/_core/_tests/test_mock_clock.py . [ 10%]
....s                                                                    [ 11%]
venv/lib64/python3.13/site-packages/trio/_core/_tests/test_parking_lot.py . [ 11%]
...                                                                      [ 12%]
venv/lib64/python3.13/site-packages/trio/_core/_tests/test_run.py ...... [ 12%]
..........................s............................................. [ 23%]
.....................................................................    [ 33%]
venv/lib64/python3.13/site-packages/trio/_core/_tests/test_thread_cache.py . [ 33%]
.ss..                                                                    [ 33%]
venv/lib64/python3.13/site-packages/trio/_core/_tests/test_tutil.py .    [ 34%]
venv/lib64/python3.13/site-packages/trio/_core/_tests/test_unbounded_queue.py . [ 34%]
....                                                                     [ 34%]
venv/lib64/python3.13/site-packages/trio/_core/_tests/test_windows.py ss [ 35%]
sssss                                                                    [ 35%]
venv/lib64/python3.13/site-packages/trio/_tests/test_abc.py ...          [ 36%]
venv/lib64/python3.13/site-packages/trio/_tests/test_channel.py ........ [ 37%]
.....                                                                    [ 38%]
venv/lib64/python3.13/site-packages/trio/_tests/test_contextvars.py ...  [ 38%]
venv/lib64/python3.13/site-packages/trio/_tests/test_deprecate.py ...... [ 39%]
......                                                                   [ 40%]
venv/lib64/python3.13/site-packages/trio/_tests/test_deprecate_strict_exception_groups_false.py . [ 40%]
..                                                                       [ 40%]
venv/lib64/python3.13/site-packages/trio/_tests/test_exports.py .sssssss [ 41%]
sssssssssssssssssssssssssssssssssss..                                    [ 47%]
venv/lib64/python3.13/site-packages/trio/_tests/test_fakenet.py ....s... [ 48%]
.                                                                        [ 48%]
venv/lib64/python3.13/site-packages/trio/_tests/test_file_io.py ........ [ 49%]
.........                                                                [ 50%]
venv/lib64/python3.13/site-packages/trio/_tests/test_highlevel_generic.py . [ 51%]
.                                                                        [ 51%]
venv/lib64/python3.13/site-packages/trio/_tests/test_highlevel_open_tcp_listeners.py . [ 51%]
...................                                                      [ 54%]
venv/lib64/python3.13/site-packages/trio/_tests/test_highlevel_open_tcp_stream.py . [ 54%]
......................                                                   [ 57%]
venv/lib64/python3.13/site-packages/trio/_tests/test_highlevel_open_unix_stream.py . [ 57%]
....s                                                                    [ 58%]
venv/lib64/python3.13/site-packages/trio/_tests/test_highlevel_serve_listeners.py . [ 58%]
...                                                                      [ 58%]
venv/lib64/python3.13/site-packages/trio/_tests/test_highlevel_socket.py . [ 58%]
......                                                                   [ 59%]
venv/lib64/python3.13/site-packages/trio/_tests/test_path.py .s......... [ 61%]
.......F....................                                             [ 65%]
venv/lib64/python3.13/site-packages/trio/_tests/test_repl.py .........   [ 66%]
venv/lib64/python3.13/site-packages/trio/_tests/test_scheduler_determinism.py . [ 66%]
.                                                                        [ 66%]
venv/lib64/python3.13/site-packages/trio/_tests/test_signals.py ........ [ 68%]
                                                                         [ 68%]
venv/lib64/python3.13/site-packages/trio/_tests/test_socket.py .......s. [ 69%]
.........................                                                [ 72%]
venv/lib64/python3.13/site-packages/trio/_tests/test_subprocess.py ..... [ 73%]
...............s........                                                 [ 77%]
venv/lib64/python3.13/site-packages/trio/_tests/test_sync.py ........... [ 78%]
.......................                                                  [ 81%]
venv/lib64/python3.13/site-packages/trio/_tests/test_testing.py ........ [ 83%]
...........                                                              [ 84%]
venv/lib64/python3.13/site-packages/trio/_tests/test_testing_raisesgroup.py . [ 84%]
.............                                                            [ 86%]
venv/lib64/python3.13/site-packages/trio/_tests/test_threads.py F.F..... [ 87%]
.....................................s....                               [ 93%]
venv/lib64/python3.13/site-packages/trio/_tests/test_timeouts.py sss.    [ 94%]
venv/lib64/python3.13/site-packages/trio/_tests/test_tracing.py ..       [ 94%]
venv/lib64/python3.13/site-packages/trio/_tests/test_unix_pipes.py ..... [ 95%]
......                                                                   [ 96%]
venv/lib64/python3.13/site-packages/trio/_tests/test_util.py .........   [ 97%]
venv/lib64/python3.13/site-packages/trio/_tests/test_wait_for_object.py s [ 97%]
sss                                                                      [ 98%]
venv/lib64/python3.13/site-packages/trio/_tests/test_windows_pipes.py ss [ 98%]
ssss                                                                     [ 98%]
venv/lib64/python3.13/site-packages/trio/_tests/tools/test_mypy_annotate.py . [ 99%]
......                                                                   [100%]

=================================== FAILURES ===================================
_______________________________ test_ki_enabled ________________________________

    async def test_ki_enabled() -> None:
        # Regular tasks aren't KI-protected
        assert not _core.currently_ki_protected()

        # Low-level call-soon callbacks are KI-protected
        token = _core.current_trio_token()
        record = []

        def check() -> None:
            record.append(_core.currently_ki_protected())

        token.run_sync_soon(check)
        await wait_all_tasks_blocked()
        assert record == [True]

        @_core.enable_ki_protection
        def protected() -> None:
            assert _core.currently_ki_protected()
            unprotected()

        @_core.disable_ki_protection
        def unprotected() -> None:
            assert not _core.currently_ki_protected()

>       protected()

venv/lib64/python3.13/site-packages/trio/_core/_tests/test_ki.py:62: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
venv/lib64/python3.13/site-packages/trio/_core/_ki.py:182: in wrapper
    return fn(*args, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    @_core.enable_ki_protection
    def protected() -> None:
>       assert _core.currently_ki_protected()
E       assert False
E        +  where False = <function currently_ki_protected at 0x7fd1217f2340>()
E        +    where <function currently_ki_protected at 0x7fd1217f2340> = _core.currently_ki_protected

venv/lib64/python3.13/site-packages/trio/_core/_tests/test_ki.py:55: AssertionError
___________________________ test_ki_disabled_in_del ____________________________

    def test_ki_disabled_in_del() -> None:
        def nestedfunction() -> bool:
            return _core.currently_ki_protected()

        def __del__() -> None:
            assert _core.currently_ki_protected()
            assert nestedfunction()

        @_core.disable_ki_protection
        def outerfunction() -> None:
            assert not _core.currently_ki_protected()
            assert not nestedfunction()
            __del__()

        __del__()
>       outerfunction()

venv/lib64/python3.13/site-packages/trio/_core/_tests/test_ki.py:255: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
venv/lib64/python3.13/site-packages/trio/_core/_ki.py:182: in wrapper
    return fn(*args, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    @_core.disable_ki_protection
    def outerfunction() -> None:
>       assert not _core.currently_ki_protected()
E       assert not True
E        +  where True = <function currently_ki_protected at 0x7fd1217f2340>()
E        +    where <function currently_ki_protected at 0x7fd1217f2340> = _core.currently_ki_protected

venv/lib64/python3.13/site-packages/trio/_core/_tests/test_ki.py:250: AssertionError
_________________________ test_async_method_signature __________________________

path = trio.Path('/tmp/pytest-of-churchyard/pytest-6/test_async_method_signature0/test')

    async def test_async_method_signature(path: trio.Path) -> None:
        # use `resolve` as a representative of wrapped methods

        assert path.resolve.__name__ == "resolve"
        assert path.resolve.__qualname__ == "Path.resolve"

        assert path.resolve.__doc__ is not None
>       assert "pathlib.Path.resolve" in path.resolve.__doc__
E       AssertionError: assert 'pathlib.Path.resolve' in 'Like :meth:`~pathlib._local.Path.resolve`, but async.\n\nMake the path absolute, resolving all symlinks on the way and also\nnormalizing it.\n'
E        +  where 'Like :meth:`~pathlib._local.Path.resolve`, but async.\n\nMake the path absolute, resolving all symlinks on the way and also\nnormalizing it.\n' = <bound method Path.resolve of trio.Path('/tmp/pytest-of-churchyard/pytest-6/test_async_method_signature0/test')>.__doc__
E        +    where <bound method Path.resolve of trio.Path('/tmp/pytest-of-churchyard/pytest-6/test_async_method_signature0/test')> = trio.Path('/tmp/pytest-of-churchyard/pytest-6/test_async_method_signature0/test').resolve

venv/lib64/python3.13/site-packages/trio/_tests/test_path.py:125: AssertionError
____________________________ test_do_in_trio_thread ____________________________

    async def test_do_in_trio_thread() -> None:
        trio_thread = threading.current_thread()

        async def check_case(
            do_in_trio_thread: Callable[..., threading.Thread],
            fn: Callable[..., T | Awaitable[T]],
            expected: tuple[str, T],
            trio_token: _core.TrioToken | None = None,
        ) -> None:
            record: RecordType = []

            def threadfn() -> None:
                try:
                    record.append(("start", threading.current_thread()))
                    x = do_in_trio_thread(fn, record, trio_token=trio_token)
                    record.append(("got", x))
                except BaseException as exc:
                    print(exc)
                    record.append(("error", type(exc)))

            child_thread = threading.Thread(target=threadfn, daemon=True)
            child_thread.start()
            while child_thread.is_alive():
                print("yawn")
                await sleep(0.01)
            assert record == [("start", child_thread), ("f", trio_thread), expected]

        token = _core.current_trio_token()

        def f1(record: RecordType) -> int:
            assert not _core.currently_ki_protected()
            record.append(("f", threading.current_thread()))
            return 2

>       await check_case(from_thread_run_sync, f1, ("got", 2), trio_token=token)

venv/lib64/python3.13/site-packages/trio/_tests/test_threads.py:94: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

do_in_trio_thread = <function run_sync at 0x7fd1216ed3a0>
fn = <function test_do_in_trio_thread.<locals>.f1 at 0x7fd11f520c20>
expected = ('got', 2)
trio_token = TrioToken(_reentry_queue=EntryQueue(queue=deque([]), idempotent_queue={}, wakeup=<trio._core._wakeup_socketpair.WakeupSocketpair object at 0x7fd11eaf3890>, done=True, lock=<unlocked _thread.RLock object owner=0 count=0 at 0x7fd11f4ed2c0>))

    async def check_case(
        do_in_trio_thread: Callable[..., threading.Thread],
        fn: Callable[..., T | Awaitable[T]],
        expected: tuple[str, T],
        trio_token: _core.TrioToken | None = None,
    ) -> None:
        record: RecordType = []

        def threadfn() -> None:
            try:
                record.append(("start", threading.current_thread()))
                x = do_in_trio_thread(fn, record, trio_token=trio_token)
                record.append(("got", x))
            except BaseException as exc:
                print(exc)
                record.append(("error", type(exc)))

        child_thread = threading.Thread(target=threadfn, daemon=True)
        child_thread.start()
        while child_thread.is_alive():
            print("yawn")
            await sleep(0.01)
>       assert record == [("start", child_thread), ("f", trio_thread), expected]
E       AssertionError: assert [('start', <T...rtionError'>)] == [('start', <T...), ('got', 2)]
E         
E         At index 1 diff: ('error', <class 'AssertionError'>) != ('f', <_MainThread(MainThread, started 140536435861312)>)
E         Right contains one more item: ('got', 2)
E         Use -v to get more diff

venv/lib64/python3.13/site-packages/trio/_tests/test_threads.py:85: AssertionError
----------------------------- Captured stdout call -----------------------------
yawn
assert not True
 +  where True = <function currently_ki_protected at 0x7fd1217f2340>()
 +    where <function currently_ki_protected at 0x7fd1217f2340> = _core.currently_ki_protected
__________________________ test_run_in_trio_thread_ki __________________________

    def test_run_in_trio_thread_ki() -> None:
        # if we get a control-C during a run_in_trio_thread, then it propagates
        # back to the caller (slick!)
        record = set()

        async def check_run_in_trio_thread() -> None:
            token = _core.current_trio_token()

            def trio_thread_fn() -> None:
                print("in Trio thread")
                assert not _core.currently_ki_protected()
                print("ki_self")
                try:
                    ki_self()
                finally:
                    import sys

                    print("finally", sys.exc_info())

            async def trio_thread_afn() -> None:
                trio_thread_fn()

            def external_thread_fn() -> None:
                try:
                    print("running")
                    from_thread_run_sync(trio_thread_fn, trio_token=token)
                except KeyboardInterrupt:
                    print("ok1")
                    record.add("ok1")
                try:
                    from_thread_run(trio_thread_afn, trio_token=token)
                except KeyboardInterrupt:
                    print("ok2")
                    record.add("ok2")

            thread = threading.Thread(target=external_thread_fn)
            thread.start()
            print("waiting")
            while thread.is_alive():
                await sleep(0.01)
            print("waited, joining")
            thread.join()
            print("done")

        _core.run(check_run_in_trio_thread)
>       assert record == {"ok1", "ok2"}
E       AssertionError: assert set() == {'ok1', 'ok2'}
E         
E         Extra items in the right set:
E         'ok2'
E         'ok1'
E         Use -v to get more diff

venv/lib64/python3.13/site-packages/trio/_tests/test_threads.py:176: AssertionError

During handling of the above exception, another exception occurred:

cls = <class '_pytest.runner.CallInfo'>
func = <function call_and_report.<locals>.<lambda> at 0x7fd11f690a40>
when = 'call'
reraise = (<class '_pytest.outcomes.Exit'>, <class 'KeyboardInterrupt'>)

    @classmethod
    def from_call(
        cls,
        func: Callable[[], TResult],
        when: Literal["collect", "setup", "call", "teardown"],
        reraise: Optional[
            Union[Type[BaseException], Tuple[Type[BaseException], ...]]
        ] = None,
    ) -> "CallInfo[TResult]":
        """Call func, wrapping the result in a CallInfo.

        :param func:
            The function to call. Called without arguments.
        :param when:
            The phase in which the function is called.
        :param reraise:
            Exception or exceptions that shall propagate if raised by the
            function, instead of being wrapped in the CallInfo.
        """
        excinfo = None
        start = timing.time()
        precise_start = timing.perf_counter()
        try:
>           result: Optional[TResult] = func()

venv/lib64/python3.13/site-packages/_pytest/runner.py:341: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
venv/lib64/python3.13/site-packages/_pytest/runner.py:241: in <lambda>
    lambda: runtest_hook(item=item, **kwds), when=when, reraise=reraise
venv/lib64/python3.13/site-packages/pluggy/_hooks.py:513: in __call__
    return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
venv/lib64/python3.13/site-packages/pluggy/_manager.py:120: in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
venv/lib64/python3.13/site-packages/_pytest/threadexception.py:87: in pytest_runtest_call
    yield from thread_exception_runtest_hook()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def thread_exception_runtest_hook() -> Generator[None, None, None]:
        with catch_threading_exception() as cm:
            try:
                yield
            finally:
                if cm.args:
                    thread_name = (
                        "<unknown>" if cm.args.thread is None else cm.args.thread.name
                    )
                    msg = f"Exception in thread {thread_name}\n\n"
                    msg += "".join(
                        traceback.format_exception(
                            cm.args.exc_type,
                            cm.args.exc_value,
                            cm.args.exc_traceback,
                        )
                    )
>                   warnings.warn(pytest.PytestUnhandledThreadExceptionWarning(msg))
E                   pytest.PytestUnhandledThreadExceptionWarning: Exception in thread Thread-3 (external_thread_fn)
E                   
E                   Traceback (most recent call last):
E                     File "/usr/lib64/python3.13/threading.py", line 1039, in _bootstrap_inner
E                       self.run()
E                       ~~~~~~~~^^
E                     File "/usr/lib64/python3.13/threading.py", line 990, in run
E                       self._target(*self._args, **self._kwargs)
E                       ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E                     File ".../trio/venv/lib64/python3.13/site-packages/trio/_tests/test_threads.py", line 156, in external_thread_fn
E                       from_thread_run_sync(trio_thread_fn, trio_token=token)
E                       ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E                     File ".../trio/venv/lib64/python3.13/site-packages/trio/_threads.py", line 632, in from_thread_run_sync
E                       return _send_message_to_trio(trio_token, RunSync(fn, args))
E                     File ".../trio/venv/lib64/python3.13/site-packages/trio/_threads.py", line 550, in _send_message_to_trio
E                       return message_to_trio.queue.get().unwrap()
E                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
E                     File ".../trio/venv/lib64/python3.13/site-packages/outcome/_impl.py", line 213, in unwrap
E                       raise captured_error
E                     File ".../trio/venv/lib64/python3.13/site-packages/trio/_core/_ki.py", line 182, in wrapper
E                       return fn(*args, **kwargs)
E                     File ".../trio/venv/lib64/python3.13/site-packages/trio/_threads.py", line 217, in unprotected_fn
E                       ret = self.context.run(self.fn, *self.args)
E                     File ".../trio/venv/lib64/python3.13/site-packages/trio/_tests/test_threads.py", line 141, in trio_thread_fn
E                       assert not _core.currently_ki_protected()
E                   AssertionError: assert not True
E                    +  where True = <function currently_ki_protected at 0x7fd1217f2340>()
E                    +    where <function currently_ki_protected at 0x7fd1217f2340> = _core.currently_ki_protected

venv/lib64/python3.13/site-packages/_pytest/threadexception.py:77: PytestUnhandledThreadExceptionWarning
----------------------------- Captured stdout call -----------------------------
runningwaiting

in Trio thread
waited, joining
done
=========================== short test summary info ============================
FAILED venv/lib64/python3.13/site-packages/trio/_core/_tests/test_ki.py::test_ki_enabled
FAILED venv/lib64/python3.13/site-packages/trio/_core/_tests/test_ki.py::test_ki_disabled_in_del
FAILED venv/lib64/python3.13/site-packages/trio/_tests/test_path.py::test_async_method_signature
FAILED venv/lib64/python3.13/site-packages/trio/_tests/test_threads.py::test_do_in_trio_thread
FAILED venv/lib64/python3.13/site-packages/trio/_tests/test_threads.py::test_run_in_trio_thread_ki
================== 5 failed, 620 passed, 77 skipped in 3.29s ===================
hroncok commented 1 month ago
E       AssertionError: assert 'pathlib.Path.resolve' in 'Like :meth:`~pathlib._local.Path.resolve`, but async.\n\nMake the path absolute, resolving all symlinks on the way and also\nnormalizing it.\n'

This failure is easy to understand for me.

The rest, not so much.

A5rocks commented 1 month ago

Interesting, I don't remember the KI changes in the alpha! Maybe worth bisecting to figure out what exactly changed?

hroncok commented 1 month ago

Let me restest with a6... 625 passed, 77 skipped in 9.85s

Working on git bisest ...

A5rocks commented 1 month ago

I bisected and KI seems to be failing due to PEP 667 (specifically https://github.com/python/cpython/pull/115153). Which kind of makes sense because we stuff KI protection metadata in weird function places, but I'm still surprised.

hroncok commented 1 month ago

The Pathlib module name thing is https://github.com/python/cpython/commit/d8d94911e2393bd30ca58a32b33d792307fdc00d

A5rocks commented 1 month ago

OK, so KI is cause this no longer works: Before:

>>> import sys
>>> def g():
...   print(sys._getframe(1).f_locals)
...
>>> def f():
...   locals()["name"] = True
...   g()
...
>>> f()
{'name': True}
>>> exit()

Now:

>>> import sys
>>> def g():
...   print(sys._getframe(1).f_locals)
...
>>> def f():
...   locals()["name"] = True
...   g()
...
>>> f()
{}
>>> exit()

This is documented so we just need to fix this. I think one way would be to assign to sys._getframe().f_locals instead of locals().