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

tests/test_run_app.py::TestShutdown hang on py3.13 with aiohappyeyeballs == 2.4.2 #9325

Closed
1 task done
mgorny opened this issue Sep 28, 2024 · 16 comments · Fixed by #9326
Closed
1 task done

tests/test_run_app.py::TestShutdown hang on py3.13 with aiohappyeyeballs == 2.4.2 #9325

mgorny opened this issue Sep 28, 2024 · 16 comments · Fixed by #9326
Labels

Comments

@mgorny
Copy link
Contributor

mgorny commented Sep 28, 2024

Describe the bug

After upgrading to aiohappyeyeballs == 2.4.2, the following four tests started hanging with Python 3.13.0rc2:

tests/test_run_app.py::TestShutdown::test_shutdown_new_conn_rejected
tests/test_run_app.py::TestShutdown::test_shutdown_timeout_handler
tests/test_run_app.py::TestShutdown::test_shutdown_timeout_not_reached
tests/test_run_app.py::TestShutdown::test_shutdown_wait_for_handler

After downgrading to aiohappyeyeballs == 2.4.0, all tests pass again.

FWICS CI hit it already in #9310, but I think you can't easily guess which tests hanged from its output.

To Reproduce

  1. Upgrade to aiohappyeyeballs == 2.4.2.
  2. Run tests with Python 3.13.

Expected behavior

Tests passing :-).

Logs/tracebacks

(this is after SIGTERM-ing hanging tests)

========================================================= test session starts =========================================================
platform linux -- Python 3.13.0rc2, pytest-8.3.3, pluggy-1.5.0 -- /tmp/portage/dev-python/aiohttp-3.10.7/work/aiohttp-3.10.7-python3_13/install/usr/bin/python3.13
cachedir: .pytest_cache
rootdir: /tmp/portage/dev-python/aiohttp-3.10.7/work/aiohttp-3.10.7
configfile: setup.cfg
collecting ... collected 8 items

tests/test_run_app.py::TestShutdown::test_shutdown_wait_for_handler FAILED                                                       [1/8]
tests/test_run_app.py::TestShutdown::test_shutdown_timeout_handler FAILED                                                        [2/8]
tests/test_run_app.py::TestShutdown::test_shutdown_timeout_not_reached FAILED                                                    [3/8]
tests/test_run_app.py::TestShutdown::test_shutdown_new_conn_rejected FAILED                                                      [4/8]
tests/test_run_app.py::TestShutdown::test_shutdown_pending_handler_responds PASSED                                               [5/8]
tests/test_run_app.py::TestShutdown::test_shutdown_close_idle_keepalive PASSED                                                   [6/8]
tests/test_run_app.py::TestShutdown::test_shutdown_close_websockets PASSED                                                       [7/8]
tests/test_run_app.py::TestShutdown::test_shutdown_handler_cancellation_suppressed PASSED                                        [8/8]

============================================================== FAILURES ===============================================================
_____________________________________________ TestShutdown.test_shutdown_wait_for_handler _____________________________________________

self = <test_run_app.TestShutdown object at 0x7f9d96c1c190>, aiohttp_unused_port = <function unused_port at 0x7f9d9725d3a0>

    def test_shutdown_wait_for_handler(
        self, aiohttp_unused_port: Callable[[], int]
    ) -> None:
        port = aiohttp_unused_port()
        finished = False
    
        async def task():
            nonlocal finished
            await asyncio.sleep(2)
            finished = True
    
>       t, connection_count = self.run_app(port, 3, task)

aiohttp_unused_port = <function unused_port at 0x7f9d9725d3a0>
finished   = True
port       = 38969
self       = <test_run_app.TestShutdown object at 0x7f9d96c1c190>
task       = <function TestShutdown.test_shutdown_wait_for_handler.<locals>.task at 0x7f9d968a7920>

