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.
This commit is contained in:
Georg Richter
2023-02-01 09:33:54 +01:00
parent 84967a90ca
commit 7daab2feb5
3 changed files with 56 additions and 31 deletions

View File

@ -19,7 +19,6 @@
import mariadb
import _thread
import time
MAX_POOL_SIZE = 64
POOL_IDLE_TIMEOUT = 1800
@ -63,7 +62,8 @@ class ConnectionPool(object):
Will reset the connection before returning it to the pool.
Default value is True.
"""
self._connections = []
self._connections_free = []
self._connections_used = []
self._pool_args = {}
self._conn_args = {}
self._lock_pool = _thread.RLock()
@ -107,15 +107,15 @@ class ConnectionPool(object):
except mariadb.Error:
# if an error occurred, close all connections
# and raise exception
for j in range(0, len(self._connections)):
for j in range(0, len(self._connections_free)):
try:
self._connections[j].close()
self._connections_free[j].close()
except mariadb.Error:
# connect failed, so we are not
# interested in errors
# from close() method
pass
del self._connections[j]
del self._connections_free[j]
raise
self.add_connection(connection)
@ -151,20 +151,19 @@ class ConnectionPool(object):
raise mariadb.PoolError("Can't get configuration for pool %s" %
self._pool_args["name"])
if len(self._connections) >= self._pool_args["size"]:
total = len(self._connections_free) + len(self._connections_used)
if total >= self._pool_args["size"]:
raise mariadb.PoolError("Can't add connection to pool %s: "
"No free slot available (%s)." %
(self._pool_args["name"],
len(self._connections)))
total))
with self._lock_pool:
if connection is None:
connection = mariadb.Connection(**self._conn_args)
connection._Connection__pool = self
connection._Connection__in_use = 0
connection._Connection__last_used = time.monotonic()
self._connections.append(connection)
self._connections_free.append(connection)
def get_connection(self):
"""
@ -172,26 +171,22 @@ class ConnectionPool(object):
exception if a connection is not available.
"""
now = time.monotonic()
conn = None
timediff = -1
with self._lock_pool:
for i in range(0, len(self._connections)):
if not self._connections[i]._Connection__in_use:
for i in range(0, len(self._connections_free)):
try:
self._connections[i].ping()
self._connections_free[i].ping()
except mariadb.Error:
continue
t = now - self._connections[i]._Connection__last_used
if t > timediff:
conn = self._connections[i]
timediff = t
if conn:
conn._Connection__in_use = 1
conn = self._connections_free[i]
conn._used += 1
self._connections_used.append(conn)
del self._connections_free[i]
return conn
return None
def _close_connection(self, connection):
"""
Returns connection to the pool. Internally used
@ -201,8 +196,12 @@ class ConnectionPool(object):
with self._lock_pool:
if self._pool_args["reset_connection"]:
connection.reset()
connection._Connection__in_use = 0
connection._Connection__last_used = time.monotonic()
for i in range(0, len(self._connections_used)):
if self._connections_used[i] == connection:
del self._connections_used[i]
self._connections_free.append(connection)
return
def set_config(self, **kwargs):
"""
@ -218,11 +217,15 @@ class ConnectionPool(object):
def close(self):
"""Closes connection pool and all connections."""
try:
for c in self._connections:
for c in self._connections_free:
c._Connection__pool = None
c.close()
for c in self._connections_used:
c._Connection__pool = None
c.close()
finally:
self._connections = None
self._connections_free = None
self._connections_used = None
del mariadb._CONNECTION_POOLS[self._pool_args["name"]]
@property

View File

@ -51,10 +51,9 @@ class Connection(mariadb._mariadb.connection):
"""
self._socket = None
self.__in_use = 0
self._used = 0
self._last_executed_statement = None
self._socket = None
self.__in_use = 0
self.__pool = None
self.__last_used = 0
self.tpc_state = TPC_STATE.NONE

View File

@ -36,6 +36,29 @@ class TestPooling(unittest.TestCase):
except mariadb.ProgrammingError:
pass
def test_conpy245(self):
# we can't test performance here, but we can check if LRU works.
# All connections must have been used the same number of times.
default_conf = conf()
pool_size = 64
iterations = 100
pool = mariadb.ConnectionPool(pool_name="CONPY245",
pool_size=pool_size,
**default_conf)
for i in range(0, iterations):
for j in range(0, pool_size):
conn = pool.get_connection()
conn.close()
for i in range(0, pool_size):
conn = pool.get_connection()
self.assertEqual(conn._used, iterations + 1)
conn.close()
pool.close()
def test_connection_pool_conf(self):
pool = mariadb.ConnectionPool(pool_name="test_conf")
default_conf = conf()