Skip to content

Commit 818d74b

Browse files
committed
Merge pull request #663 from cbandy/terminate-on-close
Terminate connections correctly when calling close
2 parents f9b59d0 + 7f19dc9 commit 818d74b

File tree

2 files changed

+38
-24
lines changed

2 files changed

+38
-24
lines changed

ext/mysql2/client.c

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -209,32 +209,19 @@ static VALUE invalidate_fd(int clientfd)
209209
#endif /* _WIN32 */
210210

211211
static void *nogvl_close(void *ptr) {
212-
mysql_client_wrapper *wrapper;
213-
wrapper = ptr;
214-
if (wrapper->connected) {
215-
MARK_CONN_INACTIVE(self);
216-
wrapper->connected = 0;
217-
#ifndef _WIN32
218-
/* Invalidate the socket before calling mysql_close(). This prevents
219-
* mysql_close() from sending a mysql-QUIT or from calling shutdown() on
220-
* the socket. The difference is that invalidate_fd will drop this
221-
* process's reference to the socket only, while a QUIT or shutdown()
222-
* would render the underlying connection unusable, interrupting other
223-
* processes which share this object across a fork().
224-
*/
225-
if (invalidate_fd(wrapper->client->net.fd) == Qfalse) {
226-
fprintf(stderr, "[WARN] mysql2 failed to invalidate FD safely, leaking some memory\n");
227-
close(wrapper->client->net.fd);
228-
return NULL;
229-
}
230-
#endif
212+
mysql_client_wrapper *wrapper = ptr;
231213

232-
mysql_close(wrapper->client); /* only used to free memory at this point */
214+
if (wrapper->client) {
215+
mysql_close(wrapper->client);
216+
wrapper->client = NULL;
217+
wrapper->connected = 0;
218+
wrapper->active_thread = Qnil;
233219
}
234220

235221
return NULL;
236222
}
237223

224+
/* this is called during GC */
238225
static void rb_mysql_client_free(void *ptr) {
239226
mysql_client_wrapper *wrapper = (mysql_client_wrapper *)ptr;
240227
decr_mysql2_client(wrapper);
@@ -245,6 +232,22 @@ void decr_mysql2_client(mysql_client_wrapper *wrapper)
245232
wrapper->refcount--;
246233

247234
if (wrapper->refcount == 0) {
235+
#ifndef _WIN32
236+
if (wrapper->connected) {
237+
/* The client is being garbage collected while connected. Prevent
238+
* mysql_close() from sending a mysql-QUIT or from calling shutdown() on
239+
* the socket by invalidating it. invalidate_fd() will drop this
240+
* process's reference to the socket only, while a QUIT or shutdown()
241+
* would render the underlying connection unusable, interrupting other
242+
* processes which share this object across a fork().
243+
*/
244+
if (invalidate_fd(wrapper->client->net.fd) == Qfalse) {
245+
fprintf(stderr, "[WARN] mysql2 failed to invalidate FD safely\n");
246+
close(wrapper->client->net.fd);
247+
}
248+
}
249+
#endif
250+
248251
nogvl_close(wrapper);
249252
xfree(wrapper->client);
250253
xfree(wrapper);
@@ -378,10 +381,13 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
378381
}
379382

380383
/*
381-
* Immediately disconnect from the server, normally the garbage collector
382-
* will disconnect automatically when a connection is no longer needed.
383-
* Explicitly closing this will free up server resources sooner than waiting
384-
* for the garbage collector.
384+
* Terminate the connection; call this when the connection is no longer needed.
385+
* The garbage collector can close the connection, but doing so emits an
386+
* "Aborted connection" error on the server and increments the Aborted_clients
387+
* status variable.
388+
*
389+
* @see http://dev.mysql.com/doc/en/communication-errors.html
390+
* @return [void]
385391
*/
386392
static VALUE rb_mysql_client_close(VALUE self) {
387393
GET_CLIENT(self);

spec/mysql2/client_spec.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,14 @@ def run_gc
162162
sleep(0.5)
163163
end
164164

165+
it "should terminate connections when calling close" do
166+
expect {
167+
Mysql2::Client.new(DatabaseCredentials['root']).close
168+
}.to_not change {
169+
@client.query("SHOW STATUS LIKE 'Aborted_clients'").first['Value'].to_i
170+
}
171+
end
172+
165173
it "should not leave dangling connections after garbage collection" do
166174
run_gc
167175

0 commit comments

Comments
 (0)