Skip to content

Commit 978a5eb

Browse files
committed
Merge pull request #378 from brianmario/client-refcount
Track connection refcount to prevent GC races
2 parents 64f07e1 + 9c60a85 commit 978a5eb

File tree

5 files changed

+41
-37
lines changed

5 files changed

+41
-37
lines changed

ext/mysql2/client.c

Lines changed: 10 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -193,13 +193,15 @@ static VALUE nogvl_close(void *ptr) {
193193
return Qnil;
194194
}
195195

196-
static void rb_mysql_client_free(void * ptr) {
196+
static void rb_mysql_client_free(void *ptr) {
197197
mysql_client_wrapper *wrapper = (mysql_client_wrapper *)ptr;
198198

199-
nogvl_close(wrapper);
200-
201-
xfree(wrapper->client);
202-
xfree(ptr);
199+
wrapper->refcount--;
200+
if (wrapper->refcount == 0) {
201+
nogvl_close(wrapper);
202+
xfree(wrapper->client);
203+
xfree(wrapper);
204+
}
203205
}
204206

205207
static VALUE allocate(VALUE klass) {
@@ -211,6 +213,7 @@ static VALUE allocate(VALUE klass) {
211213
wrapper->reconnect_enabled = 0;
212214
wrapper->connected = 0; /* means that a database connection is open */
213215
wrapper->initialized = 0; /* means that that the wrapper is initialized */
216+
wrapper->refcount = 1;
214217
wrapper->client = (MYSQL*)xmalloc(sizeof(MYSQL));
215218
return obj;
216219
}
@@ -372,9 +375,6 @@ static VALUE nogvl_use_result(void *ptr) {
372375
static VALUE rb_mysql_client_async_result(VALUE self) {
373376
MYSQL_RES * result;
374377
VALUE resultObj;
375-
#ifdef HAVE_RUBY_ENCODING_H
376-
mysql2_result_wrapper * result_wrapper;
377-
#endif
378378
GET_CLIENT(self);
379379

380380
/* if we're not waiting on a result, do nothing */
@@ -404,14 +404,7 @@ static VALUE rb_mysql_client_async_result(VALUE self) {
404404
return Qnil;
405405
}
406406

407-
resultObj = rb_mysql_result_to_obj(result);
408-
/* pass-through query options for result construction later */
409-
rb_iv_set(resultObj, "@query_options", rb_hash_dup(rb_iv_get(self, "@current_query_options")));
410-
411-
#ifdef HAVE_RUBY_ENCODING_H
412-
GetMysql2Result(resultObj, result_wrapper);
413-
result_wrapper->encoding = wrapper->encoding;
414-
#endif
407+
resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, rb_hash_dup(rb_iv_get(self, "@current_query_options")), result);
415408
return resultObj;
416409
}
417410

@@ -937,10 +930,6 @@ static VALUE rb_mysql_client_store_result(VALUE self)
937930
{
938931
MYSQL_RES * result;
939932
VALUE resultObj;
940-
#ifdef HAVE_RUBY_ENCODING_H
941-
mysql2_result_wrapper * result_wrapper;
942-
#endif
943-
944933
GET_CLIENT(self);
945934

946935
result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, wrapper, RUBY_UBF_IO, 0);
@@ -953,14 +942,7 @@ static VALUE rb_mysql_client_store_result(VALUE self)
953942
return Qnil;
954943
}
955944

956-
resultObj = rb_mysql_result_to_obj(result);
957-
/* pass-through query options for result construction later */
958-
rb_iv_set(resultObj, "@query_options", rb_hash_dup(rb_iv_get(self, "@current_query_options")));
959-
960-
#ifdef HAVE_RUBY_ENCODING_H
961-
GetMysql2Result(resultObj, result_wrapper);
962-
result_wrapper->encoding = wrapper->encoding;
963-
#endif
945+
resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, rb_hash_dup(rb_iv_get(self, "@current_query_options")), result);
964946
return resultObj;
965947

966948
}

ext/mysql2/client.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ typedef struct {
3838
int active;
3939
int connected;
4040
int initialized;
41+
int refcount;
42+
int freed;
4143
MYSQL *client;
4244
} mysql_client_wrapper;
4345

