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

View File

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

View File

@ -36,6 +36,29 @@ class TestPooling(unittest.TestCase):
except mariadb.ProgrammingError: except mariadb.ProgrammingError:
pass 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): def test_connection_pool_conf(self):
pool = mariadb.ConnectionPool(pool_name="test_conf") pool = mariadb.ConnectionPool(pool_name="test_conf")
default_conf = conf() default_conf = conf()