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

Error on multiple event loops? #190

Closed
deli-c1ous opened this issue Dec 19, 2024 · 1 comment · Fixed by #191
Closed

Error on multiple event loops? #190

deli-c1ous opened this issue Dec 19, 2024 · 1 comment · Fixed by #191

Comments

@deli-c1ous
Copy link

Hi, I'm testing niquests's AsyncSession's performance while encountering a error. Could you take a look at it and tell me how to fix it please? Thank you!

Expected Result

no error

Actual Result

the output:

run async_client_http2_test_main
run async_client_multiplexed_http2_test_main
Traceback (most recent call last):
  File "D:\myfiles\projects\python\test4.py", line 30, in <module>
    async_client_multiplexed_http2_test_main()
  File "D:\myfiles\projects\python\test4.py", line 24, in async_client_multiplexed_http2_test_main
    asyncio.run(async_client_multiplexed_http2_test())
  File "D:\myfiles\softwares\miniconda\Lib\asyncio\runners.py", line 194, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "D:\myfiles\softwares\miniconda\Lib\asyncio\runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\myfiles\softwares\miniconda\Lib\asyncio\base_events.py", line 686, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "D:\myfiles\projects\python\test4.py", line 21, in async_client_multiplexed_http2_test
    await asyncio.gather(*tasks)
  File "D:\myfiles\softwares\miniconda\Lib\site-packages\niquests\_async.py", line 923, in get
    return await self.request(  # type: ignore[call-overload,misc]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\myfiles\softwares\miniconda\Lib\site-packages\niquests\_async.py", line 866, in request
    return await self.send(prep, **send_kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\myfiles\softwares\miniconda\Lib\site-packages\niquests\_async.py", line 491, in send
    r = await adapter.send(request, **kwargs)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\myfiles\softwares\miniconda\Lib\site-packages\niquests\adapters.py", line 2031, in send
    resp_or_promise = await conn.urlopen(  # type: ignore[call-overload,misc]
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\myfiles\softwares\miniconda\Lib\site-packages\urllib3\_async\connectionpool.py", line 1737, in urlopen
    response = await self._make_request(  # type: ignore[call-overload,misc]
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\myfiles\softwares\miniconda\Lib\site-packages\urllib3\_async\connectionpool.py", line 1283, in _make_request
    await on_post_connection(conn.conn_info)
  File "D:\myfiles\softwares\miniconda\Lib\site-packages\niquests\_async.py", line 394, in on_post_connection
    await ocsp_verify(
  File "D:\myfiles\softwares\miniconda\Lib\site-packages\niquests\extensions\_async_ocsp.py", line 333, in verify
    async with _SharedRevocationStatusCache.lock(peer_certificate):
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\myfiles\softwares\miniconda\Lib\contextlib.py", line 210, in __aenter__
    return await anext(self.gen)
           ^^^^^^^^^^^^^^^^^^^^^
  File "D:\myfiles\softwares\miniconda\Lib\site-packages\niquests\extensions\_async_ocsp.py", line 217, in lock
    await self._semaphores[fingerprint].acquire()
  File "D:\myfiles\softwares\miniconda\Lib\asyncio\locks.py", line 378, in acquire
    fut = self._get_loop().create_future()
          ^^^^^^^^^^^^^^^^
  File "D:\myfiles\softwares\miniconda\Lib\asyncio\mixins.py", line 20, in _get_loop
    raise RuntimeError(f'{self!r} is bound to a different event loop')
RuntimeError: <asyncio.locks.Semaphore object at 0x000001D5E43AA330 [locked]> is bound to a different event loop

Reproduction Steps

import asyncio
import niquests

test_url = "https://httpbin.org/get"
async_test_count = 30


def async_client_http2_test_main():
    async def async_client_http2_test():
        async with niquests.AsyncSession() as s:
            tasks = [s.get(test_url) for _ in range(async_test_count)]
            await asyncio.gather(*tasks)

    asyncio.run(async_client_http2_test())


def async_client_multiplexed_http2_test_main():
    async def async_client_multiplexed_http2_test():
        async with niquests.AsyncSession(multiplexed=True) as s:
            tasks = [s.get(test_url) for _ in range(async_test_count)]
            await asyncio.gather(*tasks)
            await s.gather()

    asyncio.run(async_client_multiplexed_http2_test())


print('run async_client_http2_test_main')
async_client_http2_test_main()
print('run async_client_multiplexed_http2_test_main')
async_client_multiplexed_http2_test_main()

System Information

{
  "charset_normalizer": {
    "version": "3.4.0"
  },
  "http1": {
    "h11": "0.14.0"
  },
  "http2": {
    "jh2": "5.0.4"
  },
  "http3": {
    "enabled": true,
    "qh3": "1.2.1"
  },
  "idna": {
    "version": "3.10"
  },
  "implementation": {
    "name": "CPython",
    "version": "3.12.8"
  },
  "niquests": {
    "version": "3.11.3"
  },
  "ocsp": {
    "enabled": true
  },
  "platform": {
    "release": "11",
    "system": "Windows"
  },
  "system_ssl": {
    "name": "OpenSSL 3.0.15 3 Sep 2024",
    "version": "300000f0"
  },
  "urllib3.future": {
    "cohabitation_version": null,
    "version": "2.12.903"
  },
  "wassima": {
    "certifi_fallback": false,
    "enabled": true,
    "version": "1.1.5"
  },
  "websocket": {
    "enabled": true,
    "wsproto": "1.2.0"
  }
}
@Ousret
Copy link
Member

Ousret commented Dec 23, 2024

The error you encountered should not occurs in presented reproducer. We worked a fix that should prevent this.
It will be available in the next version.

But. Something here caught our attention, you are invoking asyncio.run multiple times. This is not recommended.
Although this is permitted, the official docs state that it is unwise to call this function more than once.

this function should be used as a main entry point for asyncio programs, and should ideally only be called once.

Moreover you are using httpbin.org endpoint that is known to be overloaded and unmaintained, you won't get reliable performance results using it.

Here is what we suggest as a replacement for your script:

import asyncio
import niquests

test_url = "https://httpbingo.org/get"
async_test_count = 30


async def async_client_http2_test():
    async with niquests.AsyncSession() as s:
        await asyncio.gather(
            *[s.get(test_url) for _ in range(async_test_count)]
        )


async def async_client_multiplexed_http2_test():
    async with niquests.AsyncSession(multiplexed=True) as s:
        await asyncio.gather(
            *[s.get(test_url) for _ in range(async_test_count)]
        )
        await s.gather()


async def main() -> None:
    print('run async_client_http2_test_main')
    before = asyncio.get_running_loop().time()
    await async_client_http2_test()
    print("result", asyncio.get_running_loop().time() - before)

    print('run async_client_multiplexed_http2_test_main')
    before = asyncio.get_running_loop().time()
    await async_client_multiplexed_http2_test()
    print("result", asyncio.get_running_loop().time() - before)


if __name__ == "__main__":
    asyncio.run(main())

Hope that clarify,
Regards,

@Ousret Ousret closed this as completed Dec 23, 2024
Ousret added a commit that referenced this issue Dec 23, 2024
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 a pull request may close this issue.

2 participants