Raise FullPoolError when _put_conn() is called on full blocking pool
MODIFY
CONTRIBUTORS.txt
CONTRIBUTORS.txt
@@ -309,5 +309,8 @@ In chronological order:
* [Franciszek Magiera] <https://github.com/franekmagiera>
* Add top-level request method.
+* Dmitry Mazin <https://archvile.net>
+ * _put_conn changes and unit tests
+
* [Your name or handle] <[email or website]>
* [Brief summary of your changes]

MODIFY
src/urllib3/connectionpool.py
src/urllib3/connectionpool.py
@@ -19,6 +19,7 @@ from .connection import (
from .exceptions import (
ClosedPoolError,
EmptyPoolError,
+ FullPoolError,
HeaderParsingError,
HostChangedError,
HTTPSProxyError,
@@ -246,7 +247,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
if self.block:
raise EmptyPoolError(
self,
- "Pool reached maximum size and no more connections are allowed.",
+ "Pool is empty and a new connection can't be opened due to blocking mode.",
)
pass # Oh well, we'll create a new connection then
@@ -283,7 +284,18 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
# self.pool is None.
pass
except queue.Full:
- # This should never happen if self.block == True
+
+ # Connection never got put back into the pool, close it.
+ if conn:
+ conn.close()
+
+ if self.block:
+ # This should never happen if you got the conn from self._get_conn
+ raise FullPoolError(
+ self,
+ "Pool reached maximum size and no more connections are allowed.",
+ )
+
log.warning("Connection pool is full, discarding connection: %s", self.host)
# Connection never got put back into the pool, close it.

MODIFY
src/urllib3/exceptions.py
src/urllib3/exceptions.py
@@ -169,6 +169,12 @@ class EmptyPoolError(PoolError):
pass
+class FullPoolError(PoolError):
+ """Raised when we try to add a connection to a full pool in blocking mode."""
+
+ pass
+
+
class ClosedPoolError(PoolError):
"""Raised when a request enters a pool after the pool has been closed."""

MODIFY
test/test_connectionpool.py
test/test_connectionpool.py
@@ -20,6 +20,7 @@ from urllib3.connectionpool import (
from urllib3.exceptions import (
ClosedPoolError,
EmptyPoolError,
+ FullPoolError,
HostChangedError,
LocationValueError,
MaxRetryError,
@@ -223,19 +224,66 @@ class TestConnectionPool:
assert pool.num_connections == 1
- def test_pool_edgecases(self):
+ def test_put_conn_when_pool_is_full_nonblocking(self):
+ """
+ If maxsize = n and we _put_conn n + 1 conns, the n + 1th conn will
+ get closed and will not get added to the pool.
+ """
with HTTPConnectionPool(host="localhost", maxsize=1, block=False) as pool:
conn1 = pool._get_conn()
- conn2 = pool._get_conn() # New because block=False
+ # pool.pool is empty because we popped the one None that pool.pool was initialized with
+ # but this pool._get_conn call will not raise EmptyPoolError because block is False
+ conn2 = pool._get_conn()
+
+ conn1.close = Mock()
+ conn2.close = Mock()
pool._put_conn(conn1)
- pool._put_conn(conn2) # Should be discarded
+ pool._put_conn(conn2)
+
+ assert conn1.close.called is False
+ assert conn2.close.called is True
assert conn1 == pool._get_conn()
assert conn2 != pool._get_conn()
assert pool.num_connections == 3
+ def test_put_conn_when_pool_is_full_blocking(self):
+ """
+ If maxsize = n and we _put_conn n + 1 conns, the n + 1th conn will
+ cause a FullPoolError.
+ """
+ with HTTPConnectionPool(host="localhost", maxsize=1, block=True) as pool:
+ conn1 = pool._get_conn()
+ conn2 = pool._new_conn()
+
+ conn1.close = Mock()
+ conn2.close = Mock()
+
+ pool._put_conn(conn1)
+ with pytest.raises(FullPoolError):
+ pool._put_conn(conn2)
+
+ assert conn1.close.called is False
+ assert conn2.close.called is True
+
+ assert conn1 == pool._get_conn()
+
+ def test_put_conn_closed_pool(self):
+ with HTTPConnectionPool(host="localhost", maxsize=1, block=True) as pool:
+ conn1 = pool._get_conn()
+ conn1.close = Mock()
+
+ pool.close()
+ assert pool.pool is None
+
+ # Accessing pool.pool will raise AttributeError, which will get
+ # caught and will close conn1
+ pool._put_conn(conn1)
+
+ assert conn1.close.called is True
+
def test_exception_str(self):
assert (
str(EmptyPoolError(HTTPConnectionPool(host="localhost"), "Test."))

Raise FullPoolError when _put_conn() is called on full blocking pool