tests/test_run_app.py:1007: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <test_run_app.TestShutdown object at 0x7f9d96c1c190>, port = 38969, timeout = 3
task = <function TestShutdown.test_shutdown_wait_for_handler.<locals>.task at 0x7f9d968a7920>, extra_test = None

    def run_app(self, port: int, timeout: int, task, extra_test=None) -> asyncio.Task:
        num_connections = -1
    
        class DictRecordClear(dict):
            def clear(self):
                nonlocal num_connections
                # During Server.shutdown() we want to know how many connections still
                # remained before it got cleared. If the handler completed successfully
                # the connection should've been removed already. If not, this may
                # indicate a memory leak.
                num_connections = len(self)
                super().clear()
    
        class ServerWithRecordClear(web.Server):
            def __init__(self, *args, **kwargs):
                super().__init__(*args, **kwargs)
                self._connections = DictRecordClear()
    
        async def test() -> None:
            await asyncio.sleep(0.5)
            async with ClientSession() as sess:
                for _ in range(5):  # pragma: no cover
                    try:
                        with pytest.raises(asyncio.TimeoutError):
                            async with sess.get(
                                f"http://localhost:{port}/",
                                timeout=ClientTimeout(total=0.1),
                            ):
                                pass
                    except ClientConnectorError:
                        await asyncio.sleep(0.5)
                    else:
                        break
                async with sess.get(f"http://localhost:{port}/stop"):
                    pass
    
                if extra_test:
                    await extra_test(sess)
    
        async def run_test(app: web.Application) -> None:
            nonlocal test_task
            test_task = asyncio.create_task(test())
            yield
            await test_task
    
        async def handler(request: web.Request) -> web.Response:
            nonlocal t
            t = asyncio.create_task(task())
            await t
            return web.Response(text="FOO")
    
        t = test_task = None
        app = web.Application()
        app.cleanup_ctx.append(run_test)
        app.router.add_get("/", handler)
        app.router.add_get("/stop", self.stop)
    
        with mock.patch("aiohttp.web_app.Server", ServerWithRecordClear):
            web.run_app(app, port=port, shutdown_timeout=timeout)
>       assert test_task.exception() is None
E       asyncio.exceptions.CancelledError

DictRecordClear = <class 'test_run_app.TestShutdown.run_app.<locals>.DictRecordClear'>
ServerWithRecordClear = <class 'test_run_app.TestShutdown.run_app.<locals>.ServerWithRecordClear'>
app        = <Application 0x7f9d96c18ec0>
extra_test = None
handler    = <function TestShutdown.run_app.<locals>.handler at 0x7f9d968a7ce0>
num_connections = 0
port       = 38969
run_test   = <function TestShutdown.run_app.<locals>.run_test at 0x7f9d968a7c40>
self       = <test_run_app.TestShutdown object at 0x7f9d96c1c190>
t          = <Task finished name='Task-10' coro=<TestShutdown.test_shutdown_wait_for_handler.<locals>.task() done, defined at /tmp/portage/dev-python/aiohttp-3.10.7/work/aiohttp-3.10.7/tests/test_run_app.py:1002> result=None>
task       = <function TestShutdown.test_shutdown_wait_for_handler.<locals>.task at 0x7f9d968a7920>
test       = <function TestShutdown.run_app.<locals>.test at 0x7f9d968a79c0>
test_task  = <Task cancelled name='Task-2' coro=<TestShutdown.run_app.<locals>.test() done, defined at /tmp/portage/dev-python/aiohttp-3.10.7/work/aiohttp-3.10.7/tests/test_run_app.py:952>>
timeout    = 3

tests/test_run_app.py:993: CancelledError
-------------------------------------------------------- Captured stdout call ---------------------------------------------------------
======== Running on http://0.0.0.0:38969 ========
(Press CTRL+C to quit)
_____________________________________________ TestShutdown.test_shutdown_timeout_handler ______________________________________________

self = <test_run_app.TestShutdown object at 0x7f9d96c1cb90>, aiohttp_unused_port = <function unused_port at 0x7f9d9725d3a0>

    def test_shutdown_timeout_handler(
        self, aiohttp_unused_port: Callable[[], int]
    ) -> None:
        port = aiohttp_unused_port()
        finished = False
    
        async def task():
            nonlocal finished
            await asyncio.sleep(2)
            finished = True
    
