Skip to content

Commit 65d5872

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 dc1cc1c commit 65d5872

2 files changed

Lines changed: 53 additions & 20 deletions

File tree

ext/mysql2/client.c

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

423+
static void rb_mysql_client_set_active_fiber(VALUE self) {
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+
rb_raise(cMysql2Error, "This connection is still waiting for a result, try again once you have the result");
433+
} else {
434+
VALUE inspect = rb_inspect(wrapper->active_fiber);
435+
const char *thr = StringValueCStr(inspect);
436+
437+
rb_raise(cMysql2Error, "This connection is in use by: %s", thr);
438+
}
439+
}
440+
423441
/* call-seq:
424442
* Mysql2::Client.escape(string)
425443
*
@@ -571,11 +589,14 @@ static VALUE rb_mysql_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VA
571589
*/
572590
static VALUE rb_mysql_client_close(VALUE self) {
573591
GET_CLIENT(self);
592+
rb_mysql_client_set_active_fiber(self);
574593

575594
if (wrapper->client) {
576595
rb_thread_call_without_gvl(nogvl_close, wrapper, RUBY_UBF_IO, 0);
577596
}
578597

598+
wrapper->active_fiber = Qnil;
599+
579600
return Qnil;
580601
}
581602

@@ -798,24 +819,6 @@ static VALUE disconnect_and_mark_inactive(VALUE self) {
798819
return Qnil;
799820
}
800821

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-
819822
/* call-seq:
820823
* client.abandon_results!
821824
*
@@ -1233,12 +1236,16 @@ static void *nogvl_ping(void *ptr) {
12331236
*/
12341237
static VALUE rb_mysql_client_ping(VALUE self) {
12351238
GET_CLIENT(self);
1239+
rb_mysql_client_set_active_fiber(self);
12361240

1241+
VALUE result = Qnil;
12371242
if (!CONNECTED(wrapper)) {
1238-
return Qfalse;
1243+
result = Qfalse;
12391244
} else {
1240-
return (VALUE)rb_thread_call_without_gvl(nogvl_ping, wrapper->client, RUBY_UBF_IO, 0);
1245+
result = (VALUE)rb_thread_call_without_gvl(nogvl_ping, wrapper->client, RUBY_UBF_IO, 0);
12411246
}
1247+
wrapper->active_fiber = Qnil;
1248+
return result;
12421249
}
12431250

12441251
/* call-seq:

spec/mysql2/client_spec.rb

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

0 commit comments

Comments
 (0)