You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Keepalive connections aren't released when closing the ConnectionPool #720
Hello. I am having an issue where it looks like connections aren't being closed correctly, and after i reach a number of requests equivalent to "hard_limit" of pool_limits, i get a PoolTimeout exception.
I tried upgrading to httpx==0.10.1, with no success.
Minimal example:
import httpx, asyncio, logging
from httpx import PoolLimits
from random import randint
queue = asyncio.Queue()
clients = [
httpx.AsyncClient(
http2=True,
pool_limits=PoolLimits(soft_limit=2, hard_limit=10),
cookies={'a': '123456789', 'b': '987654321'},
)
]
async def worker_loop(cid, client, queue):
while 1:
sub_id = await queue.get()
async with client as c:
r = await c.get(f'https://mywebsite.dummy/submission.php?id={sub_id}')
if r.status_code != 200:
print(cid, f'Got status code {r.status_code} while parsing {sub_id}')
return
async def main():
for i in range(2500):
await queue.put(randint(1, 80000000))
for k, v in enumerate(clients):
asyncio.create_task(worker_loop(k, v, queue))
while 1:
if queue.qsize() == 0:
await queue.put(randint(1, 80000000))
await asyncio.sleep(2)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.stop()
I checked with netstat, and only one actual connection is opened to the IP address, so pooling seems to work fine.
I really cannot understand why. I even tried using the "aclose()" syntax, without the "async with" block, but no difference at all.
only one actual connection is opened to the IP address, so pooling seems to work fine
I don't think that's actually the behavior you'd expect (though, from your code that's the behavior one should expect): since requests are concurrent here, HTTPX will use as many connections as it can to perform the requests, i.e. up to 10 (the hard limit). Once one of the 10 first requests terminates, the connection can be reused by a subsequent request.
So what you'd expect is to have 10 connections almost constantly being used, but you only get one, plus a pool timeout…
The async with client is what causes the connection pool to be setup, and then teardown. So in practice, you are tearing down the connection pool after each request, meaning you're only ever able to use one connection (because any previous connection would have been thrown away by closing the pool after the previous request terminated).
What you need to do for the connection pool to be effectively used, is to move the async with block at the top-level:
(Note that if we switch out for _ in range() and async with client as suggested above, there's no error.)
I pinpointed the origin of the issue to us not releasing the internal max_connections semaphore when closing keepalive connections when closing the connection pool. PR incoming!
Oh, I thought instantiating the client opened the connection pool, and you'd need to use "async with" to let the pool know you wanted a connection. Please close this issue if you feel like it. Thank you!
@florimondmanca is this truly fixed? i'm running into something that seems similar, yet i'm using version 0.13.3 which looks to contain the fix. specifically, if the number of tasks executed via asyncio.gather(...) is greater than max_connections, i get a PoolTimeout. it seems like maybe this is happening because the tasks that have completed aren't releasing their connections upon completion?
that said, i'm fairly new to asyncio to it's possible i'm doing something wrong. here's an example that throws a PoolTimeout:
Activity
florimondmanca commentedon Jan 4, 2020
I don't think that's actually the behavior you'd expect (though, from your code that's the behavior one should expect): since requests are concurrent here, HTTPX will use as many connections as it can to perform the requests, i.e. up to 10 (the hard limit). Once one of the 10 first requests terminates, the connection can be reused by a subsequent request.
So what you'd expect is to have 10 connections almost constantly being used, but you only get one, plus a pool timeout…
Your code is similar in spirit to this:
The
async with clientis what causes the connection pool to be setup, and then teardown. So in practice, you are tearing down the connection pool after each request, meaning you're only ever able to use one connection (because any previous connection would have been thrown away by closing the pool after the previous request terminated).What you need to do for the connection pool to be effectively used, is to move the
async withblock at the top-level:With this I am able to run your program without issues against a local dummy server.
But that doesn't explain the pool timeout…
I am able to reproduce the timeout issue with this example:
(Note that if we switch out
for _ in range()andasync with clientas suggested above, there's no error.)I pinpointed the origin of the issue to us not releasing the internal
max_connectionssemaphore when closing keepalive connections when closing the connection pool. PR incoming![-]Connections don't get closed[/-][+]Keepalive connections aren't released when closing the ConnectionPool[/+]edocod1 commentedon Jan 4, 2020
Oh, I thought instantiating the client opened the connection pool, and you'd need to use "async with" to let the pool know you wanted a connection. Please close this issue if you feel like it. Thank you!
zeldrinn commentedon Aug 11, 2020
@florimondmanca is this truly fixed? i'm running into something that seems similar, yet i'm using version
0.13.3which looks to contain the fix. specifically, if the number of tasks executed viaasyncio.gather(...)is greater thanmax_connections, i get aPoolTimeout. it seems like maybe this is happening because the tasks that have completed aren't releasing their connections upon completion?that said, i'm fairly new to
asyncioto it's possible i'm doing something wrong. here's an example that throws aPoolTimeout: