Skip to content

Commit 100435c

Browse files
committed
Close connections during garbage collection by default
1 parent 64d7ff8 commit 100435c

File tree

3 files changed

+55
-69
lines changed

3 files changed

+55
-69
lines changed

ext/mysql2/client.c

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -260,11 +260,7 @@ static VALUE allocate(VALUE klass) {
260260
obj = Data_Make_Struct(klass, mysql_client_wrapper, rb_mysql_client_mark, rb_mysql_client_free, wrapper);
261261
wrapper->encoding = Qnil;
262262
MARK_CONN_INACTIVE(self);
263-
#ifndef _WIN32
264-
wrapper->automatic_close = 0;
265-
#else
266263
wrapper->automatic_close = 1;
267-
#endif
268264
wrapper->server_version = 0;
269265
wrapper->reconnect_enabled = 0;
270266
wrapper->connect_timeout = 0;
@@ -386,8 +382,10 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
386382
}
387383

388384
/*
389-
* Terminate the connection; call this when the connection is no longer needed.
390-
* To have the garbage collector close the connection, enable +automatic_close+.
385+
* Immediately disconnect from the server; normally the garbage collector
386+
* will disconnect automatically when a connection is no longer needed.
387+
* Explicitly closing this will free up server resources sooner than waiting
388+
* for the garbage collector.
391389
*
392390
* @return [nil]
393391
*/
@@ -1098,9 +1096,13 @@ static VALUE get_automatic_close(VALUE self) {
10981096
}
10991097

11001098
/* call-seq:
1101-
* client.automatic_close = true
1099+
* client.automatic_close = false
1100+
*
1101+
* Set this to +false+ to leave the connection open after it is garbage
1102+
* collected. To avoid "Aborted connection" errors on the server, explicitly
1103+
* call +close+ when the connection is no longer needed.
11021104
*
1103-
* Set this to +true+ to let the garbage collector close this connection.
1105+
* @see http://dev.mysql.com/doc/en/communication-errors.html
11041106
*/
11051107
static VALUE set_automatic_close(VALUE self, VALUE value) {
11061108
GET_CLIENT(self);

spec/mysql2/client_spec.rb

Lines changed: 45 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -166,57 +166,36 @@ def run_gc
166166
expect {
167167
Mysql2::Client.new(DatabaseCredentials['root']).close
168168
}.to_not change {
169-
@client.query("SHOW STATUS LIKE 'Aborted_clients'").first['Value'].to_i
169+
@client.query("SHOW STATUS LIKE 'Aborted_%'").to_a +
170+
@client.query("SHOW STATUS LIKE 'Threads_connected'").to_a
170171
}
171172
end
172173

173174
it "should not leave dangling connections after garbage collection" do
174175
run_gc
176+
expect {
177+
expect {
178+
10.times do
179+
Mysql2::Client.new(DatabaseCredentials['root']).query('SELECT 1')
180+
end
181+
}.to change {
182+
@client.query("SHOW STATUS LIKE 'Threads_connected'").first['Value'].to_i
183+
}.by(10)
175184

176-
client = Mysql2::Client.new(DatabaseCredentials['root'])
177-
before_count = client.query("SHOW STATUS LIKE 'Threads_connected'").first['Value'].to_i
178-
179-
10.times do
180-
Mysql2::Client.new(DatabaseCredentials['root']).query('SELECT 1')
181-
end
182-
after_count = client.query("SHOW STATUS LIKE 'Threads_connected'").first['Value'].to_i
183-
expect(after_count).to eq(before_count + 10)
184-
185-
run_gc
186-
final_count = client.query("SHOW STATUS LIKE 'Threads_connected'").first['Value'].to_i
187-
expect(final_count).to eq(before_count)
188-
end
189-
190-
it "should not close connections when running in a child process" do
191-
pending("fork is not available on this platform") unless Process.respond_to?(:fork)
192-
193-
run_gc
194-
client = Mysql2::Client.new(DatabaseCredentials['root'])
195-
196-
# this empty `fork` call fixes this tests on RBX; without it, the next
197-
# `fork` call hangs forever. WTF?
198-
fork {}
199-
200-
fork do
201-
client.query('SELECT 1')
202-
client = nil
203185
run_gc
204-
end
205-
206-
Process.wait
207-
208-
# this will throw an error if the underlying socket was shutdown by the
209-
# child's GC
210-
expect { client.query('SELECT 1') }.to_not raise_exception
186+
}.to_not change {
187+
@client.query("SHOW STATUS LIKE 'Aborted_%'").to_a +
188+
@client.query("SHOW STATUS LIKE 'Threads_connected'").to_a
189+
}
211190
end
212191

213192
context "#automatic_close" do
214-
if RUBY_PLATFORM =~ /mingw|mswin/
215-
it "is enabled by default" do
216-
client = Mysql2::Client.new(DatabaseCredentials['root'])
217-
expect(client.automatic_close?).to be(true)
218-
end
193+
it "is enabled by default" do
194+
client = Mysql2::Client.new(DatabaseCredentials['root'])
195+
expect(client.automatic_close?).to be(true)
196+
end
219197

198+
if RUBY_PLATFORM =~ /mingw|mswin/
220199
it "cannot be disabled" do
221200
expect {
222201
Mysql2::Client.new(DatabaseCredentials['root'].merge(:automatic_close => false))
@@ -228,41 +207,47 @@ def run_gc
228207
expect { client.automatic_close = true }.to_not raise_error
229208
end
230209
else
231-
it "is disabled by default" do
232-
client = Mysql2::Client.new(DatabaseCredentials['root'])
233-
expect(client.automatic_close?).to be(false)
234-
end
235-
236210
it "can be configured" do
237-
client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:automatic_close => true))
238-
expect(client.automatic_close?).to be(true)
211+
client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:automatic_close => false))
212+
expect(client.automatic_close?).to be(false)
239213
end
240214

241215
it "can be assigned" do
242216
client = Mysql2::Client.new(DatabaseCredentials['root'])
243-
client.automatic_close = true
244-
expect(client.automatic_close?).to be(true)
245-
246217
client.automatic_close = false
247218
expect(client.automatic_close?).to be(false)
248219

249-
client.automatic_close = 9
220+
client.automatic_close = true
250221
expect(client.automatic_close?).to be(true)
251222

252223
client.automatic_close = nil
253224
expect(client.automatic_close?).to be(false)
225+
226+
client.automatic_close = 9
227+
expect(client.automatic_close?).to be(true)
254228
end
255-
end
256229

257-
it "should terminate connections during garbage collection" do
258-
run_gc
259-
expect {
260-
Mysql2::Client.new(DatabaseCredentials['root'].merge(:automatic_close => true)).query('SELECT 1')
230+
it "should not close connections when running in a child process" do
261231
run_gc
262-
}.to_not change {
263-
@client.query("SHOW STATUS LIKE 'Aborted_%'").to_a +
264-
@client.query("SHOW STATUS LIKE 'Threads_connected'").to_a
265-
}
232+
client = Mysql2::Client.new(DatabaseCredentials['root'])
233+
client.automatic_close = false
234+
235+
# this empty `fork` call fixes this tests on RBX; without it, the next
236+
# `fork` call hangs forever. WTF?
237+
fork {}
238+
239+
fork do
240+
client.query('SELECT 1')
241+
client = nil
242+
run_gc
243+
end
244+
245+
Process.wait
246+
247+
# this will throw an error if the underlying socket was shutdown by the
248+
# child's GC
249+
expect { client.query('SELECT 1') }.to_not raise_exception
250+
end
266251
end
267252
end
268253

spec/spec_helper.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,5 @@ def with_internal_encoding(encoding)
9090
"test", "test", 'val1', 'val1,val2'
9191
)
9292
]
93-
client.close
9493
end
9594
end

0 commit comments

Comments
 (0)