Bug #117700 Thread safety issue when calling mysql.connector.connect on multiple threads
Submitted: 13 Mar 13:26
Reporter: Jean Hominal Email Updates:
Status: Open Impact on me:
None 
Category:Connector / Python Severity:S3 (Non-critical)
Version:9.2.0 OS:Red Hat
Assigned to: CPU Architecture:Any

[13 Mar 13:26] Jean Hominal
Description:
While using mysql-connector-python, with the C extension, some calls to mysqlconnect fail with the following stack trace:

Traceback (most recent call last):
  File "/usr/local/lib64/python3.12/site-packages/mysql/connector/connection_cext.py", line 365, in _open_connection
    self._cmysql.connect(**cnx_kwargs)
_mysql_connector.MySQLInterfaceError: Authentication plugin 'mysql_native_password' cannot be loaded: it is already loaded

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/usr/local/lib/python3.12/site-packages/app/lib/sql/mysql_store.py", line 41, in __init__
    self._connection = connect(
                       ^^^^^^^^
  File "/usr/local/lib64/python3.12/site-packages/mysql/connector/pooling.py", line 322, in connect
    return CMySQLConnection(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib64/python3.12/site-packages/mysql/connector/connection_cext.py", line 153, in __init__
    self.connect(**kwargs)
  File "/usr/local/lib64/python3.12/site-packages/mysql/connector/abstracts.py", line 1529, in connect
    self._open_connection()
  File "/usr/local/lib64/python3.12/site-packages/mysql/connector/connection_cext.py", line 370, in _open_connection
    raise get_mysql_exception(
mysql.connector.errors.DatabaseError: 2059 (HY000): Authentication plugin 'mysql_native_password' cannot be loaded: it is already loaded

How to repeat:
This happens in the following context:

 * In a multi-threaded web application;
 * When concurrent calls to mysql.connector.connect are being made;
 * When there was previously no activity (and thus, no previously successful calls to mysql.connector.connect) in the same process;

Suggested fix:
After reviewing the source code for mysql-connector-python as published on github, I have a working theory for where the issue comes from:

https://github.com/mysql/mysql-connector-python/blob/9.2.0/mysql-connector-python/src/mysq...

mysql.connector.connect eventually calls into the MySQL_connect C function, and this function calls the mysql_init function in a GIL-unlocked block:

```
    Py_BEGIN_ALLOW_THREADS
    if (self->connected) {
        self->connected = 0;
        mysql_close(&self->session);
    }

    mysql_init(&self->session);
    Py_END_ALLOW_THREADS
```

This means that the mysql_init method can be called concurrently in a multi-threaded python context. However, when the library has never been initialized in the process, mysql_init ends up calling mysql_library_init, which is documented in https://dev.mysql.com/doc/c-api/9.2/en/c-api-threaded-clients.html as being not thread-safe.

I can think of two big ways of solving that:

 * Either adding a call to mysql_library_init in the module initialization function https://github.com/mysql/mysql-connector-python/blob/9.2.0/mysql-connector-python/src/mysq... ;
 * Or adding a boolean in MySQL_connect that checks whether mysql_library_init has already been called in the current process, and to call that function only if it has not been called (this boolean and check must be made in a thread-safe way)

Right now, I am going to work around this bug by managing a lock around calls to connect.