Bug #31418 User locks misfunctioning after mysql_change_user()
Submitted: 5 Oct 2007 11:10 Modified: 24 Oct 2007 19:51
Reporter: Ulf Wendel Email Updates:
Status: Closed Impact on me:
None 
Category:MySQL Server Severity:S1 (Critical)
Version:5.1.21-beta-debug OS:Linux (Linux linux-eu6p 2.6.16.53-0.8-bigsmp #1 SMP Fri Aug 31 13:07:27 UTC 2007 i686 i686 i386 GNU/Linux)
Assigned to: Alexander Nozdrin CPU Architecture:Any
Tags: mysql_change_user

[5 Oct 2007 11:10] Ulf Wendel
Description:
When a client acquires a user lock in a connection, calls mysql_change_user() prior to releasing the user lock with RELEASE_LOCK() [and relies on the implicit release of the user lock], no other client using a new connection opened after the mysql_change_user() can use user locks any more. In the new connection, the clients will get timeouts even for unrelated user locks.

As it's difficult to put in word, here's a list of the steps for reproducing the problem:

Connection 1:

 - Connect                                       --> OK
 - SELECT GET_LOCK('change_user')                --> OK
 - mysql_change_user(same server/user/pass)      --> OK
 - SELECT IS_USED_LOCK('change_user')            --> OK
 - SELECT IS_FREE_LOCK('change_user')            --> OK
 - Close                                         --> OK

Connection 2 (opened after Connection 1 has been closed):

  - Connect                                      --> OK
  - SELECT IS_USED_LOCK('different_name')        --> BLOCKS

   *NOTE* This user lock has never been set before
   *NOTE* Other functions (GET_LOCK()/IS_FREE_LOCK()) are affected as well
   

A C test case is attached. It's not nice C code, but it does expose the bug. The bug can also be verified using PHP (with libmysql AND mysqlnd) and this is where we first saw it, actually (SVN: php-mysqlnd/trunk/tests/ext/mysqli/mysqli_change_user_get_lock.phpt).

According to my testing several 5.x versions are affected. However, I did not follow up on this. I can at least reproduce the bug with 5.1.21-beta-debug on Linux.

The fact that the bug can be reproduced with PHP compiled against libmysql and the new MySQL native driver for PHP (mysqlnd => new implementation of the C/S protocol) makes me guess that its rather Server than Client related.

How to repeat:
1) Restart your MySQL Server to clean up your MySQL Servers processlist.

mysql> show processlist;
+----+------+-----------+------+---------+------+-------+------------------+
| Id | User | Host      | db   | Command | Time | State | Info             |
+----+------+-----------+------+---------+------+-------+------------------+
|  1 | root | localhost | NULL | Query   |    0 | NULL  | show processlist |
+----+------+-----------+------+---------+------+-------+------------------+
1 row in set (0.00 sec)

2) Compile and run the attached C test code:

> rm ./change_user
> gcc -c `/usr/local/mysql/bin/mysql_config --cflags` -Wall change_user.c
> gcc -o change_user change_user.o `/usr/local/mysql/bin/mysql_config --libs` 
> ./change_user

It will print debug output and instructions:

Connecting...
Server:         5.1.21-beta-debug
Protocol:       10
Client:         5.1.21-beta

SELECT IS_USED_LOCK('lock_never_set') - lock gets never set, ergo this should never block...
SELECT GET_LOCK('change_user_lock', 10) - checking if change_user will release it implicitly...
SELECT GET_LOCK('change_user_lock') --> 1 ...
Calling change_user() ...
Checking if the user lock has been released due to change_user() ...
SELECT IS_USED_LOCK('change_user_lock') --> NULL ...
IS_FREE_LOCK('change_user_lock')? ...
SELECT IS_FREE_LOCK('change_user_lock') --> 1 ...
... done!

Re-run the program and check if the very first statement
SELECT IS_USED_LOCK('lock_never_set') will block.
As at no point a user lock 'lock_never_set' has been set,
statement must not block. If it blocks, check SHOW PROCESSLIST.

3) Run SHOW PROCESSLIST again

Note that you now have a killed connection in the process list.

> /usr/local/mysql/bin//mysql -uroot -proot
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 3
Server version: 5.1.21-beta-debug Source distribution

Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

mysql> show processlist;
+----+------+-----------+------+---------+------+-------+------------------+
| Id | User | Host      | db   | Command | Time | State | Info             |
+----+------+-----------+------+---------+------+-------+------------------+
|  2 | root | localhost | test | Killed  |   70 | NULL  | NULL             |
|  3 | root | localhost | NULL | Query   |    0 | NULL  | show processlist |
+----+------+-----------+------+---------+------+-------+------------------+
2 rows in set (0.01 sec)

4) Re-run the test program

The first thing that the test program does is call IS_USED_LOCK().

> ./change_user

Connecting...
Server:         5.1.21-beta-debug
Protocol:       10
Client:         5.1.21-beta

SELECT IS_USED_LOCK('lock_never_set') - lock gets never set, ergo this should never block...

5) Monitor SHOW PROCESSLIST again

mysql> show processlist
    -> ;
+----+------+-----------+------+---------+------+-------+---------------------------------------+
| Id | User | Host      | db   | Command | Time | State | Info                                  |
+----+------+-----------+------+---------+------+-------+---------------------------------------+
|  2 | root | localhost | test | Killed  |  286 | NULL  | NULL                                  |
|  4 | root | localhost | test | Query   |  153 | init  | SELECT IS_USED_LOCK('lock_never_set') |
|  5 | root | localhost | NULL | Query   |    0 | NULL  | show processlist                      |
+----+------+-----------+------+---------+------+-------+---------------------------------------+
[5 Oct 2007 11:11] Ulf Wendel
C test code

Attachment: change_user.c (text/x-csrc), 5.61 KiB.

[5 Oct 2007 11:12] Ulf Wendel
PHP test code (enable test in SKIPIF!), php.net phpt format -> qa.php.net

Attachment: mysqli_change_user_get_lock.phpt (application/octet-stream, text), 3.81 KiB.

[5 Oct 2007 13:08] MySQL Verification Team
Thank ou for the bug report. Verified as described.
[11 Oct 2007 12:22] Bugs System
A patch for this bug has been committed. After review, it may
be pushed to the relevant source trees for release in the next
version. You can access the patch from:

  http://lists.mysql.com/commits/35346

ChangeSet@1.2583, 2007-10-11 16:22:46+04:00, anozdrin@station. +2 -0
  A patch for BUG#31418: User locks misfunctioning after
  mysql_change_user().
  
  The problem was that THD::ull was not reset in THD::cleanup().
  
  The fix is to reset it.
[11 Oct 2007 13:12] Bugs System
A patch for this bug has been committed. After review, it may
be pushed to the relevant source trees for release in the next
version. You can access the patch from:

  http://lists.mysql.com/commits/35350

ChangeSet@1.2593, 2007-10-11 17:11:12+04:00, anozdrin@station. +2 -0
  A patch for BUG#31418: User locks misfunctioning after
  mysql_change_user().
  
  The problem was that THD::ull was not reset in THD::cleanup().
  
  The fix is to reset it.
[11 Oct 2007 13:14] Alexander Nozdrin
Pushed into 5.1-runtime.
[19 Oct 2007 18:52] Bugs System
Pushed into 5.1.23-beta
[24 Oct 2007 19:51] Paul DuBois
Noted in 5.1.23 changelog.

The mysql_change_user() C API function caused advisory locks
(obtained with GET_LOCK()) to malfunction.