>       t, connection_count = self.run_app(port, 1, task)

aiohttp_unused_port = <function unused_port at 0x7f9d9725d3a0>
finished   = True
port       = 45001
self       = <test_run_app.TestShutdown object at 0x7f9d96c1cb90>
task       = <function TestShutdown.test_shutdown_timeout_handler.<locals>.task at 0x7f9d967e5940>

tests/test_run_app.py:1025: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <test_run_app.TestShutdown object at 0x7f9d96c1cb90>, port = 45001, timeout = 1
task = <function TestShutdown.test_shutdown_timeout_handler.<locals>.task at 0x7f9d967e5940>, extra_test = None

    def run_app(self, port: int, timeout: int, task, extra_test=None) -> asyncio.Task:
        num_connections = -1
    
        class DictRecordClear(dict):
            def clear(self):
                nonlocal num_connections
                # During Server.shutdown() we want to know how many connections still
                # remained before it got cleared. If the handler completed successfully
                # the connection should've been removed already. If not, this may
                # indicate a memory leak.
                num_connections = len(self)
                super().clear()
    
        class ServerWithRecordClear(web.Server):
            def __init__(self, *args, **kwargs):
                super().__init__(*args, **kwargs)
                self._connections = DictRecordClear()
    
        async def test() -> None:
            await asyncio.sleep(0.5)
            async with ClientSession() as sess:
                for _ in range(5):  # pragma: no cover
                    try:
                        with pytest.raises(asyncio.TimeoutError):
                            async with sess.get(
                                f"http://localhost:{port}/",
                                timeout=ClientTimeout(total=0.1),
                            ):
                                pass
                    except ClientConnectorError:
                        await asyncio.sleep(0.5)
                    else:
                        break
                async with sess.get(f"http://localhost:{port}/stop"):
                    pass
    
                if extra_test:
                    await extra_test(sess)
    
        async def run_test(app: web.Application) -> None:
            nonlocal test_task
            test_task = asyncio.create_task(test())
            yield
            await test_task
    
        async def handler(request: web.Request) -> web.Response:
            nonlocal t
            t = asyncio.create_task(task())
            await t
            return web.Response(text="FOO")
    
        t = test_task = None
        app = web.Application()
        app.cleanup_ctx.append(run_test)
        app.router.add_get("/", handler)
        app.router.add_get("/stop", self.stop)
    
        with mock.patch("aiohttp.web_app.Server", ServerWithRecordClear):
            web.run_app(app, port=port, shutdown_timeout=timeout)
>       assert test_task.exception() is None
E       asyncio.exceptions.CancelledError

DictRecordClear = <class 'test_run_app.TestShutdown.run_app.<locals>.DictRecordClear'>
ServerWithRecordClear = <class 'test_run_app.TestShutdown.run_app.<locals>.ServerWithRecordClear'>
app        = <Application 0x7f9d96dbfed0>
extra_test = None
handler    = <function TestShutdown.run_app.<locals>.handler at 0x7f9d967e5440>
num_connections = 0
port       = 45001
run_test   = <function TestShutdown.run_app.<locals>.run_test at 0x7f9d967e5300>
self       = <test_run_app.TestShutdown object at 0x7f9d96c1cb90>
t          = <Task finished name='Task-25' coro=<TestShutdown.test_shutdown_timeout_handler.<locals>.task() done, defined at /tmp/portage/dev-python/aiohttp-3.10.7/work/aiohttp-3.10.7/tests/test_run_app.py:1020> result=None>
task       = <function TestShutdown.test_shutdown_timeout_handler.<locals>.task at 0x7f9d967e5940>
test       = <function TestShutdown.run_app.<locals>.test at 0x7f9d967e51c0>
test_task  = <Task cancelled name='Task-17' coro=<TestShutdown.run_app.<locals>.test() done, defined at /tmp/portage/dev-python/aiohttp-3.10.7/work/aiohttp-3.10.7/tests/test_run_app.py:952>>
timeout    = 1

