Bug #117700 Thread safety issue when calling mysql.connector.connect on multiple threads
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.
Portions of this website are copyright © 2001, 2002 The PHP Group
Page generated in 0.052 sec. using MySQL 8.4.7-u6-cloud
Timestamp references displayed by the system are UTC.
Content reproduced on this site is the property of the
respective copyright holders.
It is not reviewed in advance
by Oracle and does not necessarily represent the opinion of
Oracle or any other party.
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.