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

RuntimeError: await wasn't used with future #106429

Closed
Dreamsorcerer opened this issue Jul 4, 2023 · 14 comments
Closed

RuntimeError: await wasn't used with future #106429

Dreamsorcerer opened this issue Jul 4, 2023 · 14 comments
Labels
pending The issue will be closed if no feedback is provided topic-asyncio type-bug An unexpected behavior, bug, or error

Comments

@Dreamsorcerer
Copy link
Contributor

Dreamsorcerer commented Jul 4, 2023

This seems like a really weird one. Original issue is aio-libs/aiohttp#7117

I can reproduce easily with:

git clone https://github.com/DevilXD/TwitchDropsMiner.git
cd TwitchDropsMiner/
SSL_CERT_FILE=/dev/null SSL_CERT_DIR=/dev/null python3 main.py

It seems like something must have messed up the loop in some way to cause it to fail at this point.

I believe the exception comes from https://github.com/python/cpython/blob/main/Modules/_asynciomodule.c#L1631
But, I'm not familiar with the C side of things to understand what happened.

An interesting detail is that I played around to try and figure out what was happening, and it doesn't seem to be related to the particular future being awaited on, it must be something that happens before it. I added an asyncio.sleep() call just before the future that was giving the error, and now the error occurs inside the sleep() call. Note the sleep() at the end of this traceback:

