Skip to content

Commit b241f54

Browse files
committed
Track connection refcount to prevent GC races
In certain cases a Mysql2::Client and a Mysql2::Result may be freed in the same GC run. If the Client is freed before the Result a segv may occur when the Result tries to use the socket to make sure there isn't any more data to read before freeing itself. Huge thanks to @evanphx for helping me track this one down and @rkh for helping with TravisCI since that's where the issue was finally reproducable.
1 parent 5ebf341 commit b241f54

File tree

4 files changed

+15
-7
lines changed

4 files changed

+15
-7
lines changed

ext/mysql2/client.c

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -185,10 +185,12 @@ static VALUE nogvl_close(void *ptr) {
185185
static void rb_mysql_client_free(void * ptr) {
186186
mysql_client_wrapper *wrapper = (mysql_client_wrapper *)ptr;
187187

188-
nogvl_close(wrapper);
188+
if (wrapper->refcount == 0) {
189+
nogvl_close(wrapper);
189190

190-
xfree(wrapper->client);
191-
xfree(ptr);
191+
xfree(wrapper->client);
192+
xfree(ptr);
193+
}
192194
}
193195

194196
static VALUE allocate(VALUE klass) {
@@ -200,6 +202,7 @@ static VALUE allocate(VALUE klass) {
200202
wrapper->reconnect_enabled = 0;
201203
wrapper->connected = 0; /* means that a database connection is open */
202204
wrapper->initialized = 0; /* means that that the wrapper is initialized */
205+
wrapper->refcount = 0;
203206
wrapper->client = (MYSQL*)xmalloc(sizeof(MYSQL));
204207
return obj;
205208
}
@@ -393,7 +396,7 @@ static VALUE rb_mysql_client_async_result(VALUE self) {
393396
return Qnil;
394397
}
395398

396-
resultObj = rb_mysql_result_to_obj(result);
399+
resultObj = rb_mysql_result_to_obj(wrapper, result);
397400
/* pass-through query options for result construction later */
398401
rb_iv_set(resultObj, "@query_options", rb_hash_dup(rb_iv_get(self, "@current_query_options")));
399402

@@ -942,7 +945,7 @@ static VALUE rb_mysql_client_store_result(VALUE self)
942945
return Qnil;
943946
}
944947

945-
resultObj = rb_mysql_result_to_obj(result);
948+
resultObj = rb_mysql_result_to_obj(wrapper, result);
946949
/* pass-through query options for result construction later */
947950
rb_iv_set(resultObj, "@query_options", rb_hash_dup(rb_iv_get(self, "@current_query_options")));
948951

ext/mysql2/client.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ typedef struct {
3838
int active;
3939
int connected;
4040
int initialized;
41+
int refcount;
4142
MYSQL *client;
4243
} mysql_client_wrapper;
4344

ext/mysql2/result.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) {
7272
if (wrapper && wrapper->resultFreed != 1) {
7373
mysql_free_result(wrapper->result);
7474
wrapper->resultFreed = 1;
75+
wrapper->client_wrapper->refcount--;
7576
}
7677
}
7778

@@ -562,7 +563,7 @@ static VALUE rb_mysql_result_count(VALUE self) {
562563
}
563564

564565
/* Mysql2::Result */
565-
VALUE rb_mysql_result_to_obj(MYSQL_RES * r) {
566+
VALUE rb_mysql_result_to_obj(mysql_client_wrapper *client_wrapper, MYSQL_RES *r) {
566567
VALUE obj;
567568
mysql2_result_wrapper * wrapper;
568569
obj = Data_Make_Struct(cMysql2Result, mysql2_result_wrapper, rb_mysql_result_mark, rb_mysql_result_free, wrapper);
@@ -575,6 +576,8 @@ VALUE rb_mysql_result_to_obj(MYSQL_RES * r) {
575576
wrapper->rows = Qnil;
576577
wrapper->encoding = Qnil;
577578
wrapper->streamingComplete = 0;
579+
wrapper->client_wrapper = client_wrapper;
580+
wrapper->client_wrapper->refcount++;
578581
rb_obj_call_init(obj, 0, NULL);
579582
return obj;
580583
}

ext/mysql2/result.h

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

44
void init_mysql2_result();
5-
VALUE rb_mysql_result_to_obj(MYSQL_RES * r);
5+
VALUE rb_mysql_result_to_obj(mysql_client_wrapper *client_wrapper, MYSQL_RES * r);
66

77
typedef struct {
88
VALUE fields;
@@ -14,6 +14,7 @@ typedef struct {
1414
char streamingComplete;
1515
char resultFreed;
1616
MYSQL_RES *result;
17+
mysql_client_wrapper *client_wrapper;
1718
} mysql2_result_wrapper;
1819

1920
#define GetMysql2Result(obj, sval) (sval = (mysql2_result_wrapper*)DATA_PTR(obj));

0 commit comments

Comments
 (0)