tests/test_run_app.py:993: CancelledError
-------------------------------------------------------- Captured stdout call ---------------------------------------------------------
======== Running on http://0.0.0.0:45001 ========
(Press CTRL+C to quit)
___________________________________________ TestShutdown.test_shutdown_timeout_not_reached ____________________________________________

self = <test_run_app.TestShutdown object at 0x7f9d96d3ba80>, aiohttp_unused_port = <function unused_port at 0x7f9d9725d3a0>

    def test_shutdown_timeout_not_reached(
        self, aiohttp_unused_port: Callable[[], int]
    ) -> None:
        port = aiohttp_unused_port()
        finished = False
    
        async def task():
            nonlocal finished
            await asyncio.sleep(1)
            finished = True
    
        start_time = time.time()
>       t, connection_count = self.run_app(port, 15, task)

aiohttp_unused_port = <function unused_port at 0x7f9d9725d3a0>
finished   = True
port       = 58897
self       = <test_run_app.TestShutdown object at 0x7f9d96d3ba80>
start_time = 1727516905.0695245
task       = <function TestShutdown.test_shutdown_timeout_not_reached.<locals>.task at 0x7f9d967e5760>

tests/test_run_app.py:1044: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <test_run_app.TestShutdown object at 0x7f9d96d3ba80>, port = 58897, timeout = 15
task = <function TestShutdown.test_shutdown_timeout_not_reached.<locals>.task at 0x7f9d967e5760>, extra_test = None

    def run_app(self, port: int, timeout: int, task, extra_test=None) -> asyncio.Task:
        num_connections = -1
    
        class DictRecordClear(dict):
            def clear(self):
                nonlocal num_connections
                # During Server.shutdown() we want to know how many connections still
                # remained before it got cleared. If the handler completed successfully
                # the connection should've been removed already. If not, this may
                # indicate a memory leak.
                num_connections = len(self)
                super().clear()
    
        class ServerWithRecordClear(web.Server):
            def __init__(self, *args, **kwargs):
                super().__init__(*args, **kwargs)
                self._connections = DictRecordClear()
    
        async def test() -> None:
            await asyncio.sleep(0.5)
            async with ClientSession() as sess:
                for _ in range(5):  # pragma: no cover
                    try:
                        with pytest.raises(asyncio.TimeoutError):
                            async with sess.get(
                                f"http://localhost:{port}/",
                                timeout=ClientTimeout(total=0.1),
                            ):
                                pass
                    except ClientConnectorError:
                        await asyncio.sleep(0.5)
                    else:
                        break
                async with sess.get(f"http://localhost:{port}/stop"):
                    pass
    
                if extra_test:
                    await extra_test(sess)
    
        async def run_test(app: web.Application) -> None:
            nonlocal test_task
            test_task = asyncio.create_task(test())
            yield
            await test_task
    
        async def handler(request: web.Request) -> web.Response:
            nonlocal t
            t = asyncio.create_task(task())
            await t
            return web.Response(text="FOO")
    
        t = test_task = None
        app = web.Application()
        app.cleanup_ctx.append(run_test)
        app.router.add_get("/", handler)
        app.router.add_get("/stop", self.stop)
    
        with mock.patch("aiohttp.web_app.Server", ServerWithRecordClear):
            web.run_app(app, port=port, shutdown_timeout=timeout)
>       assert test_task.exception() is None
E       asyncio.exceptions.CancelledError

