Skip to content

Commit 54567ac

Browse files
Requests which raise a PoolTimeout need to be removed from the pool queue. (#502)
* Requests which raise a PoolTimeout need to be removed from the pool queue. * Update CHANGELOG.md
1 parent b8cc3d5 commit 54567ac

File tree

5 files changed

+79
-6
lines changed

5 files changed

+79
-6
lines changed

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
66

7-
## 0.14.7
7+
## master
88

9-
- Fix AttributeError that happened when Socks5Connection were terminated
9+
- Requests which raise a PoolTimeout need to be removed from the pool queue. (#502)
10+
- Fix AttributeError that happened when Socks5Connection were terminated. (#501)
1011

1112
## 0.14.6 (February 1st, 2022)
1213

httpcore/_async/connection_pool.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,16 @@ async def handle_async_request(self, request: Request) -> Response:
223223
while True:
224224
timeouts = request.extensions.get("timeout", {})
225225
timeout = timeouts.get("pool", None)
226-
connection = await status.wait_for_connection(timeout=timeout)
226+
try:
227+
connection = await status.wait_for_connection(timeout=timeout)
228+
except BaseException as exc:
229+
# If we timeout here, or if the task is cancelled, then make
230+
# sure to remove the request from the queue before bubbling
231+
# up the exception.
232+
async with self._pool_lock:
233+
self._requests.remove(status)
234+
raise exc
235+
227236
try:
228237
response = await connection.handle_async_request(request)
229238
except ConnectionNotAvailable:

httpcore/_sync/connection_pool.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,16 @@ def handle_request(self, request: Request) -> Response:
223223
while True:
224224
timeouts = request.extensions.get("timeout", {})
225225
timeout = timeouts.get("pool", None)
226-
connection = status.wait_for_connection(timeout=timeout)
226+
try:
227+
connection = status.wait_for_connection(timeout=timeout)
228+
except BaseException as exc:
229+
# If we timeout here, or if the task is cancelled, then make
230+
# sure to remove the request from the queue before bubbling
231+
# up the exception.
232+
with self._pool_lock:
233+
self._requests.remove(status)
234+
raise exc
235+
227236
try:
228237
response = connection.handle_request(request)
229238
except ConnectionNotAvailable:

tests/_async/test_connection_pool.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import pytest
44
import trio as concurrency
55

6-
from httpcore import AsyncConnectionPool, ConnectError, UnsupportedProtocol
6+
from httpcore import AsyncConnectionPool, ConnectError, PoolTimeout, UnsupportedProtocol
77
from httpcore.backends.mock import AsyncMockBackend
88

99

@@ -461,3 +461,30 @@ async def test_connection_pool_closed_while_request_in_flight():
461461
async with pool.stream("GET", "https://example.com/"):
462462
with pytest.raises(RuntimeError):
463463
await pool.aclose()
464+
465+
466+
@pytest.mark.anyio
467+
async def test_connection_pool_timeout():
468+
"""
469+
Ensure that exceeding max_connections can cause a request to timeout.
470+
"""
471+
network_backend = AsyncMockBackend(
472+
[
473+
b"HTTP/1.1 200 OK\r\n",
474+
b"Content-Type: plain/text\r\n",
475+
b"Content-Length: 13\r\n",
476+
b"\r\n",
477+
b"Hello, world!",
478+
]
479+
)
480+
481+
async with AsyncConnectionPool(
482+
network_backend=network_backend, max_connections=1
483+
) as pool:
484+
# Send a request to a pool that is configured to only support a single
485+
# connection, and then ensure that a second concurrent request
486+
# fails with a timeout.
487+
async with pool.stream("GET", "https://example.com/"):
488+
with pytest.raises(PoolTimeout):
489+
extensions = {"timeout": {"pool": 0.0001}}
490+
await pool.request("GET", "https://example.com/", extensions=extensions)

tests/_sync/test_connection_pool.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import pytest
44
from tests import concurrency
55

6-
from httpcore import ConnectionPool, ConnectError, UnsupportedProtocol
6+
from httpcore import ConnectionPool, ConnectError, PoolTimeout, UnsupportedProtocol
77
from httpcore.backends.mock import MockBackend
88

99

@@ -461,3 +461,30 @@ def test_connection_pool_closed_while_request_in_flight():
461461
with pool.stream("GET", "https://example.com/"):
462462
with pytest.raises(RuntimeError):
463463
pool.close()
464+
465+
466+
467+
def test_connection_pool_timeout():
468+
"""
469+
Ensure that exceeding max_connections can cause a request to timeout.
470+
"""
471+
network_backend = MockBackend(
472+
[
473+
b"HTTP/1.1 200 OK\r\n",
474+
b"Content-Type: plain/text\r\n",
475+
b"Content-Length: 13\r\n",
476+
b"\r\n",
477+
b"Hello, world!",
478+
]
479+
)
480+
481+
with ConnectionPool(
482+
network_backend=network_backend, max_connections=1
483+
) as pool:
484+
# Send a request to a pool that is configured to only support a single
485+
# connection, and then ensure that a second concurrent request
486+
# fails with a timeout.
487+
with pool.stream("GET", "https://example.com/"):
488+
with pytest.raises(PoolTimeout):
489+
extensions = {"timeout": {"pool": 0.0001}}
490+
pool.request("GET", "https://example.com/", extensions=extensions)

0 commit comments

Comments
 (0)