Skip to content
This repository was archived by the owner on Mar 1, 2026. It is now read-only.

Commit 339e3da

Browse files
committed
Bug #25584097: MYSQL_STMT_CLOSE HANGS WHEN THE STATEMENT IS CANCELED WITH KILL QUERY
Two problems: Problem1: The client library was not handling killing a cursor producing mysql_stmt_execute being killed. Basically it was not copying the error from net into the prepared statement's error area and was not resetting the state when trying to read the result and failing. Hence, at close, the client was trying to read the rest of the resultset and failing. Fixed by properly handling the error condition. Problem2: The server side materialized cursor wasn't closed properly when a KILL was sent while the statement was still running and did not receive all of the data from the cursor. Fixed by making sure the cursor is properly disposed of. Test case added. Change-Id: Iff8305972948c4239bead8bd08b11088289a297e
1 parent 5430cd9 commit 339e3da

3 files changed

Lines changed: 88 additions & 1 deletion

File tree

‎libmysql/libmysql.cc‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1760,8 +1760,12 @@ static bool execute(MYSQL_STMT *stmt, char *packet, ulong length,
17601760
(the reset of the result set will be read in prepare_to_fetch_result).
17611761
*/
17621762

1763-
if ((pkt_len = cli_safe_read(mysql, &is_data_packet)) == packet_error)
1763+
if ((pkt_len = cli_safe_read(mysql, &is_data_packet)) == packet_error) {
1764+
set_stmt_errmsg(stmt, net);
1765+
mysql->status = MYSQL_STATUS_READY;
1766+
stmt->read_row_func = stmt_read_row_no_data;
17641767
return true;
1768+
}
17651769

17661770
if (is_data_packet) {
17671771
assert(stmt->result.rows == 0);

‎sql/sql_prepare.cc‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3467,6 +3467,15 @@ bool Prepared_statement::execute(THD *thd, String *expanded_query,
34673467
stmt_backup.save_rlb(thd);
34683468

34693469
auto execute_guard = create_scope_guard([&]() {
3470+
// In an error situation, cursor may have been left open, close it:
3471+
if (status && open_cursor) {
3472+
if (m_cursor == nullptr && m_cursor_result != nullptr) {
3473+
m_cursor = m_cursor_result->cursor();
3474+
}
3475+
if (m_cursor != nullptr && m_cursor->is_open()) {
3476+
close_cursor();
3477+
}
3478+
}
34703479
/*
34713480
Restore the current database (if changed).
34723481

‎testclients/mysql_client_test.cc‎

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@
3636
#include <stdio.h>
3737
#include <stdlib.h>
3838
#include <sys/types.h>
39+
#include <condition_variable>
3940
#include <memory>
41+
#include <mutex>
42+
#include <thread>
4043

4144
#include "my_byteorder.h"
4245
#include "my_compiler.h"
@@ -23283,6 +23286,76 @@ static void test_bug33535746() {
2328323286
myquery(rc);
2328423287
}
2328523288

23289+
static void test_bug25584097() {
23290+
DBUG_TRACE;
23291+
myheader("test_bug25584097");
23292+
int rc;
23293+
23294+
class test_bug25584097_thd {
23295+
unsigned long thread_id{0};
23296+
std::condition_variable cnd;
23297+
std::mutex mtx;
23298+
23299+
public:
23300+
void run() {
23301+
int rc;
23302+
MYSQL *lmysql;
23303+
MYSQL_STMT *stmt;
23304+
const char *sqlstmt = "select sleep(300)";
23305+
unsigned long ct = (unsigned long)CURSOR_TYPE_READ_ONLY;
23306+
23307+
printf("child thread start\n");
23308+
lmysql = mysql_client_init(nullptr);
23309+
DIE_UNLESS(lmysql);
23310+
23311+
if (!mysql_real_connect(lmysql, opt_host, opt_user, opt_password,
23312+
current_db, opt_port, opt_unix_socket, 0)) {
23313+
fprintf(stderr, "Failed to connect to the database\n");
23314+
DIE_UNLESS(0);
23315+
}
23316+
23317+
{
23318+
std::unique_lock lk(mtx);
23319+
thread_id = mysql_thread_id(lmysql);
23320+
}
23321+
stmt = mysql_stmt_init(lmysql);
23322+
DIE_UNLESS(stmt != nullptr);
23323+
rc = mysql_stmt_prepare(stmt, sqlstmt, (unsigned long)strlen(sqlstmt));
23324+
DIE_UNLESS(rc == 0);
23325+
rc = mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (const void *)&ct);
23326+
DIE_UNLESS(rc == 0);
23327+
printf("child thread ready to exec\n");
23328+
cnd.notify_one();
23329+
rc = mysql_stmt_execute(stmt);
23330+
DIE_UNLESS(rc != 0);
23331+
printf("child thread cleaning up\n");
23332+
rc = mysql_stmt_close(stmt);
23333+
DIE_UNLESS(rc == 0);
23334+
mysql_close(lmysql);
23335+
printf("child thread ending\n");
23336+
}
23337+
23338+
unsigned long wait_to_kill() {
23339+
std::unique_lock lk(mtx);
23340+
cnd.wait(lk, [this] { return thread_id != 0; });
23341+
return thread_id;
23342+
}
23343+
} foo;
23344+
23345+
std::thread thd(&test_bug25584097_thd::run, &foo);
23346+
printf("Waiting for the child thread\n");
23347+
unsigned long thd_to_kill = foo.wait_to_kill();
23348+
sleep(2);
23349+
23350+
printf("Killing the child thread\n");
23351+
char cmd[50];
23352+
sprintf(cmd, "KILL %lu", thd_to_kill);
23353+
rc = mysql_query(mysql, cmd);
23354+
myquery(rc);
23355+
printf("Wating for the child thread to finish\n");
23356+
thd.join();
23357+
}
23358+
2328623359
static struct my_tests_st my_tests[] = {
2328723360
{"test_bug5194", test_bug5194},
2328823361
{"disable_query_logs", disable_query_logs},
@@ -23596,6 +23669,7 @@ static struct my_tests_st my_tests[] = {
2359623669
{"test_wl13075", test_wl13075},
2359723670
{"test_bug34007830", test_bug34007830},
2359823671
{"test_bug33535746", test_bug33535746},
23672+
{"test_bug25584097", test_bug25584097},
2359923673
{nullptr, nullptr}};
2360023674

2360123675
static struct my_tests_st *get_my_tests() { return my_tests; }

0 commit comments

Comments
 (0)