DictRecordClear = <class 'test_run_app.TestShutdown.run_app.<locals>.DictRecordClear'>
ServerWithRecordClear = <class 'test_run_app.TestShutdown.run_app.<locals>.ServerWithRecordClear'>
app        = <Application 0x7f9d96c1d6d0>
extra_test = None
handler    = <function TestShutdown.run_app.<locals>.handler at 0x7f9d967e5e40>
num_connections = 0
port       = 58897
run_test   = <function TestShutdown.run_app.<locals>.run_test at 0x7f9d967e5d00>
self       = <test_run_app.TestShutdown object at 0x7f9d96d3ba80>
t          = <Task finished name='Task-40' coro=<TestShutdown.test_shutdown_timeout_not_reached.<locals>.task() done, defined at /tmp/portage/dev-python/aiohttp-3.10.7/work/aiohttp-3.10.7/tests/test_run_app.py:1038> result=None>
task       = <function TestShutdown.test_shutdown_timeout_not_reached.<locals>.task at 0x7f9d967e5760>
test       = <function TestShutdown.run_app.<locals>.test at 0x7f9d967e5bc0>
test_task  = <Task cancelled name='Task-32' coro=<TestShutdown.run_app.<locals>.test() done, defined at /tmp/portage/dev-python/aiohttp-3.10.7/work/aiohttp-3.10.7/tests/test_run_app.py:952>>
timeout    = 15

tests/test_run_app.py:993: CancelledError
-------------------------------------------------------- Captured stdout call ---------------------------------------------------------
======== Running on http://0.0.0.0:58897 ========
(Press CTRL+C to quit)
____________________________________________ TestShutdown.test_shutdown_new_conn_rejected _____________________________________________

self = <test_run_app.TestShutdown object at 0x7f9d96890e90>, aiohttp_unused_port = <function unused_port at 0x7f9d9725d3a0>

    def test_shutdown_new_conn_rejected(
        self, aiohttp_unused_port: Callable[[], int]
    ) -> None:
        port = aiohttp_unused_port()
        finished = False
    
        async def task() -> None:
            nonlocal finished
            await asyncio.sleep(9)
            finished = True
    
        async def test(sess: ClientSession) -> None:
            # Ensure we are in the middle of shutdown (waiting for task()).
            await asyncio.sleep(1)
            with pytest.raises(ClientConnectorError):
                # Use a new session to try and open a new connection.
                async with ClientSession() as sess:
                    async with sess.get(f"http://localhost:{port}/"):
                        pass
            assert finished is False
    
>       t, connection_count = self.run_app(port, 10, task, test)

aiohttp_unused_port = <function unused_port at 0x7f9d9725d3a0>
finished   = True
port       = 48985
self       = <test_run_app.TestShutdown object at 0x7f9d96890e90>
task       = <function TestShutdown.test_shutdown_new_conn_rejected.<locals>.task at 0x7f9d967e5a80>
test       = <function TestShutdown.test_shutdown_new_conn_rejected.<locals>.test at 0x7f9d967e62a0>

tests/test_run_app.py:1073: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <test_run_app.TestShutdown object at 0x7f9d96890e90>, port = 48985, timeout = 10
task = <function TestShutdown.test_shutdown_new_conn_rejected.<locals>.task at 0x7f9d967e5a80>
extra_test = <function TestShutdown.test_shutdown_new_conn_rejected.<locals>.test at 0x7f9d967e62a0>

    def run_app(self, port: int, timeout: int, task, extra_test=None) -> asyncio.Task:
        num_connections = -1
    
        class DictRecordClear(dict):
            def clear(self):
                nonlocal num_connections
                # During Server.shutdown() we want to know how many connections still
                # remained before it got cleared. If the handler completed successfully
                # the connection should've been removed already. If not, this may
                # indicate a memory leak.
                num_connections = len(self)
                super().clear()
    
        class ServerWithRecordClear(web.Server):
            def __init__(self, *args, **kwargs):
                super().__init__(*args, **kwargs)
                self._connections = DictRecordClear()
    
        async def test() -> None:
            await asyncio.sleep(0.5)
            async with ClientSession() as sess:
                for _ in range(5):  # pragma: no cover
                    try:
                        with pytest.raises(asyncio.TimeoutError):
                            async with sess.get(
                                f"http://localhost:{port}/",
                                timeout=ClientTimeout(total=0.1),
                            ):
                                pass
                    except ClientConnectorError:
                        await asyncio.sleep(0.5)
                    else:
                        break
                async with sess.get(f"http://localhost:{port}/stop"):
                    pass
    
                if extra_test:
                    await extra_test(sess)
    
        async def run_test(app: web.Application) -> None:
            nonlocal test_task
            test_task = asyncio.create_task(test())
            yield
            await test_task
    
        async def handler(request: web.Request) -> web.Response:
            nonlocal t
            t = asyncio.create_task(task())
            await t
            return web.Response(text="FOO")
    
        t = test_task = None
        app = web.Application()
        app.cleanup_ctx.append(run_test)
        app.router.add_get("/", handler)
        app.router.add_get("/stop", self.stop)
    
        with mock.patch("aiohttp.web_app.Server", ServerWithRecordClear):
            web.run_app(app, port=port, shutdown_timeout=timeout)
