Bug #84476 Lost server connection leaks pooled connections
Submitted: 11 Jan 2017 22:51 Modified: 22 Mar 2017 18:34
Reporter: Travis Jensen Email Updates:
Status: Closed Impact on me:
None 
Category:Connector / Python Severity:S2 (Serious)
Version:2.1.3, 2.1.4 OS:Any
Assigned to: CPU Architecture:Any
Tags: connection pool, connector, python

[11 Jan 2017 22:51] Travis Jensen
Description:

If there is an exception resetting the underlying session, pooled connections will be "leaked". In particular, if a client is holding a pooled connection when the server goes away (mysql.connector.errors.InterfaceError: 2013: Lost connection to MySQL server during query"), if the client then closes that connection, it will "leak" from the pool, and one less connection will be available in the pool.

Strictly speaking, this isn't the pool leaking. Instead, the pool will gradually be reduced in size to zero. The effect is the same:

mysql.connector.errors.PoolError: Failed getting connection; pool exhausted

How to repeat:

(Note: this is pseudo-code-ish; I've not actually run it, but it is indicative of what it takes.)

    connection = mysql.connector.connect(
                option_files='/etc/my.cnf', pool_size=3)
    cursor = connection.cursor()
    try:
        sql = "select ..."     # make this a long-running query
        cursor.execute(sql)    # now, while it is running, unplug the 
                               # connection to the db
    except Exception:
        # You'll get the above-mentioned exception or something like it.
        cursor.close()
        connection.close()

`connection.close()` will raise a new exception (mysql.connector.errors.OperationalError: MySQL Connection not available.) while trying to reset the connection. As a result, the connection will never get added back into the pool, and our pool will be down to two members. Repeat two more times, and you get:

mysql.connector.errors.PoolError: Failed getting connection; pool exhausted

Suggested fix:

mysql.connector.pooling.PooledMySQLConnection.close() should always add a connection back into the pool. The pool should survive transient database connectivity problems. Logically, it should be equivalent to this, but I recognize that there may be state in the underlying connection that makes the solution less trivial than this.

    def close(self):
        """Do not close, but add connection back to pool
        The close() method does not close the connection with the
        MySQL server. The connection is added back to the pool so it
        can be reused. 
                                                 
        When the pool is configured to reset the session, the session
        state will be cleared by re-authenticating the user.
        """
        try:
            cnx = self._cnx
            if self._cnx_pool.reset_session:
                cnx.reset_session()
        finally:
            self._cnx_pool.add_connection(cnx)
            self._cnx = None
[12 Jan 2017 6:36] Chiranjeevi Battula
Hello Travis Jensen,

Thank you for the bug report and test case.
Verified this behavior on MySQL Connector/Python 2.1.4.

Thanks,
Chiranjeevi.
[22 Mar 2017 18:34] Paul DuBois
Posted by developer:
 
Noted in 2.1.6 changelog.

If an exception reset the underlying session, connections in a
connection pooled could become unavailable to the pool.