06:33:25 PM: Traceback (most recent call last):
06:33:25 PM:   File "/home/ubuntu/desktop/TwitchDropsMiner/main.py", line 174, in <module>
06:33:25 PM:     loop.run_until_complete(client.run())
06:33:25 PM:   File "/usr/lib/python3.10/asyncio/base_events.py", line 646, in run_until_complete
06:33:25 PM:     return future.result()
06:33:25 PM:   File "/home/ubuntu/desktop/TwitchDropsMiner/twitch.py", line 768, in run
06:33:25 PM:     await self._run()
06:33:25 PM:   File "/home/ubuntu/desktop/TwitchDropsMiner/twitch.py", line 787, in _run
06:33:25 PM:     auth_state = await self.get_auth()
06:33:25 PM:   File "/home/ubuntu/desktop/TwitchDropsMiner/twitch.py", line 1458, in get_auth
06:33:25 PM:     await self._auth_state.validate()
06:33:25 PM:   File "/home/ubuntu/desktop/TwitchDropsMiner/twitch.py", line 516, in validate
06:33:25 PM:     await self._validate()
06:33:25 PM:   File "/home/ubuntu/desktop/TwitchDropsMiner/twitch.py", line 525, in _validate
06:33:25 PM:     async with self._twitch.request(
06:33:25 PM:   File "/usr/lib/python3.10/contextlib.py", line 199, in __aenter__
06:33:25 PM:     return await anext(self.gen)
06:33:25 PM:   File "/home/ubuntu/desktop/TwitchDropsMiner/twitch.py", line 1483, in request
06:33:25 PM:     response = await self.gui.coro_unless_closed(
06:33:25 PM:   File "/home/ubuntu/desktop/TwitchDropsMiner/gui.py", line 2054, in coro_unless_closed
06:33:25 PM:     return await next(iter(done))
06:33:25 PM:   File "/home/ubuntu/hacking/aiohttp/aiohttp/client.py", line 1103, in send
06:33:25 PM:     return self._coro.send(arg)
06:33:25 PM:   File "/home/ubuntu/hacking/aiohttp/aiohttp/client.py", line 511, in _request
06:33:25 PM:     conn = await self._connector.connect(
06:33:25 PM:   File "/home/ubuntu/hacking/aiohttp/aiohttp/connector.py", line 517, in connect
06:33:25 PM:     proto = await self._create_connection(req, traces, timeout)
06:33:25 PM:   File "/home/ubuntu/hacking/aiohttp/aiohttp/connector.py", line 883, in _create_connection
06:33:25 PM:     _, proto = await self._create_direct_connection(req, traces, timeout)
06:33:25 PM:   File "/home/ubuntu/hacking/aiohttp/aiohttp/connector.py", line 1119, in _create_direct_connection
06:33:25 PM:     transp, proto = await self._wrap_create_connection(
06:33:25 PM:   File "/home/ubuntu/hacking/aiohttp/aiohttp/connector.py", line 966, in _wrap_create_connection
06:33:25 PM:     return await x  # type: ignore[return-value]  # noqa
06:33:25 PM:   File "/usr/lib/python3.10/asyncio/base_events.py", line 1049, in create_connection
06:33:25 PM:     sock = await self._connect_sock(
06:33:25 PM:   File "/usr/lib/python3.10/asyncio/base_events.py", line 960, in _connect_sock
06:33:25 PM:     await self.sock_connect(sock, address)
06:33:25 PM:   File "/usr/lib/python3.10/asyncio/selector_events.py", line 501, in sock_connect
06:33:25 PM:     await sleep(.1)
06:33:25 PM:   File "/usr/lib/python3.10/asyncio/tasks.py", line 605, in sleep
06:33:25 PM:     return await future
06:33:25 PM: RuntimeError: await wasn't used with future

Your environment

  • CPython versions tested on: 3.10
  • Operating system: Linux
@Dreamsorcerer Dreamsorcerer added the type-bug An unexpected behavior, bug, or error label Jul 4, 2023
@Dreamsorcerer
Copy link
Contributor Author

According to the original issue, the problem occurs occasionally during normal use of the app for some users. Breaking SSL is just an easy way to reproduce 100% of the time.

@gvanrossum
Copy link
Member

gvanrossum commented Jul 4, 2023

I'n not comfortable just running the suggested repro, given that the name of the project contains the word "miner".

But it seems you are. Could you try disabling the _asyncio builtin module and running it again? To do that, before any asyncio imports, do this:

import sys
assert sys.modules.get("_asyncio") is None
sys.modules["_asyncio"] = None

If it still fails, that would rule out a bug in the C accelerator module. If it then doesn't fail, we know we have to look in _asynciomodule.c.

@Dreamsorcerer
Copy link
Contributor Author

I'n not comfortable just running the suggested repro, given that the name of the project contains the word "miner".

I wouldn't base my trust on a project's name. :P I just run everything in a container. The exception happens on startup, before you configure the app to do anything, so you also don't need Twitch credentials or anything to reproduce.

Could you try disabling the _asyncio builtin module and running it again?

Yes, the issue still reproduces. I can verify that it is now raising from the Python file at:
https://github.com/python/cpython/blob/main/Lib/asyncio/futures.py#L289

Though still unsure where to look next to figure out what's happening. I assume that somehow, somewhere, a future is getting yielded from a second time even though it's not finished.

@gvanrossum
Copy link
Member

My money is on a bug in the app.

@kumaraditya303
Copy link
Contributor

From traceback:

06:33:25 PM:   File "/home/ubuntu/hacking/aiohttp/aiohttp/client.py", line 1103, in send
06:33:25 PM:     return self._coro.send(arg)

it appears aiohttp is manually sending to coroutine, if you manually manipulate the coroutine then the internal handling break and nothing can be done.

@Dreamsorcerer
Copy link
Contributor Author

Dreamsorcerer commented Jul 6, 2023

it appears aiohttp is manually sending to coroutine, if you manually manipulate the coroutine then the internal handling break and nothing can be done.

Hmm, it appears to just be a wrapper around the coroutine. I'm not sure why though. As far as I can see, aiohttp doesn't call the method itself..

https://github.com/aio-libs/aiohttp/blob/master/aiohttp/client.py#L1096

@DevilXD
Copy link
Contributor

DevilXD commented Jul 6, 2023

Together with @guihkx, we've found this to be the smallest working repro:

import asyncio
import aiohttp

async def main():
    async with aiohttp.ClientSession() as session:
        try:
            response = await asyncio.ensure_future(session.get('https://www.google.com/'))
            print(await response.text())
        finally:
            response.release()

asyncio.run(main())

This is after setting SSL_CERT_FILE=/dev/null SSL_CERT_DIR=/dev/null of course.

@Dreamsorcerer
Copy link
Contributor Author

Dreamsorcerer commented Jul 6, 2023

Hmm, doesn't reproduce for me:

Traceback (most recent call last):
  File "/home/ubuntu/hacking/aiohttp/aiohttp/connector.py", line 964, in _wrap_create_connection
    return await self._loop.create_connection(*args, **kwargs)  # type: ignore[return-value]  # noqa
  File "/usr/lib/python3.10/asyncio/base_events.py", line 1064, in create_connection
    raise exceptions[0]
  File "/usr/lib/python3.10/asyncio/base_events.py", line 1049, in create_connection
    sock = await self._connect_sock(
  File "/usr/lib/python3.10/asyncio/base_events.py", line 960, in _connect_sock
    await self.sock_connect(sock, address)
  File "/usr/lib/python3.10/asyncio/selector_events.py", line 499, in sock_connect
    return await fut
  File "/usr/lib/python3.10/asyncio/selector_events.py", line 504, in _sock_connect
    sock.connect(address)
OSError: [Errno 101] Network is unreachable

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/ubuntu/desktop/test.py", line 7, in main
    response = await asyncio.ensure_future(session.get('https://www.google.com/'))
  File "/home/ubuntu/hacking/aiohttp/aiohttp/client.py", line 1106, in throw
    self._coro.throw(arg)
  File "/home/ubuntu/hacking/aiohttp/aiohttp/client.py", line 511, in _request
    conn = await self._connector.connect(
  File "/home/ubuntu/hacking/aiohttp/aiohttp/connector.py", line 517, in connect
    proto = await self._create_connection(req, traces, timeout)
  File "/home/ubuntu/hacking/aiohttp/aiohttp/connector.py", line 883, in _create_connection
    _, proto = await self._create_direct_connection(req, traces, timeout)
  File "/home/ubuntu/hacking/aiohttp/aiohttp/connector.py", line 1147, in _create_direct_connection
    raise last_exc
  File "/home/ubuntu/hacking/aiohttp/aiohttp/connector.py", line 1117, in _create_direct_connection
    transp, proto = await self._wrap_create_connection(
  File "/home/ubuntu/hacking/aiohttp/aiohttp/connector.py", line 972, in _wrap_create_connection
    raise client_error(req.connection_key, exc) from exc
aiohttp.client_exceptions.ClientConnectorError: Cannot connect to host www.google.com:443 ssl:default [Network is unreachable]

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/ubuntu/desktop/test.py", line 12, in <module>
    asyncio.run(main())
  File "/usr/lib/python3.10/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/usr/lib/python3.10/asyncio/base_events.py", line 646, in run_until_complete
    return future.result()
  File "/home/ubuntu/desktop/test.py", line 10, in main
    response.release()
UnboundLocalError: local variable 'response' referenced before assignment

@guihkx
Copy link

guihkx commented Jul 6, 2023

Just providing some extra information I can think of, since it does reproduce on my Arch Linux machine:

  • Python 3.11.3
  • OpenSSL 3.1.1

pip freeze:

aiohttp==3.8.4
aiosignal==1.3.1
async-timeout==4.0.2
attrs==23.1.0
charset-normalizer==3.1.0
frozenlist==1.3.3
idna==3.4
multidict==6.0.4
yarl==1.9.2

Output:

$ SSL_CERT_DIR=/dev/null SSL_CERT_FILE=/dev/null python3 main.py
Traceback (most recent call last):
  File "/home/gui/dev/aiohttp-repro/main.py", line 7, in main
    response = await asyncio.ensure_future(session.get('https://www.google.com/'))
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/gui/dev/aiohttp-repro/env/lib/python3.11/site-packages/aiohttp/client.py", line 1125, in send
    return self._coro.send(arg)
           ^^^^^^^^^^^^^^^^^^^^
  File "/home/gui/dev/aiohttp-repro/env/lib/python3.11/site-packages/aiohttp/client.py", line 536, in _request
    conn = await self._connector.connect(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/gui/dev/aiohttp-repro/env/lib/python3.11/site-packages/aiohttp/connector.py", line 540, in connect
    proto = await self._create_connection(req, traces, timeout)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/gui/dev/aiohttp-repro/env/lib/python3.11/site-packages/aiohttp/connector.py", line 901, in _create_connection
    _, proto = await self._create_direct_connection(req, traces, timeout)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/gui/dev/aiohttp-repro/env/lib/python3.11/site-packages/aiohttp/connector.py", line 1175, in _create_direct_connection
    transp, proto = await self._wrap_create_connection(
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/gui/dev/aiohttp-repro/env/lib/python3.11/site-packages/aiohttp/connector.py", line 980, in _wrap_create_connection
    return await self._loop.create_connection(*args, **kwargs)  # type: ignore[return-value]  # noqa
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/asyncio/base_events.py", line 1069, in create_connection
    sock = await self._connect_sock(
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/asyncio/base_events.py", line 973, in _connect_sock
    await self.sock_connect(sock, address)
  File "/usr/lib/python3.11/asyncio/selector_events.py", line 634, in sock_connect
    return await fut
           ^^^^^^^^^
RuntimeError: await wasn't used with future

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/gui/dev/aiohttp-repro/main.py", line 12, in <module>
    asyncio.run(main())
  File "/usr/lib/python3.11/asyncio/runners.py", line 190, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/asyncio/base_events.py", line 653, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "/home/gui/dev/aiohttp-repro/main.py", line 10, in main
    response.release()
    ^^^^^^^^
UnboundLocalError: cannot access local variable 'response' where it is not associated with a value

@kumaraditya303
Copy link
Contributor

kumaraditya303 commented Jul 7, 2023

As I said earlier this has nothing to do with ssl or anything, you are breaking the handling of coroutine by sending to it manually:

import asyncio

async def main():
    async def coro():
        f = asyncio.Future()
        await f

    c = coro()
    fut = asyncio.ensure_future(c)
    c.send(None)  # Start the coroutine
    # await fut

asyncio.run(main())

This has the same error

Task exception was never retrieved
future: <Task finished name='Task-2' coro=<main.<locals>.coro() done, defined at /workspaces/cpython/main.py:4> exception=RuntimeError("await wasn't used with future")>
Traceback (most recent call last):
  File "/workspaces/cpython/main.py", line 6, in coro
    await f
RuntimeError: await wasn't used with future

@kumaraditya303 kumaraditya303 added the pending The issue will be closed if no feedback is provided label Jul 7, 2023
@DevilXD
Copy link
Contributor

DevilXD commented Jul 7, 2023

I'm confused. The repro code doesn't have anything that "sends to it manually". asyncio.ensure_future says that it returns either an asyncio.Future (as-is, if one was passed) or asyncio.Task (also as-is, but also wraps coros or awaitables into a Task). aiohttp requests have dunder methods defined, that lets the request act as both, an async context manager, and as a coroutine/awaitable. Using asyncio.ensure_future on it should be equivalent to wrapping the request in a task via asyncio.create_task, right?

Also, I don't get why it breaks when SSL handling is intentionally broken. All of this points to an aiohttp issue then? Or is it really in asyncio itself?

@Dreamsorcerer
Copy link
Contributor Author

If you look further up the thread, he's referring to a line of code in aiohttp (#106429 (comment)). But, I've no idea how that line actually gets called, I can't see anything that calls it in aiohttp itself, and the code looks like it just wraps a coroutine object, for a reason that probably only @asvetlov knows (if he can even remember from 6 years ago: aio-libs/aiohttp@439c732#diff-f334e752b4894ef951105572ab8b195aeb8db90eb6e48b1dfbd9a01da4c854f5R740).

@Dreamsorcerer
Copy link
Contributor Author

With no further information, I'll probably need to spend a day (which I don't have spare right now) breaking that wrapper in order to understand it enough to replace it. So, if nobody has any further ideas, I'll probably close this issue until I've found time to look at that in more depth.

@kumaraditya303
Copy link
Contributor

I am closing this on the basis of my above comment, I'll reopen if you provide further information.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pending The issue will be closed if no feedback is provided topic-asyncio type-bug An unexpected behavior, bug, or error
Projects
Status: Done
Development

No branches or pull requests

6 participants