>       assert test_task.exception() is None
E       asyncio.exceptions.CancelledError

DictRecordClear = <class 'test_run_app.TestShutdown.run_app.<locals>.DictRecordClear'>
ServerWithRecordClear = <class 'test_run_app.TestShutdown.run_app.<locals>.ServerWithRecordClear'>
app        = <Application 0x7f9d96891940>
extra_test = <function TestShutdown.test_shutdown_new_conn_rejected.<locals>.test at 0x7f9d967e62a0>
handler    = <function TestShutdown.run_app.<locals>.handler at 0x7f9d967e5120>
num_connections = 0
port       = 48985
run_test   = <function TestShutdown.run_app.<locals>.run_test at 0x7f9d967e6520>
self       = <test_run_app.TestShutdown object at 0x7f9d96890e90>
t          = <Task finished name='Task-55' coro=<TestShutdown.test_shutdown_new_conn_rejected.<locals>.task() done, defined at /tmp/portage/dev-python/aiohttp-3.10.7/work/aiohttp-3.10.7/tests/test_run_app.py:1058> result=None>
task       = <function TestShutdown.test_shutdown_new_conn_rejected.<locals>.task at 0x7f9d967e5a80>
test       = <function TestShutdown.run_app.<locals>.test at 0x7f9d967e6480>
test_task  = <Task cancelled name='Task-47' coro=<TestShutdown.run_app.<locals>.test() done, defined at /tmp/portage/dev-python/aiohttp-3.10.7/work/aiohttp-3.10.7/tests/test_run_app.py:952>>
timeout    = 10

tests/test_run_app.py:993: CancelledError
-------------------------------------------------------- Captured stdout call ---------------------------------------------------------
======== Running on http://0.0.0.0:48985 ========
(Press CTRL+C to quit)
======================================================== slowest 10 durations =========================================================
85.24s call     tests/test_run_app.py::TestShutdown::test_shutdown_wait_for_handler
9.51s call     tests/test_run_app.py::TestShutdown::test_shutdown_new_conn_rejected
6.62s call     tests/test_run_app.py::TestShutdown::test_shutdown_timeout_handler
4.37s call     tests/test_run_app.py::TestShutdown::test_shutdown_timeout_not_reached
4.01s call     tests/test_run_app.py::TestShutdown::test_shutdown_pending_handler_responds
2.41s call     tests/test_run_app.py::TestShutdown::test_shutdown_handler_cancellation_suppressed
1.01s call     tests/test_run_app.py::TestShutdown::test_shutdown_close_websockets
1.01s call     tests/test_run_app.py::TestShutdown::test_shutdown_close_idle_keepalive
0.00s teardown tests/test_run_app.py::TestShutdown::test_shutdown_timeout_handler
0.00s setup    tests/test_run_app.py::TestShutdown::test_shutdown_timeout_handler
======================================================= short test summary info =======================================================
FAILED tests/test_run_app.py::TestShutdown::test_shutdown_wait_for_handler - asyncio.exceptions.CancelledError
FAILED tests/test_run_app.py::TestShutdown::test_shutdown_timeout_handler - asyncio.exceptions.CancelledError
FAILED tests/test_run_app.py::TestShutdown::test_shutdown_timeout_not_reached - asyncio.exceptions.CancelledError
FAILED tests/test_run_app.py::TestShutdown::test_shutdown_new_conn_rejected - asyncio.exceptions.CancelledError
=============================================== 4 failed, 4 passed in 114.53s (0:01:54) ===============================================

Python Version

