#include "stdio.h" #include "stdlib.h" #include "string.h" #include #include #include int exit_failure(const char *msg, MYSQL *mysql, MYSQL_RES *res); /* Connection parameter - change */ const char *con_host = "localhost"; const char *con_user = "root"; const char *con_pass = "root"; const char *con_db = "test"; unsigned int con_port = 3306; const char *con_socket = "/tmp/mysql.sock"; unsigned long con_flags = 0; MYSQL *conn; MYSQL_RES *res; MYSQL_ROW row; int main(void) { printf("\n"); printf("Connecting...\n"); conn = mysql_init(NULL); if (conn == NULL) exit_failure("mysql_init() failed", NULL, NULL); if (mysql_real_connect(conn, con_host, con_user, con_pass, con_db, con_port, con_socket, con_flags) == NULL) exit_failure("mysql_real_connect() failed", conn, NULL); printf("Server: %s\n", mysql_get_server_info(conn)); printf("Protocol: %u\n", mysql_get_proto_info(conn)); printf("Client: %s\n\n", mysql_get_client_info()); /* This will *block* when you execute this program more than once without restarting the MySQL Server between program invocation. This is the actual bug. */ printf("SELECT IS_USED_LOCK('lock_never_set') - lock gets never set, ergo this should never block...\n"); if (mysql_query(conn, "SELECT IS_USED_LOCK('lock_never_set')") != 0) exit_failure("First IS_USED_LOCK() failed", conn, NULL); res = mysql_store_result(conn); if (res == NULL) exit_failure("mysql_store_result() - lock_never_set", conn, NULL); if ((row = mysql_fetch_row(res)) == NULL) exit_failure("mysql_fetch_row() - lock_never_set", conn, res); if (row[0] != NULL) exit_failure("User lock 'lock_never_set' is already set.", conn, res); mysql_free_result(res); /* Now we start to put the MySQL Server in a state that no other user will be able to use any function related to user locks before restarting the MySQL Server. We acquire a user lock. This user lock will not be released by the client using RELEASE_LOCK(). Instead the client will rely on mysql_change_user() to free the lock, see also http://dev.mysql.com/doc/refman/5.1/en/mysql-change-user.html As we'll see this seems to work fine... */ printf("SELECT GET_LOCK('change_user_lock', 10) - checking if change_user will release it implicitly...\n"); if (mysql_query(conn, "SELECT GET_LOCK('change_user_lock', 10)") != 0) exit_failure("GET_LOCK('change_user_lock') failed", conn, NULL); res = mysql_store_result(conn); if (res == NULL) exit_failure("mysql_store_result() - change_user_lock", conn, NULL); if ((row = mysql_fetch_row(res)) == NULL) exit_failure("mysql_fetch_row() - change_user_lock", conn, res); if (row[0] == NULL) exit_failure("Cannot acquire user lock 'change_user_lock'.", conn, res); printf("SELECT GET_LOCK('change_user_lock') --> %s ...\n", row[0]); if (strncmp(row[0], "1", 1)) exit_failure("GET_LOCK('change_user_lock') did not return '1' to indicate success", conn, res); mysql_free_result(res); /* Cool, we got the user lock 'change_user_lock'. Now we call change_user_lock() and expect the server to clean up the connection. That includes, among others: - ROLLBACK - release all locks acquired with GET_LOCK() - ... We test the lock release with: SELECT IS_USED_LOCK('change_user_lock') => expected: NULL SELECT IS_FREE_LOCK('change_user_lock') => expected: 1 */ printf("Calling change_user() ...\n"); if (mysql_change_user(conn, con_user, con_pass, con_db) != 0) exit_failure("mysql_change_user() failed", conn, NULL); printf("Checking if the user lock has been released due to change_user() ...\n"); if (mysql_query(conn, "SELECT IS_USED_LOCK('change_user_lock')") != 0) exit_failure("SELECT IS_USED_LOCK('change_user_lock') failed", conn, NULL); res = mysql_store_result(conn); if (res == NULL) exit_failure("mysql_store_result() - change_user_lock", conn, NULL); if ((row = mysql_fetch_row(res)) == NULL) exit_failure("mysql_fetch_row() - change_user_lock", conn, res); if (row[0] != NULL) exit_failure("IS_USED_LOCK('change_user_lock') indicates that the lock has not been released after change_user()!", conn, res); mysql_free_result(res); printf("SELECT IS_USED_LOCK('change_user_lock') --> NULL ...\n"); printf("IS_FREE_LOCK('change_user_lock')? ...\n"); if (mysql_query(conn, "SELECT IS_FREE_LOCK('change_user_lock')") != 0) exit_failure("SELECT IS_FREE_LOCK('change_user_lock') failed", conn, NULL); res = mysql_store_result(conn); if (res == NULL) exit_failure("mysql_store_result() - change_user_lock", conn, NULL); if ((row = mysql_fetch_row(res)) == NULL) exit_failure("mysql_fetch_row() - change_user_lock", conn, res); if (row[0] == NULL || strncmp(row[0], "1", 1)) exit_failure("IS_FREE_LOCK('change_user_lock') indicates that the lock has not been released after change_user()!", conn, res); printf("SELECT IS_FREE_LOCK('change_user_lock') --> %s ...\n", row[0]); mysql_free_result(res); mysql_close(conn); /* Great, the MySQL Server has released the user locks. Does mysql_change_user() work fine? Well, let's re-run the test... */ printf("... done!\n\n"); printf("Re-run the program and check if the very first statement\n"); printf("SELECT IS_USED_LOCK('lock_never_set') will block.\n"); printf("As at no point a user lock 'lock_never_set' has been set,\n"); printf("statement must not block. If it blocks, check SHOW PROCESSLIST.\n"); return EXIT_SUCCESS; } int exit_failure(const char *msg, MYSQL *mysql, MYSQL_RES *res) { printf("ERROR: %s\n", msg); if (mysql) { printf("[%u] %s\n", mysql_errno(mysql), mysql_error(mysql)); if (res) mysql_free_result(res); mysql_close(mysql); } printf("\n"); exit(EXIT_FAILURE); }