diff --git a/odoo/sql_db.py b/odoo/sql_db.py index 4819837e526..2f3243d106d 100644 --- a/odoo/sql_db.py +++ b/odoo/sql_db.py @@ -48,6 +48,8 @@ re_into = re.compile('.* into "?([a-zA-Z_0-9]+)"? .*$') sql_counter = 0 +MAX_IDLE_TIMEOUT = 60 * 10 + class Savepoint: """ Reifies an active breakpoint, allows :meth:`BaseCursor.savepoint` users @@ -573,22 +575,28 @@ class ConnectionPool(object): @locked def borrow(self, connection_info): """ + Borrow a PsycoConnection from the pool. If no connection is available, create a new one + as long as there are still slots available. Perform some garbage-collection in the pool: + idle, dead and leaked connections are removed. + :param dict connection_info: dict of psql connection keywords :rtype: PsycoConnection """ - # free dead and leaked connections - for i, (cnx, _) in tools.reverse_enumerate(self._connections): + # free idle, dead and leaked connections + for i, (cnx, used, last_used) in tools.reverse_enumerate(self._connections): + if not used and not cnx.closed and time.time() - last_used > MAX_IDLE_TIMEOUT: + self._debug('Close connection at index %d: %r', i, cnx.dsn) + cnx.close() if cnx.closed: self._connections.pop(i) self._debug('Removing closed connection at index %d: %r', i, cnx.dsn) continue if getattr(cnx, 'leaked', False): delattr(cnx, 'leaked') - self._connections.pop(i) - self._connections.append((cnx, False)) + self._connections[i][1] = False _logger.info('%r: Free leaked connection to %r', self, cnx.dsn) - for i, (cnx, used) in enumerate(self._connections): + for i, (cnx, used, _) in enumerate(self._connections): if not used and cnx._original_dsn == connection_info: try: cnx.reset() @@ -598,15 +606,14 @@ class ConnectionPool(object): if not cnx.closed: cnx.close() continue - self._connections.pop(i) - self._connections.append((cnx, True)) + self._connections[i][1] = True self._debug('Borrow existing connection to %r at index %d', cnx.dsn, i) return cnx if len(self._connections) >= self._maxconn: # try to remove the oldest connection not used - for i, (cnx, used) in enumerate(self._connections): + for i, (cnx, used, _) in enumerate(self._connections): if not used: self._connections.pop(i) if not cnx.closed: @@ -625,20 +632,22 @@ class ConnectionPool(object): _logger.info('Connection to the database failed') raise result._original_dsn = connection_info - self._connections.append((result, True)) + self._connections.append([result, True, 0]) self._debug('Create new connection backend PID %d', result.get_backend_pid()) return result @locked def give_back(self, connection, keep_in_pool=True): self._debug('Give back connection to %r', connection.dsn) - for i, (cnx, used) in enumerate(self._connections): + for i, (cnx, _, _) in enumerate(self._connections): if cnx is connection: - self._connections.pop(i) if keep_in_pool: - self._connections.append((cnx, False)) + # Release the connection and record the last time used + self._connections[i][1] = False + self._connections[i][2] = time.time() self._debug('Put connection to %r in pool', cnx.dsn) else: + self._connections.pop(i) self._debug('Forgot connection to %r', cnx.dsn) cnx.close() break @@ -649,7 +658,7 @@ class ConnectionPool(object): def close_all(self, dsn=None): count = 0 last = None - for i, (cnx, used) in tools.reverse_enumerate(self._connections): + for i, (cnx, _, _) in tools.reverse_enumerate(self._connections): if dsn is None or cnx._original_dsn == dsn: cnx.close() last = self._connections.pop(i)[0]