Skip to content

Commit 5699f8d

Browse files
soarjettify
authored andcommitted
Pool connections recycling (#373)
* Added optional parameter `recycle` for `create_engine` in aiopg.sa * Fix code style * Added test for connection recycling * Use `loop.time()` instead of `time.time()` * One more `time.time()` to `loop.time()` change * Remove unused import * Rename parameter `recycle` to `pool_recycle` according to SQLAlchemy engine parameter See: #373
1 parent 7db36f4 commit 5699f8d

File tree

4 files changed

+48
-11
lines changed

4 files changed

+48
-11
lines changed

aiopg/connection.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ def __init__(self, dsn, loop, timeout, waiter, echo, **kwargs):
111111
assert self._conn.isexecuting(), "Is conn an async at all???"
112112
self._fileno = self._conn.fileno()
113113
self._timeout = timeout
114+
self._last_usage = self._loop.time()
114115
self._waiter = waiter
115116
self._writing = False
116117
self._cancelling = False
@@ -264,6 +265,7 @@ def cursor(self, name=None, cursor_factory=None,
264265
psycopg in asynchronous mode.
265266
266267
"""
268+
self._last_usage = self._loop.time()
267269
coro = self._cursor(name=name, cursor_factory=cursor_factory,
268270
scrollable=scrollable, withhold=withhold,
269271
timeout=timeout)
@@ -492,6 +494,11 @@ def timeout(self):
492494
"""Return default timeout for connection operations."""
493495
return self._timeout
494496

497+
@property
498+
def last_usage(self):
499+
"""Return time() when connection was used."""
500+
return self._last_usage
501+
495502
@property
496503
def echo(self):
497504
"""Return echo mode status."""

aiopg/pool.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,21 @@
1717

1818

1919
def create_pool(dsn=None, *, minsize=1, maxsize=10,
20-
loop=None, timeout=TIMEOUT,
20+
loop=None, timeout=TIMEOUT, pool_recycle=-1,
2121
enable_json=True, enable_hstore=True, enable_uuid=True,
2222
echo=False, on_connect=None,
2323
**kwargs):
2424
coro = _create_pool(dsn=dsn, minsize=minsize, maxsize=maxsize, loop=loop,
25-
timeout=timeout, enable_json=enable_json,
26-
enable_hstore=enable_hstore, enable_uuid=enable_uuid,
27-
echo=echo, on_connect=on_connect, **kwargs)
25+
timeout=timeout, pool_recycle=pool_recycle,
26+
enable_json=enable_json, enable_hstore=enable_hstore,
27+
enable_uuid=enable_uuid, echo=echo,
28+
on_connect=on_connect, **kwargs)
2829
return _PoolContextManager(coro)
2930

3031

3132
@asyncio.coroutine
3233
def _create_pool(dsn=None, *, minsize=1, maxsize=10,
33-
loop=None, timeout=TIMEOUT,
34+
loop=None, timeout=TIMEOUT, pool_recycle=-1,
3435
enable_json=True, enable_hstore=True, enable_uuid=True,
3536
echo=False, on_connect=None,
3637
**kwargs):
@@ -40,7 +41,7 @@ def _create_pool(dsn=None, *, minsize=1, maxsize=10,
4041
pool = Pool(dsn, minsize, maxsize, loop, timeout,
4142
enable_json=enable_json, enable_hstore=enable_hstore,
4243
enable_uuid=enable_uuid, echo=echo, on_connect=on_connect,
43-
**kwargs)
44+
pool_recycle=pool_recycle, **kwargs)
4445
if minsize > 0:
4546
with (yield from pool._cond):
4647
yield from pool._fill_free_pool(False)
@@ -52,7 +53,7 @@ class Pool(asyncio.AbstractServer):
5253

5354
def __init__(self, dsn, minsize, maxsize, loop, timeout, *,
5455
enable_json, enable_hstore, enable_uuid, echo,
55-
on_connect, **kwargs):
56+
on_connect, pool_recycle, **kwargs):
5657
if minsize < 0:
5758
raise ValueError("minsize should be zero or greater")
5859
if maxsize < minsize and maxsize != 0:
@@ -61,6 +62,7 @@ def __init__(self, dsn, minsize, maxsize, loop, timeout, *,
6162
self._minsize = minsize
6263
self._loop = loop
6364
self._timeout = timeout
65+
self._recycle = pool_recycle
6466
self._enable_json = enable_json
6567
self._enable_hstore = enable_hstore
6668
self._enable_uuid = enable_uuid
@@ -187,6 +189,10 @@ def _fill_free_pool(self, override_min):
187189
conn = self._free[-1]
188190
if conn.closed:
189191
self._free.pop()
192+
elif self._recycle > -1 \
193+
and self._loop.time() - conn.last_usage > self._recycle:
194+
conn.close()
195+
self._free.pop()
190196
else:
191197
self._free.rotate()
192198
n += 1

aiopg/sa/engine.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ def _exec_default(self, default):
4444

4545

4646
def create_engine(dsn=None, *, minsize=1, maxsize=10, loop=None,
47-
dialect=_dialect, timeout=TIMEOUT, **kwargs):
47+
dialect=_dialect, timeout=TIMEOUT, pool_recycle=-1,
48+
**kwargs):
4849
"""A coroutine for Engine creation.
4950
5051
Returns Engine instance with embedded connection pool.
@@ -54,17 +55,19 @@ def create_engine(dsn=None, *, minsize=1, maxsize=10, loop=None,
5455

5556
coro = _create_engine(dsn=dsn, minsize=minsize, maxsize=maxsize,
5657
loop=loop, dialect=dialect, timeout=timeout,
57-
**kwargs)
58+
pool_recycle=pool_recycle, **kwargs)
5859
return _EngineContextManager(coro)
5960

6061

6162
@asyncio.coroutine
6263
def _create_engine(dsn=None, *, minsize=1, maxsize=10, loop=None,
63-
dialect=_dialect, timeout=TIMEOUT, **kwargs):
64+
dialect=_dialect, timeout=TIMEOUT, pool_recycle=-1,
65+
**kwargs):
6466
if loop is None:
6567
loop = asyncio.get_event_loop()
6668
pool = yield from aiopg.create_pool(dsn, minsize=minsize, maxsize=maxsize,
67-
loop=loop, timeout=timeout, **kwargs)
69+
loop=loop, timeout=timeout,
70+
pool_recycle=pool_recycle, **kwargs)
6871
conn = yield from pool.acquire()
6972
try:
7073
real_dsn = conn.dsn

tests/test_pool.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,27 @@ def sleep(conn):
505505
assert (1,) == val
506506

507507

508+
@asyncio.coroutine
509+
def test_pool_with_connection_recycling(create_pool, loop):
510+
pool = yield from create_pool(minsize=1,
511+
maxsize=1,
512+
pool_recycle=3)
513+
with (yield from pool) as conn:
514+
cur = yield from conn.cursor()
515+
yield from cur.execute('SELECT 1;')
516+
val = yield from cur.fetchone()
517+
assert (1,) == val
518+
519+
yield from asyncio.sleep(5, loop=loop)
520+
521+
assert 1 == pool.freesize
522+
with (yield from pool) as conn:
523+
cur = yield from conn.cursor()
524+
yield from cur.execute('SELECT 1;')
525+
val = yield from cur.fetchone()
526+
assert (1,) == val
527+
528+
508529
@asyncio.coroutine
509530
def test_connection_in_good_state_after_timeout_in_transaction(create_pool):
510531
@asyncio.coroutine

0 commit comments

Comments
 (0)