Skip to content

Commit 10ccf8b

Browse files
committed
Prevent concurrent uses of #ping and #close
Fix: #1433 Apply the same locking mecanism `#query` uses to these two methods.
1 parent d694a45 commit 10ccf8b

2 files changed

Lines changed: 56 additions & 21 deletions

File tree

ext/mysql2/client.c

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,26 @@ static VALUE allocate(VALUE klass) {
420420
return obj;
421421
}
422422

423+
static void rb_mysql_client_set_active_fiber(VALUE self, bool async_check) {
424+
VALUE fiber_current = rb_fiber_current();
425+
GET_CLIENT(self);
426+
427+
// see if this connection is still waiting on a result from a previous query
428+
if (NIL_P(wrapper->active_fiber)) {
429+
// mark this connection active
430+
wrapper->active_fiber = fiber_current;
431+
} else if (wrapper->active_fiber == fiber_current) {
432+
if (async_check) {
433+
rb_raise(cMysql2Error, "This connection is still waiting for a result, try again once you have the result");
434+
}
435+
} else {
436+
VALUE inspect = rb_inspect(wrapper->active_fiber);
437+
const char *thr = StringValueCStr(inspect);
438+
439+
rb_raise(cMysql2Error, "This connection is in use by: %s", thr);
440+
}
441+
}
442+
423443
/* call-seq:
424444
* Mysql2::Client.escape(string)
425445
*
@@ -571,11 +591,14 @@ static VALUE rb_mysql_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VA
571591
*/
572592
static VALUE rb_mysql_client_close(VALUE self) {
573593
GET_CLIENT(self);
594+
rb_mysql_client_set_active_fiber(self, false);
574595

575596
if (wrapper->client) {
576597
rb_thread_call_without_gvl(nogvl_close, wrapper, RUBY_UBF_IO, 0);
577598
}
578599

600+
wrapper->active_fiber = Qnil;
601+
579602
return Qnil;
580603
}
581604

@@ -798,24 +821,6 @@ static VALUE disconnect_and_mark_inactive(VALUE self) {
798821
return Qnil;
799822
}
800823

801-
static void rb_mysql_client_set_active_fiber(VALUE self) {
802-
VALUE fiber_current = rb_fiber_current();
803-
GET_CLIENT(self);
804-
805-
// see if this connection is still waiting on a result from a previous query
806-
if (NIL_P(wrapper->active_fiber)) {
807-
// mark this connection active
808-
wrapper->active_fiber = fiber_current;
809-
} else if (wrapper->active_fiber == fiber_current) {
810-
rb_raise(cMysql2Error, "This connection is still waiting for a result, try again once you have the result");
811-
} else {
812-
VALUE inspect = rb_inspect(wrapper->active_fiber);
813-
const char *thr = StringValueCStr(inspect);
814-
815-
rb_raise(cMysql2Error, "This connection is in use by: %s", thr);
816-
}
817-
}
818-
819824
/* call-seq:
820825
* client.abandon_results!
821826
*
@@ -873,7 +878,7 @@ static VALUE rb_mysql_query(VALUE self, VALUE sql, VALUE current) {
873878
args.sql_len = RSTRING_LEN(args.sql);
874879
args.wrapper = wrapper;
875880

876-
rb_mysql_client_set_active_fiber(self);
881+
rb_mysql_client_set_active_fiber(self, true);
877882

878883
#ifndef _WIN32
879884
rb_rescue2(do_send_query, (VALUE)&args, disconnect_and_raise, self, rb_eException, (VALUE)0);
@@ -1233,12 +1238,16 @@ static void *nogvl_ping(void *ptr) {
12331238
*/
12341239
static VALUE rb_mysql_client_ping(VALUE self) {
12351240
GET_CLIENT(self);
1241+
rb_mysql_client_set_active_fiber(self, true);
12361242

1243+
VALUE result = Qnil;
12371244
if (!CONNECTED(wrapper)) {
1238-
return Qfalse;
1245+
result = Qfalse;
12391246
} else {
1240-
return (VALUE)rb_thread_call_without_gvl(nogvl_ping, wrapper->client, RUBY_UBF_IO, 0);
1247+
result = (VALUE)rb_thread_call_without_gvl(nogvl_ping, wrapper->client, RUBY_UBF_IO, 0);
12411248
}
1249+
wrapper->active_fiber = Qnil;
1250+
return result;
12421251
}
12431252

12441253
/* call-seq:

spec/mysql2/client_spec.rb

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1279,4 +1279,30 @@ def connect(*args); end
12791279
expect(client.inspect).not_to include("pass")
12801280
expect(client.inspect).not_to include("secretsecret")
12811281
end
1282+
1283+
it "should not allow concurrent use of #ping" do
1284+
@client.ping
1285+
thread = Thread.new { @client.query("SELECT SLEEP(1)") }
1286+
thread.join(0.1)
1287+
10.times do
1288+
expect do
1289+
@client.ping
1290+
end.to raise_error(Mysql2::Error, /This connection is in use by/)
1291+
end
1292+
expect(thread.value.to_a).to eq([{ "SLEEP(1)" => 0 }])
1293+
expect(@client.ping).to eq(true)
1294+
end
1295+
1296+
it "should not allow concurrent use of #close" do
1297+
@client.ping
1298+
thread = Thread.new { @client.query("SELECT SLEEP(1)") }
1299+
thread.join(0.1)
1300+
10.times do
1301+
expect do
1302+
@client.close
1303+
end.to raise_error(Mysql2::Error, /This connection is in use by/)
1304+
end
1305+
expect(thread.value.to_a).to eq([{ "SLEEP(1)" => 0 }])
1306+
expect(@client.close).to be_nil
1307+
end
12821308
end

0 commit comments

Comments
 (0)