diff --git a/lib/MySQL_Session.cpp b/lib/MySQL_Session.cpp index 1e4267c80..5d2721d34 100644 --- a/lib/MySQL_Session.cpp +++ b/lib/MySQL_Session.cpp @@ -2062,7 +2062,7 @@ bool MySQL_Session::handler_again___status_SETTING_INIT_CONNECT(int *_rc) { previous_status.pop(); NEXT_IMMEDIATE_NEW(st); } else { - if (rc==-1) { + if (rc==-1 || rc==-2) { // the command failed int myerr=mysql_errno(myconn->mysql); MyHGM->p_update_mysql_error_counter( @@ -2077,11 +2077,18 @@ bool MySQL_Session::handler_again___status_SETTING_INIT_CONNECT(int *_rc) { // client error, serious detected_broken_connection(__FILE__ , __LINE__ , __func__ , "while setting INIT CONNECT", myconn, myerr, mysql_error(myconn->mysql)); //if ((myds->myconn->reusable==true) && ((myds->myprot.prot_status & SERVER_STATUS_IN_TRANS)==0)) { - if ((myds->myconn->reusable==true) && myds->myconn->IsActiveTransaction()==false && myds->myconn->MultiplexDisabled()==false) { - retry_conn=true; + if (rc != -2) { // see PMC-10003 + if ((myds->myconn->reusable==true) && myds->myconn->IsActiveTransaction()==false && myds->myconn->MultiplexDisabled()==false) { + retry_conn=true; + } } myds->destroy_MySQL_Connection_From_Pool(false); myds->fd=0; + if (rc==-2) { + // Here we handle PMC-10003 + // and we terminate the session + retry_conn=false; + } if (retry_conn) { myds->DSS=STATE_NOT_INITIALIZED; //previous_status.push(PROCESSING_QUERY); diff --git a/lib/mysql_connection.cpp b/lib/mysql_connection.cpp index 5ec89d7f0..d7d920ae9 100644 --- a/lib/mysql_connection.cpp +++ b/lib/mysql_connection.cpp @@ -2616,7 +2616,13 @@ int MySQL_Connection::async_send_simple_command(short event, char *stmt, unsigne if (MyRS) { // this is a severe mistake, we shouldn't have reach here // for now we do not assert but report the error - proxy_error("Retrieved a resultset while running a simple command. This is an error!! Simple command: %s\n", stmt); + // PMC-10003: Retrieved a resultset while running a simple command using async_send_simple_command() . + // async_send_simple_command() is used by ProxySQL to configure the connection, thus it + // shouldn't retrieve any resultset. + // A common issue for triggering this error is to have configure mysql-init_connect to + // run a statement that returns a resultset. + proxy_error2(10003, "PMC-10003: Retrieved a resultset while running a simple command. This is an error!! Simple command: %s\n", stmt); + return -2; } if (async_state_machine==ASYNC_QUERY_END) { compute_unknown_transaction_status(); diff --git a/test/tap/tests/mysql-init_connect-t.cpp b/test/tap/tests/mysql-init_connect-t.cpp index f5fc2f1db..f9317da78 100644 --- a/test/tap/tests/mysql-init_connect-t.cpp +++ b/test/tap/tests/mysql-init_connect-t.cpp @@ -11,6 +11,12 @@ #include "command_line.h" #include "utils.h" +/* +This TAP test validate the use of mysql-init_connect. +It uses 2 valid init_connect, and 2 invalid ones that trigger PMC-10003. +It also sets a value that causes a syntax error +*/ + inline unsigned long long monotonic_time() { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); @@ -23,7 +29,7 @@ int main(int argc, char** argv) { if(cl.getEnv()) return exit_status(); - plan(7); + plan(8); MYSQL* mysqladmin = mysql_init(NULL); if (!mysqladmin) @@ -40,8 +46,8 @@ int main(int argc, char** argv) { return exit_status(); if (!mysql_real_connect(mysql, cl.host, cl.username, cl.password, NULL, cl.port, NULL, 0)) { - fprintf(stderr, "Failed to connect to database: Error: %s\n", - mysql_error(mysql)); + fprintf(stderr, "Failed to connect to database: Error: %s\n", + mysql_error(mysql)); return exit_status(); } diag("Setting mysql-init_connect to DO 1"); @@ -59,7 +65,7 @@ int main(int argc, char** argv) { unsigned long long num_rows = mysql_num_rows(res); ok(num_rows == 1, "mysql_num_rows() , expected: 1 , actual: %llu", num_rows); while ((row = mysql_fetch_row(res))) { - ok(strcmp(row[0],"100")==0, "row: expected: \"100\" , actual: \"%s\"", row[0]); + ok(strcmp(row[0],"100")==0, "row: expected: \"100\" , actual: \"%s\"", row[0]); } mysql_free_result(res); } @@ -71,15 +77,11 @@ int main(int argc, char** argv) { { const char *q = "SELECT /* create_new_connection=1 */ 200"; diag("Running query: %s", q); - MYSQL_QUERY(mysql, q); - res = mysql_store_result(mysql); - MYSQL_ROW row; - unsigned long long num_rows = mysql_num_rows(res); - ok(num_rows == 1, "mysql_num_rows() , expected: 1 , actual: %llu", num_rows); - while ((row = mysql_fetch_row(res))) { - ok(strcmp(row[0],"200")==0, "row: expected: \"200\" , actual: \"%s\"", row[0]); - } - mysql_free_result(res); + int rc=mysql_query(mysql,q); + ok(rc!=0, "Query should fail. Error: %s", mysql_error(mysql)); + if (rc==0) + return exit_status(); + mysql_close(mysql); } diag("Setting mysql-init_connect to SELECT SLEEP(3)"); @@ -87,25 +89,78 @@ int main(int argc, char** argv) { MYSQL_QUERY(mysqladmin, "load mysql variables to runtime"); { + // reconnect + MYSQL* mysql = mysql_init(NULL); + if (!mysql) + return exit_status(); + if (!mysql_real_connect(mysql, cl.host, cl.username, cl.password, NULL, cl.port, NULL, 0)) { + fprintf(stderr, "Failed to connect to database: Error: %s\n", + mysql_error(mysql)); + return exit_status(); + } const char *q = "SELECT /* create_new_connection=1 */ 300"; diag("Running query: %s", q); unsigned long long begin = monotonic_time(); + int rc=mysql_query(mysql,q); + ok(rc!=0, "Query should fail. Error: %s", mysql_error(mysql)); + if (rc==0) + return exit_status(); + mysql_close(mysql); + unsigned long long end = monotonic_time(); + unsigned long time_diff_ms = (end-begin)/1000; + ok(time_diff_ms>2900 && time_diff_ms < 3200 , "Total query execution time should be around 3 seconds. Actual : %llums", time_diff_ms); + } + + diag("Setting mysql-init_connect to Syntax Error"); + MYSQL_QUERY(mysqladmin, "UPDATE global_variables SET variable_value='Syntax Error' WHERE variable_name = 'mysql-init_connect'"); + MYSQL_QUERY(mysqladmin, "load mysql variables to runtime"); + + { + // reconnect + MYSQL* mysql = mysql_init(NULL); + if (!mysql) + return exit_status(); + if (!mysql_real_connect(mysql, cl.host, cl.username, cl.password, NULL, cl.port, NULL, 0)) { + fprintf(stderr, "Failed to connect to database: Error: %s\n", + mysql_error(mysql)); + return exit_status(); + } + const char *q = "SELECT /* create_new_connection=1 */ 400"; + diag("Running query: %s", q); + int rc=mysql_query(mysql,q); + ok(rc!=0, "Query should fail. Error: %s", mysql_error(mysql)); + if (rc==0) + return exit_status(); + mysql_close(mysql); + } + + diag("Setting mysql-init_connect to DO 1"); + MYSQL_QUERY(mysqladmin, "UPDATE global_variables SET variable_value='DO 1' WHERE variable_name = 'mysql-init_connect'"); + MYSQL_QUERY(mysqladmin, "load mysql variables to runtime"); + + { + // reconnect + MYSQL* mysql = mysql_init(NULL); + if (!mysql) + return exit_status(); + if (!mysql_real_connect(mysql, cl.host, cl.username, cl.password, NULL, cl.port, NULL, 0)) { + fprintf(stderr, "Failed to connect to database: Error: %s\n", + mysql_error(mysql)); + return exit_status(); + } + const char *q = "SELECT /* create_new_connection=1 */ 500"; + diag("Running query: %s", q); MYSQL_QUERY(mysql, q); res = mysql_store_result(mysql); MYSQL_ROW row; unsigned long long num_rows = mysql_num_rows(res); ok(num_rows == 1, "mysql_num_rows() , expected: 1 , actual: %llu", num_rows); while ((row = mysql_fetch_row(res))) { - ok(strcmp(row[0],"300")==0, "row: expected: \"300\" , actual: \"%s\"", row[0]); + ok(strcmp(row[0],"500")==0, "row: expected: \"500\" , actual: \"%s\"", row[0]); } mysql_free_result(res); - unsigned long long end = monotonic_time(); - unsigned long time_diff_ms = (end-begin)/1000; - ok(time_diff_ms>2900 && time_diff_ms < 3200 , "Total query execution time should be around 3 seconds. Actual : %llums", time_diff_ms); - } - mysql_close(mysql); mysql_close(mysqladmin); return exit_status();