Skip to content

Commit 01c26d7

Browse files
committed
BlockingConnectionPool simplification: use counter instead of connection list
1 parent 428d609 commit 01c26d7

File tree

1 file changed

+47
-38
lines changed

1 file changed

+47
-38
lines changed

redis/asyncio/connection.py

Lines changed: 47 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1237,6 +1237,19 @@ class ConnectionPool:
12371237
``connection_class``.
12381238
"""
12391239

1240+
__slots__ = (
1241+
"connection_class",
1242+
"connection_kwargs",
1243+
"max_connections",
1244+
"_fork_lock",
1245+
"_lock",
1246+
"_created_connections",
1247+
"_available_connections",
1248+
"_in_use_connections",
1249+
"encoder_class",
1250+
"pid",
1251+
)
1252+
12401253
@classmethod
12411254
def from_url(cls: Type[_CP], url: str, **kwargs) -> _CP:
12421255
"""
@@ -1519,36 +1532,34 @@ class BlockingConnectionPool(ConnectionPool):
15191532
>>> pool = BlockingConnectionPool(timeout=5)
15201533
"""
15211534

1535+
__slots__ = (
1536+
"queue_class",
1537+
"timeout",
1538+
"pool",
1539+
)
1540+
15221541
def __init__(
15231542
self,
15241543
max_connections: int = 50,
15251544
timeout: Optional[int] = 20,
15261545
connection_class: Type[Connection] = Connection,
1527-
queue_class: Type[asyncio.Queue] = asyncio.LifoQueue,
1546+
queue_class: Type[asyncio.Queue] = asyncio.Queue,
15281547
**connection_kwargs,
15291548
):
15301549

15311550
self.queue_class = queue_class
15321551
self.timeout = timeout
1533-
self._connections: List[Connection]
15341552
super().__init__(
15351553
connection_class=connection_class,
15361554
max_connections=max_connections,
15371555
**connection_kwargs,
15381556
)
15391557

15401558
def reset(self):
1541-
# Create and fill up a thread safe queue with ``None`` values.
1559+
# a queue of ready connections. populated lazily
15421560
self.pool = self.queue_class(self.max_connections)
1543-
while True:
1544-
try:
1545-
self.pool.put_nowait(None)
1546-
except asyncio.QueueFull:
1547-
break
1548-
1549-
# Keep a list of actual connection instances so that we can
1550-
# disconnect them later.
1551-
self._connections = []
1561+
# used to decide wether we can allocate new connection or wait
1562+
self._created_connections = 0
15521563

15531564
# this must be the last operation in this method. while reset() is
15541565
# called when holding _fork_lock, other threads in this process
@@ -1562,41 +1573,36 @@ def reset(self):
15621573
self.pid = os.getpid()
15631574

15641575
def make_connection(self):
1565-
"""Make a fresh connection."""
1566-
connection = self.connection_class(**self.connection_kwargs)
1567-
self._connections.append(connection)
1568-
return connection
1576+
"""Create a new connection"""
1577+
self._created_connections += 1
1578+
return self.connection_class(**self.connection_kwargs)
15691579

15701580
async def get_connection(self, command_name, *keys, **options):
15711581
"""
15721582
Get a connection, blocking for ``self.timeout`` until a connection
15731583
is available from the pool.
15741584
1575-
If the connection returned is ``None`` then creates a new connection.
1576-
Because we use a last-in first-out queue, the existing connections
1577-
(having been returned to the pool after the initial ``None`` values
1578-
were added) will be returned before ``None`` values. This means we only
1579-
create new connections when we need to, i.e.: the actual number of
1580-
connections will only increase in response to demand.
1585+
Checks internal connection counter to ensure connections are allocated lazily.
15811586
"""
15821587
# Make sure we haven't changed process.
15831588
self._checkpid()
15841589

1585-
# Try and get a connection from the pool. If one isn't available within
1586-
# self.timeout then raise a ``ConnectionError``.
1587-
connection = None
1588-
try:
1589-
async with async_timeout.timeout(self.timeout):
1590-
connection = await self.pool.get()
1591-
except (asyncio.QueueEmpty, asyncio.TimeoutError):
1592-
# Note that this is not caught by the redis client and will be
1593-
# raised unless handled by application code. If you want never to
1594-
raise ConnectionError("No connection available.")
1595-
1596-
# If the ``connection`` is actually ``None`` then that's a cue to make
1597-
# a new connection to add to the pool.
1598-
if connection is None:
1599-
connection = self.make_connection()
1590+
# if we are under max_connections, try getting one immediately. if it fails
1591+
# it is ok to allocate new one
1592+
if self._created_connections < self.max_connections:
1593+
try:
1594+
connection = self.pool.get_nowait()
1595+
except asyncio.QueueEmpty:
1596+
connection = self.make_connection()
1597+
else:
1598+
# wait for available connection
1599+
try:
1600+
async with async_timeout.timeout(self.timeout):
1601+
connection = await self.pool.get()
1602+
except asyncio.TimeoutError:
1603+
# Note that this is not caught by the redis client and will be
1604+
# raised unless handled by application code.
1605+
raise ConnectionError("No connection available.")
16001606

16011607
try:
16021608
# ensure this connection is connected to Redis
@@ -1646,7 +1652,10 @@ async def disconnect(self, inuse_connections: bool = True):
16461652
self._checkpid()
16471653
async with self._lock:
16481654
resp = await asyncio.gather(
1649-
*(connection.disconnect() for connection in self._connections),
1655+
*(
1656+
self.pool.get_nowait().disconnect()
1657+
for _ in range(self.pool.qsize())
1658+
),
16501659
return_exceptions=True,
16511660
)
16521661
exc = next((r for r in resp if isinstance(r, BaseException)), None)

0 commit comments

Comments
 (0)