Skip to content
This repository was archived by the owner on Feb 21, 2023. It is now read-only.

Commit 8230473

Browse files
authored
Merge pull request #1256 from aio-libs/fix-1103
Add auto_close_connection_pool and close_connection_pool in Redis.close()
2 parents e8ae69c + 7e47114 commit 8230473

File tree

4 files changed

+99
-1
lines changed

4 files changed

+99
-1
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ jobs:
4141
path: tests/requirements.txt
4242
- name: Run mypy
4343
run: |
44+
pip install -U setuptools
4445
pip install -r tests/requirements-mypy.txt
4546
mypy
4647
- name: Run linter

CHANGES/1256.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add auto_close_connection_pool for Redis-created connection pools, not manually created pools

aioredis/client.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -858,8 +858,16 @@ def __init__(
858858
health_check_interval: int = 0,
859859
client_name: Optional[str] = None,
860860
username: Optional[str] = None,
861+
auto_close_connection_pool: bool = True,
861862
):
862863
kwargs: Dict[str, Any]
864+
# auto_close_connection_pool only has an effect if connection_pool is
865+
# None. This is a similar feature to the missing __del__ to resolve #1103,
866+
# but it accounts for whether a user wants to manually close the connection
867+
# pool, as a similar feature to ConnectionPool's __del__.
868+
self.auto_close_connection_pool = (
869+
auto_close_connection_pool if connection_pool is None else False
870+
)
863871
if not connection_pool:
864872
kwargs = {
865873
"db": db,
@@ -1067,11 +1075,22 @@ def __del__(self, _warnings: Any = warnings) -> None:
10671075
context = {"client": self, "message": self._DEL_MESSAGE}
10681076
asyncio.get_event_loop().call_exception_handler(context)
10691077

1070-
async def close(self):
1078+
async def close(self, close_connection_pool: Optional[bool] = None) -> None:
1079+
"""
1080+
Closes Redis client connection
1081+
1082+
:param close_connection_pool: decides whether to close the connection pool used
1083+
by this Redis client, overriding Redis.auto_close_connection_pool. By default,
1084+
let Redis.auto_close_connection_pool decide whether to close the connection pool.
1085+
"""
10711086
conn = self.connection
10721087
if conn:
10731088
self.connection = None
10741089
await self.connection_pool.release(conn)
1090+
if close_connection_pool or (
1091+
close_connection_pool is None and self.auto_close_connection_pool
1092+
):
1093+
await self.connection_pool.disconnect()
10751094

10761095
# COMMAND EXECUTION AND PROTOCOL PARSING
10771096
async def execute_command(self, *args, **options):

tests/test_connection_pool.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,83 @@
1414
pytestmark = pytest.mark.asyncio
1515

1616

17+
class TestRedisAutoReleaseConnectionPool:
18+
@pytest.fixture
19+
async def r(self, create_redis) -> aioredis.Redis:
20+
"""This is necessary since r and r2 create ConnectionPools behind the scenes"""
21+
r = await create_redis()
22+
r.auto_close_connection_pool = True
23+
yield r
24+
25+
@staticmethod
26+
def get_total_connected_connections(pool):
27+
return len(pool._available_connections) + len(pool._in_use_connections)
28+
29+
@staticmethod
30+
async def create_two_conn(r: aioredis.Redis):
31+
if not r.single_connection_client: # Single already initialized connection
32+
r.connection = await r.connection_pool.get_connection("_")
33+
return await r.connection_pool.get_connection("_")
34+
35+
@staticmethod
36+
def has_no_connected_connections(pool: aioredis.ConnectionPool):
37+
return not any(
38+
x.is_connected
39+
for x in pool._available_connections + list(pool._in_use_connections)
40+
)
41+
42+
async def test_auto_disconnect_redis_created_pool(self, r: aioredis.Redis):
43+
new_conn = await self.create_two_conn(r)
44+
assert new_conn != r.connection
45+
assert self.get_total_connected_connections(r.connection_pool) == 2
46+
await r.close()
47+
assert self.has_no_connected_connections(r.connection_pool)
48+
49+
async def test_do_not_auto_disconnect_redis_created_pool(self, r2: aioredis.Redis):
50+
assert r2.auto_close_connection_pool is False, (
51+
"The connection pool should not be disconnected as a manually created "
52+
"connection pool was passed in in conftest.py"
53+
)
54+
new_conn = await self.create_two_conn(r2)
55+
assert self.get_total_connected_connections(r2.connection_pool) == 2
56+
await r2.close()
57+
assert r2.connection_pool._in_use_connections == {new_conn}
58+
assert new_conn.is_connected
59+
assert len(r2.connection_pool._available_connections) == 1
60+
assert r2.connection_pool._available_connections[0].is_connected
61+
62+
async def test_auto_release_override_true_manual_created_pool(
63+
self, r: aioredis.Redis
64+
):
65+
assert r.auto_close_connection_pool is True, "This is from the class fixture"
66+
await self.create_two_conn(r)
67+
await r.close()
68+
assert self.get_total_connected_connections(r.connection_pool) == 2, (
69+
"The connection pool should not be disconnected as a manually created "
70+
"connection pool was passed in in conftest.py"
71+
)
72+
assert self.has_no_connected_connections(r.connection_pool)
73+
74+
@pytest.mark.parametrize("auto_close_conn_pool", [True, False])
75+
async def test_close_override(self, r: aioredis.Redis, auto_close_conn_pool):
76+
r.auto_close_connection_pool = auto_close_conn_pool
77+
await self.create_two_conn(r)
78+
await r.close(close_connection_pool=True)
79+
assert self.has_no_connected_connections(r.connection_pool)
80+
81+
@pytest.mark.parametrize("auto_close_conn_pool", [True, False])
82+
async def test_negate_auto_close_client_pool(
83+
self, r: aioredis.Redis, auto_close_conn_pool
84+
):
85+
r.auto_close_connection_pool = auto_close_conn_pool
86+
new_conn = await self.create_two_conn(r)
87+
await r.close(close_connection_pool=False)
88+
assert not self.has_no_connected_connections(r.connection_pool)
89+
assert r.connection_pool._in_use_connections == {new_conn}
90+
assert r.connection_pool._available_connections[0].is_connected
91+
assert self.get_total_connected_connections(r.connection_pool) == 2
92+
93+
1794
class DummyConnection(Connection):
1895
description_format = "DummyConnection<>"
1996

0 commit comments

Comments
 (0)