diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index b5be870..c88dcaf 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -264,7 +264,7 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) { fd_set fdset; int fd, retval; int async = 0; - VALUE opts, defaults; + VALUE opts, defaults, read_timeout; GET_CLIENT(self); REQUIRE_OPEN_DB(wrapper); @@ -303,6 +303,23 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) { return rb_raise_mysql2_error(wrapper->client); } + read_timeout = rb_iv_get(self, "@read_timeout"); + struct timeval tv; + struct timeval* tvp = NULL; + if (!NIL_P(read_timeout)) { + Check_Type(read_timeout, T_FIXNUM); + tvp = &tv; + long int sec = FIX2INT(read_timeout); + // TODO: support partial seconds? + // also, this check is here for sanity, we also check up in Ruby + if (sec >= 0) { + tvp->tv_sec = sec; + } else { + rb_raise(cMysql2Error, "read_timeout must be a positive integer, you passed %d", sec); + } + tvp->tv_usec = 0; + } + if (!async) { // the below code is largely from do_mysql // http://github.com/datamapper/do @@ -311,7 +328,11 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) { FD_ZERO(&fdset); FD_SET(fd, &fdset); - retval = rb_thread_select(fd + 1, &fdset, NULL, NULL, NULL); + retval = rb_thread_select(fd + 1, &fdset, NULL, NULL, tvp); + + if (retval == 0) { + rb_raise(cMysql2Error, "Timeout waiting for a response from the last query. (waited %d seconds)", FIX2INT(read_timeout)); + } if (retval < 0) { rb_sys_fail(0); diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index ee1a36b..0cc99de 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -24,6 +24,11 @@ module Mysql2 # force the encoding to utf8 self.charset_name = opts[:encoding] || 'utf8' + @read_timeout = opts[:read_timeout] + if @read_timeout and @read_timeout < 0 + raise Mysql2::Error, "read_timeout must be a positive integer, you passed #{@read_timeout}" + end + ssl_set(*opts.values_at(:sslkey, :sslcert, :sslca, :sslcapath, :sslciper)) user = opts[:username] diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 748b423..565b4ee 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -85,6 +85,12 @@ describe Mysql2::Client do @client.should respond_to(:query) end + it "should expect read_timeout to be a positive integer" do + lambda { + Mysql2::Client.new(:read_timeout => -1) + }.should raise_error(Mysql2::Error) + end + context "#query" do it "should only accept strings as the query parameter" do lambda { @@ -124,6 +130,13 @@ describe Mysql2::Client do }.should raise_error(Mysql2::Error) end + it "should timeout if we wait longer than :read_timeout" do + client = Mysql2::Client.new(:read_timeout => 1) + lambda { + client.query("SELECT sleep(2)") + }.should raise_error(Mysql2::Error) + end + # XXX this test is not deterministic (because Unix signal handling is not) # and may fail on a loaded system if RUBY_PLATFORM !~ /mingw|mswin/