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

Commit eaede3d

Browse files
pfreixespopravich
authored andcommitted
Support for connection timeout param
Related to #184, it allows aioredis to configure a limited time that will be used trying to open a connection, if it is reached a `asyncio.TimeoutError` will be raised By default any timeout is configured.
1 parent 6b13dd1 commit eaede3d

File tree

5 files changed

+59
-8
lines changed

5 files changed

+59
-8
lines changed

aioredis/connection.py

+10-5
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242

4343
@asyncio.coroutine
4444
def create_connection(address, *, db=None, password=None, ssl=None,
45-
encoding=None, loop=None):
45+
encoding=None, loop=None, timeout=None):
4646
"""Creates redis connection.
4747
4848
Opens connection to Redis server specified by address argument.
@@ -54,6 +54,10 @@ def create_connection(address, *, db=None, password=None, ssl=None,
5454
SSL argument is passed through to asyncio.create_connection.
5555
By default SSL/TLS is not used.
5656
57+
By default any timeout is applied at the connection stage, however
58+
you can set a limitted time used trying to open a connection via
59+
the `timeout` Kw.
60+
5761
Encoding argument can be used to decode byte-replies to strings.
5862
By default no decoding is done.
5963
@@ -66,17 +70,18 @@ def create_connection(address, *, db=None, password=None, ssl=None,
6670
if isinstance(address, (list, tuple)):
6771
host, port = address
6872
logger.debug("Creating tcp connection to %r", address)
69-
reader, writer = yield from asyncio.open_connection(
70-
host, port, ssl=ssl, loop=loop)
73+
reader, writer = yield from asyncio.wait_for(asyncio.open_connection(
74+
host, port, ssl=ssl, loop=loop), timeout, loop=loop)
7175
sock = writer.transport.get_extra_info('socket')
7276
if sock is not None:
7377
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
7478
address = sock.getpeername()
7579
address = tuple(address[:2])
7680
else:
7781
logger.debug("Creating unix connection to %r", address)
78-
reader, writer = yield from asyncio.open_unix_connection(
79-
address, ssl=ssl, loop=loop)
82+
reader, writer = yield from asyncio.wait_for(
83+
asyncio.open_unix_connection(address, ssl=ssl, loop=loop),
84+
timeout, loop=loop)
8085
sock = writer.transport.get_extra_info('socket')
8186
if sock is not None:
8287
address = sock.getpeername()

aioredis/pool.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414

1515
@asyncio.coroutine
1616
def create_pool(address, *, db=0, password=None, ssl=None, encoding=None,
17-
minsize=1, maxsize=10, commands_factory=_NOTSET, loop=None):
17+
minsize=1, maxsize=10, commands_factory=_NOTSET,
18+
loop=None, timeout_create_connection=None):
19+
# FIXME: rewrite docstring
1820
"""Creates Redis Pool.
1921
2022
By default it creates pool of Redis instances, but it is
@@ -37,6 +39,7 @@ def create_pool(address, *, db=0, password=None, ssl=None, encoding=None,
3739
pool = RedisPool(address, db, password, encoding,
3840
minsize=minsize, maxsize=maxsize,
3941
commands_factory=commands_factory,
42+
timeout_create_connection=timeout_create_connection,
4043
ssl=ssl, loop=loop)
4144
try:
4245
yield from pool._fill_free(override_min=False)
@@ -53,7 +56,8 @@ class RedisPool:
5356
"""
5457

5558
def __init__(self, address, db=0, password=None, encoding=None,
56-
*, minsize, maxsize, commands_factory, ssl=None, loop=None):
59+
*, minsize, maxsize, commands_factory, ssl=None,
60+
timeout_create_connection=None, loop=None):
5761
assert isinstance(minsize, int) and minsize >= 0, (
5862
"minsize must be int >= 0", minsize, type(minsize))
5963
assert maxsize is not None, "Arbitrary pool size is disallowed."
@@ -70,6 +74,7 @@ def __init__(self, address, db=0, password=None, encoding=None,
7074
self._encoding = encoding
7175
self._minsize = minsize
7276
self._factory = commands_factory
77+
self._timeout_create_connection = timeout_create_connection
7378
self._loop = loop
7479
self._pool = collections.deque(maxlen=maxsize)
7580
self._used = set()
@@ -251,6 +256,7 @@ def _fill_free(self, *, override_min):
251256
# connection may be closed at yield point
252257
self._drop_closed()
253258

259+
@asyncio.coroutine
254260
def _create_new_connection(self):
255261
return create_redis(self._address,
256262
db=self._db,

docs/api_reference.rst

+17-1
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,13 @@ Connection usage is as simple as:
3636
3737
3838
.. cofunction:: create_connection(address, \*, db=0, password=None, ssl=None,\
39-
encoding=None, loop=None)
39+
encoding=None, loop=None, timeout=None)
4040

4141
Creates Redis connection.
4242

43+
.. versionchanged:: v0.3.1
44+
``timeout`` argument added.
45+
4346
:param address: An address where to connect. Can be a (host, port) tuple or
4447
unix domain socket path string.
4548
:type address: tuple or str
@@ -61,6 +64,11 @@ Connection usage is as simple as:
6164
(uses :func:`asyncio.get_event_loop` if not specified).
6265
:type loop: :ref:`EventLoop<asyncio-event-loop>`
6366

67+
:param timeout: Max time used to open a connection, otherwise
68+
raise `asyncio.TimeoutError` exception.
69+
``None`` by default
70+
:type timeout: float or None
71+
6472
:return: :class:`RedisConnection` instance.
6573

6674

@@ -238,6 +246,9 @@ The library provides connections pool. The basic usage is as follows:
238246
.. deprecated:: v0.2.9
239247
*commands_factory* argument is deprecated and will be removed in *v0.3*.
240248

249+
.. versionchanged:: v0.3.1
250+
``timeout_create_connection`` argument added.
251+
241252
:param address: An address where to connect. Can be a (host, port) tuple or
242253
unix domain socket path string.
243254
:type address: tuple or str
@@ -271,6 +282,11 @@ The library provides connections pool. The basic usage is as follows:
271282
(uses :func:`asyncio.get_event_loop` if not specified).
272283
:type loop: :ref:`EventLoop<asyncio-event-loop>`
273284

285+
:param timeout_create_connection: Max time used to open a connection,
286+
otherwise raise an `asyncio.TimeoutError`.
287+
``None`` by default.
288+
:type timeout_create_connection: float or None
289+
274290
:return: :class:`RedisPool` instance.
275291

276292

tests/connection_test.py

+16
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@ def test_connect_tcp(request, create_connection, loop, server):
3232
assert str(conn) == '<RedisConnection [db:0]>'
3333

3434

35+
@pytest.mark.run_loop
36+
def test_connect_tcp_timeout(request, create_connection, loop, server):
37+
with pytest.raises(asyncio.TimeoutError):
38+
yield from create_connection(
39+
server.tcp_address, loop=loop, timeout=0)
40+
41+
3542
@pytest.mark.run_loop
3643
@pytest.mark.skipif(sys.platform == 'win32',
3744
reason="No unixsocket on Windows")
@@ -43,6 +50,15 @@ def test_connect_unixsocket(create_connection, loop, server):
4350
assert str(conn) == '<RedisConnection [db:0]>'
4451

4552

53+
@pytest.mark.run_loop
54+
@pytest.mark.skipif(sys.platform == 'win32',
55+
reason="No unixsocket on Windows")
56+
def test_connect_unixsocket_timeout(create_connection, loop, server):
57+
with pytest.raises(asyncio.TimeoutError):
58+
yield from create_connection(
59+
server.unixsocket, db=0, loop=loop, timeout=0)
60+
61+
4662
def test_global_loop(create_connection, loop, server):
4763
asyncio.set_event_loop(loop)
4864

tests/pool_test.py

+8
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,14 @@ def test_maxsize(maxsize, create_pool, loop, server):
5757
minsize=2, maxsize=maxsize, loop=loop)
5858

5959

60+
@pytest.mark.run_loop
61+
def test_create_connection_timeout(create_pool, loop, server):
62+
with pytest.raises(asyncio.TimeoutError):
63+
yield from create_pool(
64+
server.tcp_address, loop=loop,
65+
timeout_create_connection=0)
66+
67+
6068
def test_no_yield_from(pool):
6169
with pytest.raises(RuntimeError):
6270
with pool:

0 commit comments

Comments
 (0)