Skip to content

Commit 9c76acf

Browse files
committed
Keep a reference from the Result to the Statement
This prevents the GC from getting the Statement first, which would call mysql_stmt_close, which calls mysql_stmt_result_free on any outstanding results. When the Result object gets GC next, it also calls mysql_stmt_result_free and that would crash.
1 parent d0fef7c commit 9c76acf

File tree

5 files changed

+20
-7
lines changed

5 files changed

+20
-7
lines changed

ext/mysql2/client.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,7 @@ static VALUE rb_mysql_client_async_result(VALUE self) {
490490
current = rb_hash_dup(rb_iv_get(self, "@current_query_options"));
491491
(void)RB_GC_GUARD(current);
492492
Check_Type(current, T_HASH);
493-
resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, NULL);
493+
resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, Qnil);
494494

495495
return resultObj;
496496
}
@@ -1050,7 +1050,7 @@ static VALUE rb_mysql_client_store_result(VALUE self)
10501050
current = rb_hash_dup(rb_iv_get(self, "@current_query_options"));
10511051
(void)RB_GC_GUARD(current);
10521052
Check_Type(current, T_HASH);
1053-
resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, NULL);
1053+
resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, Qnil);
10541054

10551055
return resultObj;
10561056
}

ext/mysql2/result.c

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,26 +75,28 @@ static VALUE sym_symbolize_keys, sym_as, sym_array, sym_database_timezone, sym_a
7575
sym_local, sym_utc, sym_cast_booleans, sym_cache_rows, sym_cast, sym_stream, sym_name;
7676
static ID intern_merge;
7777

78+
/* Mark any VALUEs that are only referenced in C, so the GC won't get them. */
7879
static void rb_mysql_result_mark(void * wrapper) {
7980
mysql2_result_wrapper * w = wrapper;
8081
if (w) {
8182
rb_gc_mark(w->fields);
8283
rb_gc_mark(w->rows);
8384
rb_gc_mark(w->encoding);
8485
rb_gc_mark(w->client);
86+
rb_gc_mark(w->statement);
8587
}
8688
}
8789

8890
/* this may be called manually or during GC */
8991
static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) {
90-
unsigned int i;
9192
if (!wrapper) return;
9293

9394
if (wrapper->resultFreed != 1) {
9495
if (wrapper->stmt) {
9596
mysql_stmt_free_result(wrapper->stmt);
9697

9798
if (wrapper->result_buffers) {
99+
unsigned int i;
98100
for (i = 0; i < wrapper->numberOfFields; i++) {
99101
if (wrapper->result_buffers[i].buffer) {
100102
xfree(wrapper->result_buffers[i].buffer);
@@ -107,6 +109,7 @@ static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) {
107109
}
108110
}
109111
/* FIXME: this may call flush_use_result, which can hit the socket */
112+
/* For prepared statements, wrapper->result is the result metadata */
110113
mysql_free_result(wrapper->result);
111114
wrapper->resultFreed = 1;
112115
}
@@ -949,7 +952,7 @@ static VALUE rb_mysql_result_count(VALUE self) {
949952
}
950953

951954
/* Mysql2::Result */
952-
VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r, MYSQL_STMT * s) {
955+
VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r, VALUE statement) {
953956
VALUE obj;
954957
mysql2_result_wrapper * wrapper;
955958

@@ -966,12 +969,19 @@ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_
966969
wrapper->client = client;
967970
wrapper->client_wrapper = DATA_PTR(client);
968971
wrapper->client_wrapper->refcount++;
969-
wrapper->stmt = s;
970972
wrapper->result_buffers = NULL;
971973
wrapper->is_null = NULL;
972974
wrapper->error = NULL;
973975
wrapper->length = NULL;
974976

977+
/* Keep a handle to the Statement to ensure it doesn't get garbage collected first */
978+
wrapper->statement = statement;
979+
if (statement != Qnil) {
980+
mysql_stmt_wrapper *stmt_wrapper = DATA_PTR(statement);
981+
wrapper->stmt = stmt_wrapper->stmt;
982+
stmt_wrapper->refcount++;
983+
}
984+
975985
rb_obj_call_init(obj, 0, NULL);
976986
rb_iv_set(obj, "@query_options", options);
977987

ext/mysql2/result.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22
#define MYSQL2_RESULT_H
33

44
void init_mysql2_result();
5-
VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r, MYSQL_STMT * s);
5+
VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r, VALUE statement);
66

77
typedef struct {
88
VALUE fields;
99
VALUE rows;
1010
VALUE client;
1111
VALUE encoding;
12+
VALUE statement;
1213
unsigned int numberOfFields;
1314
unsigned long numberOfRows;
1415
unsigned long lastRowProcessed;

ext/mysql2/statement.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,12 +359,13 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
359359
if (!is_streaming) {
360360
// recieve the whole result set from the server
361361
if (rb_thread_call_without_gvl(nogvl_stmt_store_result, stmt, RUBY_UBF_IO, 0) == Qfalse) {
362+
mysql_free_result(metadata);
362363
rb_raise_mysql2_stmt_error(self);
363364
}
364365
MARK_CONN_INACTIVE(stmt_wrapper->client);
365366
}
366367

367-
resultObj = rb_mysql_result_to_obj(stmt_wrapper->client, wrapper->encoding, current, metadata, stmt);
368+
resultObj = rb_mysql_result_to_obj(stmt_wrapper->client, wrapper->encoding, current, metadata, self);
368369

369370
if (!is_streaming) {
370371
// cache all result

ext/mysql2/statement.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ void init_mysql2_statement();
88
typedef struct {
99
VALUE client;
1010
MYSQL_STMT *stmt;
11+
int refcount;
1112
} mysql_stmt_wrapper;
1213

1314
VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql);

0 commit comments

Comments
 (0)