$ python3.13 --version
Python 3.13.0rc2

aiohttp Version

$ python -m pip show aiohttp
Name: aiohttp
Version: 3.10.7
Summary: Async http client/server framework (asyncio)
Home-page: https://github.com/aio-libs/aiohttp
Author: 
Author-email: 
License: Apache 2
Location: /usr/lib/python3.13/site-packages
Requires: aiohappyeyeballs, aiosignal, attrs, frozenlist, multidict, yarl
Required-by: aiohttp-cors, aiohttp-oauthlib, aiohttp_socks, aioresponses, vdirsyncer

multidict Version

$ python -m pip show multidict
Name: multidict
Version: 6.1.0
Summary: multidict implementation
Home-page: https://github.com/aio-libs/multidict
Author: Andrew Svetlov
Author-email: andrew.svetlov@gmail.com
License: Apache 2
Location: /usr/lib/python3.13/site-packages
Requires: 
Required-by: aiohttp, grpclib, yarl

yarl Version

$ python -m pip show yarl
Name: yarl
Version: 1.13.1
Summary: Yet another URL library
Home-page: https://github.com/aio-libs/yarl
Author: Andrew Svetlov
Author-email: andrew.svetlov@gmail.com
License: Apache-2.0
Location: /usr/lib/python3.13/site-packages
Requires: idna, multidict
Required-by: aiohttp, vcrpy

OS

Gentoo Linux amd64

Related component

Server

Additional context

No response

Code of Conduct

  • I agree to follow the aio-libs Code of Conduct
@mgorny mgorny added the bug label Sep 28, 2024
@mgorny
Copy link
Contributor Author

mgorny commented Sep 28, 2024

Ah, sorry, just noticed it was mentioned already in #8599 (comment).

@Dreamsorcerer
Copy link
Member

Yeah, this is already reproducing in CI and bdraco is looking at it.

@bdraco
Copy link
Member

bdraco commented Sep 28, 2024

I haven't had much luck with this one yet. It looks like there may be a change in cancellation semantics with python 3.13 or its just a race.

@bdraco
Copy link
Member

bdraco commented Sep 28, 2024

maybe python/cpython#117407

@bdraco
Copy link
Member

bdraco commented Sep 28, 2024

['parent_cancel_requested', True]
['parent_task.cancelling()', 1]

@bdraco
Copy link
Member

bdraco commented Sep 28, 2024

If I revert python/cpython#117407 the problem goes away

@bdraco
Copy link
Member

bdraco commented Sep 28, 2024

When the task group exits the parent task is getting cancelled

@bdraco
Copy link
Member

bdraco commented Sep 28, 2024

I think the cancellation is somehow leaking upwards in 3.13...

@bdraco
Copy link
Member

bdraco commented Sep 28, 2024

In DataQueue.read the _waiter is cancelled and it raises and at that point cancelling() is 1

@bdraco
Copy link
Member

bdraco commented Sep 28, 2024

Maybe its that the cancellation is raised sooner in 3.13 ?

@bdraco
Copy link
Member

bdraco commented Sep 28, 2024

Looks like it is our bug #9326

@mgorny
Copy link
Contributor Author

mgorny commented Sep 28, 2024

Thanks a lot!

@bdraco
Copy link
Member

bdraco commented Sep 28, 2024

I'll try to find time to do another patch release this weekend so this doesn't bite users when they update to 3.13 on Tuesday #9326 (comment)

@mgorny
Copy link
Contributor Author

mgorny commented Sep 28, 2024

@bdraco, for the record, it seems that the release has been deferred to October 7th. Not that I wouldn't like to see a fix ASAP :-).

@bdraco
Copy link
Member

bdraco commented Sep 28, 2024

@bdraco, for the record, it seems that the release has been deferred to October 7th. Not that I wouldn't like to see a fix ASAP :-).

Thanks for the heads up.

I'll be traveling that week, and I also have a busy week with my day job coming up so I still plan on getting a new release done ASAP.

@bdraco
Copy link
Member

bdraco commented Sep 28, 2024

3.10.8 published with the fix

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants