[PATCH 02 of 23 cubicweb V2] [database/pool] close connections when load is low

Laurent Peuch cortex at worlddomination.be
Fri Jan 17 14:36:59 CET 2020


# HG changeset patch
# User Laurent Peuch <cortex at worlddomination.be>
# Date 1576829177 -3600
#      Fri Dec 20 09:06:17 2019 +0100
# Node ID cc81dd2c317bcc289fdd03337ada54eacdb977cc
# Parent  dfaeea73d730524c596a49c46cfe680bf8fcde48
# Available At https://hg.logilab.org/users/lpeuch/cubicweb
#              hg pull https://hg.logilab.org/users/lpeuch/cubicweb -r cc81dd2c317b
[database/pool] close connections when load is low

The logic is to close connection one by one if we didn't had to wait to grab a
connection and that last time we had to wait a lot was some time ago (assuming
then that the load is now low).

Closes #17241878

diff --git a/cubicweb/server/repository.py b/cubicweb/server/repository.py
--- a/cubicweb/server/repository.py
+++ b/cubicweb/server/repository.py
@@ -26,6 +26,7 @@ repository mainly:
 * handles session management
 """
 
+import time
 from itertools import chain
 from contextlib import contextmanager
 from logging import getLogger
@@ -150,6 +151,10 @@ class FastReadCounter(object):
         self.value = value
         self._lock = threading.Lock()
 
+    def decrement(self):
+        with self._lock:
+            self.value -= 1
+
     def increment(self):
         with self._lock:
             self.value += 1
@@ -157,7 +162,8 @@ class FastReadCounter(object):
 
 class _CnxSetPool:
 
-    def __init__(self, source, size, min_size=3, min_timeout=0.1, max_timeout=5):
+    def __init__(self, source, size, min_size=3, min_timeout=0.1, max_timeout=5,
+                 low_load_delay=30):
         self._cnxsets = []
         self.size = size
         self.min_size = min_size
@@ -165,6 +171,9 @@ class _CnxSetPool:
         self.min_timeout = min_timeout
         self.max_timeout = max_timeout
         self.source = source
+        self.low_load_delay = low_load_delay
+
+        self._last_time_we_waited = None
 
         if self.size is not None:
             self._queue = queue.Queue()
@@ -196,7 +205,9 @@ class _CnxSetPool:
         # if we have something in the queue, return immediately
         if self.qsize():
             try:
-                return self._queue.get(block=False)
+                connection = self._queue.get(block=False)
+                self._maybe_close_connection()
+                return connection
             except queue.Empty:
                 pass  # probably for some race condition we missed it
 
@@ -214,7 +225,9 @@ class _CnxSetPool:
 
         # here, we can't open new connections because we are full, just wait
         try:
-            return self._queue.get(block=True, timeout=self.max_timeout)
+            connection = self._queue.get(block=True, timeout=self.max_timeout)
+            self._last_time_we_waited = time.time()
+            return connection
         except queue.Empty:
             # This situation is really annoying because we don't want to break connections but for
             # now it's needed. Why? Because in some situations CubicWeb likes to exhaust all
@@ -255,6 +268,37 @@ class _CnxSetPool:
         for cnxset in self._cnxsets:
             yield cnxset
 
+    def _maybe_close_connection(self):
+        """
+        This function is called to reduce the number of open connections when the load is low.
+        """
+        # we are at the minimum number of connections, don't do anything
+        if self.current_size.value <= self.min_size:
+            return
+
+        # we don't have any hanging open connection, don't do anything
+        if not self.qsize():
+            return
+
+        # only start to close connections once we didn't have to wait for sometime
+        if self._last_time_we_waited is not None\
+           and (time.time() - self._last_time_we_waited) < self.low_load_delay:
+            return
+
+        # let's try to close a connection
+        try:
+            connection = self._queue.get(block=False)
+        except queue.Empty:
+            pass  # probably for some race condition we missed it
+        else:
+            try:
+                connection.close(True)
+            except Exception as e:
+                self.exception('error while closing %s, error: %s' % (connection, e))
+            else:
+                self.current_size.decrement()
+                self._last_time_connection_close = time.time()
+
     def close(self):
         # XXX we don't close the connection when there is no queue?
         if self._queue is not None:



More information about the cubicweb-devel mailing list