add Mysql2::Client#close method

This allows users to (manually) avoid the tricky case of hitting
a socket write during GC and potentially blocking the
interpreter during GC.  An explicit `close' is also useful in
situations where server resources are limited and a client only
needs to connect for a limited time.
This commit is contained in:
Eric Wong 2010-05-05 17:38:08 -07:00
parent f7a6b49cbd
commit 1d92db8aab
3 changed files with 68 additions and 14 deletions

View File

@ -22,6 +22,15 @@
* - mysql_ssl_set()
*/
static VALUE nogvl_init(void *ptr) {
struct nogvl_connect_args *args = ptr;
/* may initialize embedded server and read /etc/services off disk */
args->mysql = mysql_init(NULL);
return args->mysql == NULL ? Qfalse : Qtrue;
}
static VALUE nogvl_connect(void *ptr)
{
struct nogvl_connect_args *args = ptr;
@ -37,6 +46,7 @@ static VALUE nogvl_connect(void *ptr)
/* Mysql2::Client */
static VALUE rb_mysql_client_new(int argc, VALUE * argv, VALUE klass) {
mysql2_client_wrapper * client;
struct nogvl_connect_args args = {
.host = "localhost",
.user = NULL,
@ -57,7 +67,7 @@ static VALUE rb_mysql_client_new(int argc, VALUE * argv, VALUE klass) {
unsigned int connect_timeout = 0;
my_bool reconnect = 1;
obj = Data_Make_Struct(klass, MYSQL, NULL, rb_mysql_client_free, args.mysql);
obj = Data_Make_Struct(klass, mysql2_client_wrapper, NULL, rb_mysql_client_free, client);
if (rb_scan_args(argc, argv, "01", &opts) == 1) {
Check_Type(opts, T_HASH);
@ -128,12 +138,7 @@ static VALUE rb_mysql_client_new(int argc, VALUE * argv, VALUE klass) {
}
}
/*
* FIXME: mysql_init may initialize the embedded server and read
* /etc/services off disk (always blocking), so in those unlikely
* cases we should make this release the GVL
*/
if (!mysql_init(args.mysql)) {
if (rb_thread_blocking_region(nogvl_init, &args, RUBY_UBF_IO, 0) == Qfalse) {
// TODO: warning - not enough memory?
rb_raise(cMysql2Error, "%s", mysql_error(args.mysql));
return Qnil;
@ -167,6 +172,8 @@ static VALUE rb_mysql_client_new(int argc, VALUE * argv, VALUE klass) {
return Qnil;
}
client->client = args.mysql;
rb_obj_call_init(obj, argc, argv);
return obj;
}
@ -175,15 +182,43 @@ static VALUE rb_mysql_client_init(RB_MYSQL_UNUSED int argc, RB_MYSQL_UNUSED VALU
return self;
}
static void rb_mysql_client_free(void * client) {
MYSQL * c = client;
if (c) {
static void rb_mysql_client_free(void * ptr) {
mysql2_client_wrapper * client = ptr;
if (client->client) {
/*
* FIXME: this may send a "QUIT" message to the server and thus block
* on the socket write)
* this may send a "QUIT" message to the server and thus block
* on the socket write, users are encouraged to close this manually
* to avoid this behavior
*/
mysql_close(client);
mysql_close(client->client);
}
xfree(ptr);
}
static VALUE nogvl_close(void * ptr) {
mysql_close((MYSQL *)ptr);
return Qnil;
}
/*
* Immediately disconnect from the server, normally the garbage collector
* will disconnect automatically when a connection is no longer needed.
* Explicitly closing this can free up server resources sooner and is
* also 100% safe when faced with signals or running multithreaded
*/
static VALUE rb_mysql_client_close(VALUE self) {
mysql2_client_wrapper *client;
Data_Get_Struct(self, mysql2_client_wrapper, client);
if (client->client) {
rb_thread_blocking_region(nogvl_close, client->client, RUBY_UBF_IO, 0);
client->client = NULL;
} else {
rb_raise(cMysql2Error, "already closed MySQL connection");
}
return Qnil;
}
/*
@ -607,6 +642,7 @@ void Init_mysql2_ext() {
VALUE cMysql2Client = rb_define_class_under(mMysql2, "Client", rb_cObject);
rb_define_singleton_method(cMysql2Client, "new", rb_mysql_client_new, -1);
rb_define_method(cMysql2Client, "initialize", rb_mysql_client_init, -1);
rb_define_method(cMysql2Client, "close", rb_mysql_client_close, 0);
rb_define_method(cMysql2Client, "query", rb_mysql_client_query, -1);
rb_define_method(cMysql2Client, "escape", rb_mysql_client_escape, 1);
rb_define_method(cMysql2Client, "info", rb_mysql_client_info, 0);

View File

@ -30,7 +30,10 @@ static ID intern_new, intern_local;
static VALUE cMysql2Error;
/* Mysql2::Client */
#define GetMysql2Client(obj, sval) (sval = (MYSQL*)DATA_PTR(obj));
typedef struct {
MYSQL * client;
} mysql2_client_wrapper;
#define GetMysql2Client(obj, sval) (sval = ((mysql2_client_wrapper*)(DATA_PTR(obj)))->client);
static ID sym_socket, sym_host, sym_port, sym_username, sym_password,
sym_database, sym_reconnect, sym_connect_timeout, sym_id, sym_version,
sym_sslkey, sym_sslcert, sym_sslca, sym_sslcapath, sym_sslcipher,

View File

@ -34,6 +34,21 @@ describe Mysql2::Client do
results[1]['Value'].class.should eql(String)
end
it "should respond to #close" do
@client.should respond_to :close
end
it "should be able to close properly" do
@client.close.should be_nil
end
it "should raise an exception when closed twice" do
@client.close.should be_nil
lambda {
@client.close
}.should raise_error(Mysql2::Error)
end
it "should respond to #query" do
@client.should respond_to :query
end