Skip to content

Commit 7daab2f

Browse files
committed
Fix for CONPY-245:
Instead of iterating through all connections and checking the health status via ping, used and unused connections were separated in different lists. This ensures that the last used connection will be always the first.
1 parent 84967a9 commit 7daab2f

File tree

3 files changed

+57
-32
lines changed

3 files changed

+57
-32
lines changed

mariadb/connectionpool.py

Lines changed: 33 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919

2020
import mariadb
2121
import _thread
22-
import time
2322

2423
MAX_POOL_SIZE = 64
2524
POOL_IDLE_TIMEOUT = 1800
@@ -63,7 +62,8 @@ def __init__(self, *args, **kwargs):
6362
Will reset the connection before returning it to the pool.
6463
Default value is True.
6564
"""
66-
self._connections = []
65+
self._connections_free = []
66+
self._connections_used = []
6767
self._pool_args = {}
6868
self._conn_args = {}
6969
self._lock_pool = _thread.RLock()
@@ -107,15 +107,15 @@ def __init__(self, *args, **kwargs):
107107
except mariadb.Error:
108108
# if an error occurred, close all connections
109109
# and raise exception
110-
for j in range(0, len(self._connections)):
110+
for j in range(0, len(self._connections_free)):
111111
try:
112-
self._connections[j].close()
112+
self._connections_free[j].close()
113113
except mariadb.Error:
114114
# connect failed, so we are not
115115
# interested in errors
116116
# from close() method
117117
pass
118-
del self._connections[j]
118+
del self._connections_free[j]
119119
raise
120120
self.add_connection(connection)
121121

@@ -151,46 +151,41 @@ def add_connection(self, connection=None):
151151
raise mariadb.PoolError("Can't get configuration for pool %s" %
152152
self._pool_args["name"])
153153

154-
if len(self._connections) >= self._pool_args["size"]:
154+
total = len(self._connections_free) + len(self._connections_used)
155+
if total >= self._pool_args["size"]:
155156
raise mariadb.PoolError("Can't add connection to pool %s: "
156157
"No free slot available (%s)." %
157158
(self._pool_args["name"],
158-
len(self._connections)))
159+
total))
159160

160161
with self._lock_pool:
161162
if connection is None:
162163
connection = mariadb.Connection(**self._conn_args)
163164

164165
connection._Connection__pool = self
165-
connection._Connection__in_use = 0
166-
connection._Connection__last_used = time.monotonic()
167-
self._connections.append(connection)
166+
self._connections_free.append(connection)
168167

169168
def get_connection(self):
170169
"""
171170
Returns a connection from the connection pool or raises a PoolError
172171
exception if a connection is not available.
173172
"""
174173

175-
now = time.monotonic()
176174
conn = None
177-
timediff = -1
178175

179176
with self._lock_pool:
180-
for i in range(0, len(self._connections)):
181-
if not self._connections[i]._Connection__in_use:
182-
try:
183-
self._connections[i].ping()
184-
except mariadb.Error:
185-
continue
186-
t = now - self._connections[i]._Connection__last_used
187-
if t > timediff:
188-
conn = self._connections[i]
189-
timediff = t
190-
191-
if conn:
192-
conn._Connection__in_use = 1
193-
return conn
177+
for i in range(0, len(self._connections_free)):
178+
try:
179+
self._connections_free[i].ping()
180+
except mariadb.Error:
181+
continue
182+
conn = self._connections_free[i]
183+
conn._used += 1
184+
self._connections_used.append(conn)
185+
del self._connections_free[i]
186+
return conn
187+
188+
return None
194189

195190
def _close_connection(self, connection):
196191
"""
@@ -201,8 +196,12 @@ def _close_connection(self, connection):
201196
with self._lock_pool:
202197
if self._pool_args["reset_connection"]:
203198
connection.reset()
204-
connection._Connection__in_use = 0
205-
connection._Connection__last_used = time.monotonic()
199+
200+
for i in range(0, len(self._connections_used)):
201+
if self._connections_used[i] == connection:
202+
del self._connections_used[i]
203+
self._connections_free.append(connection)
204+
return
206205

207206
def set_config(self, **kwargs):
208207
"""
@@ -218,11 +217,15 @@ def set_config(self, **kwargs):
218217
def close(self):
219218
"""Closes connection pool and all connections."""
220219
try:
221-
for c in self._connections:
220+
for c in self._connections_free:
221+
c._Connection__pool = None
222+
c.close()
223+
for c in self._connections_used:
222224
c._Connection__pool = None
223225
c.close()
224226
finally:
225-
self._connections = None
227+
self._connections_free = None
228+
self._connections_used = None
226229
del mariadb._CONNECTION_POOLS[self._pool_args["name"]]
227230

228231
@property

mariadb/connections.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,9 @@ def __init__(self, *args, **kwargs):
5151
"""
5252

5353
self._socket = None
54-
self.__in_use = 0
54+
self._used = 0
5555
self._last_executed_statement = None
5656
self._socket = None
57-
self.__in_use = 0
5857
self.__pool = None
5958
self.__last_used = 0
6059
self.tpc_state = TPC_STATE.NONE

testing/test/integration/test_pooling.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,29 @@ def test_conpy39(self):
3636
except mariadb.ProgrammingError:
3737
pass
3838

39+
def test_conpy245(self):
40+
# we can't test performance here, but we can check if LRU works.
41+
# All connections must have been used the same number of times.
42+
43+
default_conf = conf()
44+
pool_size = 64
45+
iterations = 100
46+
47+
pool = mariadb.ConnectionPool(pool_name="CONPY245",
48+
pool_size=pool_size,
49+
**default_conf)
50+
for i in range(0, iterations):
51+
for j in range(0, pool_size):
52+
conn = pool.get_connection()
53+
conn.close()
54+
55+
for i in range(0, pool_size):
56+
conn = pool.get_connection()
57+
self.assertEqual(conn._used, iterations + 1)
58+
conn.close()
59+
60+
pool.close()
61+
3962
def test_connection_pool_conf(self):
4063
pool = mariadb.ConnectionPool(pool_name="test_conf")
4164
default_conf = conf()

0 commit comments

Comments
 (0)