ext/mysql2/result.c

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,22 +64,33 @@ static void rb_mysql_result_mark(void * wrapper) {
6464
rb_gc_mark(w->fields);
6565
rb_gc_mark(w->rows);
6666
rb_gc_mark(w->encoding);
67+
rb_gc_mark(w->client);
6768
}
6869
}
6970

7071
/* this may be called manually or during GC */
7172
static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) {
7273
if (wrapper && wrapper->resultFreed != 1) {
74+
/* FIXME: this may call flush_use_result, which can hit the socket */
7375
mysql_free_result(wrapper->result);
7476
wrapper->resultFreed = 1;
7577
}
7678
}
7779

7880
/* this is called during GC */
79-
static void rb_mysql_result_free(void * wrapper) {
80-
mysql2_result_wrapper * w = wrapper;
81-
/* FIXME: this may call flush_use_result, which can hit the socket */
82-
rb_mysql_result_free_result(w);
81+
static void rb_mysql_result_free(void *ptr) {
82+
mysql2_result_wrapper * wrapper = ptr;
83+
rb_mysql_result_free_result(wrapper);
84+
85+
// If the GC gets to client first it will be nil
86+
if (wrapper->client != Qnil) {
87+
wrapper->client_wrapper->refcount--;
88+
if (wrapper->client_wrapper->refcount == 0) {
89+
xfree(wrapper->client_wrapper->client);
90+
xfree(wrapper->client_wrapper);
91+
}
92+
}
93+
8394
xfree(wrapper);
8495
}
8596

@@ -565,7 +576,7 @@ static VALUE rb_mysql_result_count(VALUE self) {
565576
}
566577

567578
/* Mysql2::Result */
568-
VALUE rb_mysql_result_to_obj(MYSQL_RES * r) {
579+
VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r) {
569580
VALUE obj;
570581
mysql2_result_wrapper * wrapper;
571582
obj = Data_Make_Struct(cMysql2Result, mysql2_result_wrapper, rb_mysql_result_mark, rb_mysql_result_free, wrapper);
@@ -576,9 +587,16 @@ VALUE rb_mysql_result_to_obj(MYSQL_RES * r) {
576587
wrapper->result = r;
577588
wrapper->fields = Qnil;
578589
wrapper->rows = Qnil;
579-
wrapper->encoding = Qnil;
590+
wrapper->encoding = encoding;
580591
wrapper->streamingComplete = 0;
592+
wrapper->client = client;
593+
wrapper->client_wrapper = DATA_PTR(client);
594+
wrapper->client_wrapper->refcount++;
595+
581596
rb_obj_call_init(obj, 0, NULL);
597+
598+
rb_iv_set(obj, "@query_options", options);
599+
582600
return obj;
583601
}
584602

ext/mysql2/result.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,20 @@
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(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r);
66

77
typedef struct {
88
VALUE fields;
99
VALUE rows;
10+
VALUE client;
1011
VALUE encoding;
1112
unsigned int numberOfFields;
1213
unsigned long numberOfRows;
1314
unsigned long lastRowProcessed;
1415
char streamingComplete;
1516
char resultFreed;
1617
MYSQL_RES *result;
18+
mysql_client_wrapper *client_wrapper;
1719
} mysql2_result_wrapper;
1820

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

spec/mysql2/client_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,10 +257,10 @@ def connect *args
257257
mark[:END] = Time.now
258258
mark.include?(:USR1).should be_true
259259
(mark[:USR1] - mark[:START]).should >= 1
260-
(mark[:USR1] - mark[:START]).should < 1.1
260+
(mark[:USR1] - mark[:START]).should < 1.2
261261
(mark[:END] - mark[:USR1]).should > 0.9
262262
(mark[:END] - mark[:START]).should >= 2
263-
(mark[:END] - mark[:START]).should < 2.1
263+
(mark[:END] - mark[:START]).should < 2.2
264264
Process.kill(:TERM, pid)
265265
Process.waitpid2(pid)
266266
ensure

0 commit comments

Comments
 (0)