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:
parent
f7a6b49cbd
commit
1d92db8aab
@ -22,6 +22,15 @@
|
|||||||
* - mysql_ssl_set()
|
* - 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)
|
static VALUE nogvl_connect(void *ptr)
|
||||||
{
|
{
|
||||||
struct nogvl_connect_args *args = ptr;
|
struct nogvl_connect_args *args = ptr;
|
||||||
@ -37,6 +46,7 @@ static VALUE nogvl_connect(void *ptr)
|
|||||||
|
|
||||||
/* Mysql2::Client */
|
/* Mysql2::Client */
|
||||||
static VALUE rb_mysql_client_new(int argc, VALUE * argv, VALUE klass) {
|
static VALUE rb_mysql_client_new(int argc, VALUE * argv, VALUE klass) {
|
||||||
|
mysql2_client_wrapper * client;
|
||||||
struct nogvl_connect_args args = {
|
struct nogvl_connect_args args = {
|
||||||
.host = "localhost",
|
.host = "localhost",
|
||||||
.user = NULL,
|
.user = NULL,
|
||||||
@ -57,7 +67,7 @@ static VALUE rb_mysql_client_new(int argc, VALUE * argv, VALUE klass) {
|
|||||||
unsigned int connect_timeout = 0;
|
unsigned int connect_timeout = 0;
|
||||||
my_bool reconnect = 1;
|
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) {
|
if (rb_scan_args(argc, argv, "01", &opts) == 1) {
|
||||||
Check_Type(opts, T_HASH);
|
Check_Type(opts, T_HASH);
|
||||||
@ -128,12 +138,7 @@ static VALUE rb_mysql_client_new(int argc, VALUE * argv, VALUE klass) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
if (rb_thread_blocking_region(nogvl_init, &args, RUBY_UBF_IO, 0) == Qfalse) {
|
||||||
* 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)) {
|
|
||||||
// TODO: warning - not enough memory?
|
// TODO: warning - not enough memory?
|
||||||
rb_raise(cMysql2Error, "%s", mysql_error(args.mysql));
|
rb_raise(cMysql2Error, "%s", mysql_error(args.mysql));
|
||||||
return Qnil;
|
return Qnil;
|
||||||
@ -167,6 +172,8 @@ static VALUE rb_mysql_client_new(int argc, VALUE * argv, VALUE klass) {
|
|||||||
return Qnil;
|
return Qnil;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client->client = args.mysql;
|
||||||
|
|
||||||
rb_obj_call_init(obj, argc, argv);
|
rb_obj_call_init(obj, argc, argv);
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
@ -175,15 +182,43 @@ static VALUE rb_mysql_client_init(RB_MYSQL_UNUSED int argc, RB_MYSQL_UNUSED VALU
|
|||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void rb_mysql_client_free(void * client) {
|
static void rb_mysql_client_free(void * ptr) {
|
||||||
MYSQL * c = client;
|
mysql2_client_wrapper * client = ptr;
|
||||||
if (c) {
|
|
||||||
|
if (client->client) {
|
||||||
/*
|
/*
|
||||||
* FIXME: this may send a "QUIT" message to the server and thus block
|
* this may send a "QUIT" message to the server and thus block
|
||||||
* on the socket write)
|
* 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);
|
VALUE cMysql2Client = rb_define_class_under(mMysql2, "Client", rb_cObject);
|
||||||
rb_define_singleton_method(cMysql2Client, "new", rb_mysql_client_new, -1);
|
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, "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, "query", rb_mysql_client_query, -1);
|
||||||
rb_define_method(cMysql2Client, "escape", rb_mysql_client_escape, 1);
|
rb_define_method(cMysql2Client, "escape", rb_mysql_client_escape, 1);
|
||||||
rb_define_method(cMysql2Client, "info", rb_mysql_client_info, 0);
|
rb_define_method(cMysql2Client, "info", rb_mysql_client_info, 0);
|
||||||
|
@ -30,7 +30,10 @@ static ID intern_new, intern_local;
|
|||||||
static VALUE cMysql2Error;
|
static VALUE cMysql2Error;
|
||||||
|
|
||||||
/* Mysql2::Client */
|
/* 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,
|
static ID sym_socket, sym_host, sym_port, sym_username, sym_password,
|
||||||
sym_database, sym_reconnect, sym_connect_timeout, sym_id, sym_version,
|
sym_database, sym_reconnect, sym_connect_timeout, sym_id, sym_version,
|
||||||
sym_sslkey, sym_sslcert, sym_sslca, sym_sslcapath, sym_sslcipher,
|
sym_sslkey, sym_sslcert, sym_sslca, sym_sslcapath, sym_sslcipher,
|
||||||
|
@ -34,6 +34,21 @@ describe Mysql2::Client do
|
|||||||
results[1]['Value'].class.should eql(String)
|
results[1]['Value'].class.should eql(String)
|
||||||
end
|
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
|
it "should respond to #query" do
|
||||||
@client.should respond_to :query
|
@client.should respond_to :query
|
||||||
end
|
end
|
||||||
|
Loading…
Reference in New Issue
Block a user