static VALUE rb_mysql_client_async_result(VALUE self) { MYSQL_RES * result; GET_CLIENT(self); REQUIRE_OPEN_DB(wrapper); if (rb_thread_blocking_region(nogvl_read_query_result, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) { // an error occurred, mark this connection inactive MARK_CONN_INACTIVE(self); return rb_raise_mysql2_error(wrapper->client); } result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, wrapper->client, RUBY_UBF_IO, 0); // we have our result, mark this connection inactive MARK_CONN_INACTIVE(self); if (result == NULL) { if (mysql_field_count(wrapper->client) != 0) { rb_raise_mysql2_error(wrapper->client); } return Qnil; } VALUE resultObj = rb_mysql_result_to_obj(result); // pass-through query options for result construction later rb_iv_set(resultObj, "@query_options", rb_funcall(rb_iv_get(self, "@query_options"), rb_intern("dup"), 0)); #ifdef HAVE_RUBY_ENCODING_H mysql2_result_wrapper * result_wrapper; GetMysql2Result(resultObj, result_wrapper); result_wrapper->encoding = wrapper->encoding; #endif return resultObj; }
/* call-seq: * client.async_result * * Returns the result for the last async issued query. */ static VALUE rb_mysql_client_async_result(VALUE self) { MYSQL_RES * result; VALUE resultObj; #ifdef HAVE_RUBY_ENCODING_H mysql2_result_wrapper * result_wrapper; #endif GET_CLIENT(self); // if we're not waiting on a result, do nothing if (NIL_P(wrapper->active_thread)) return Qnil; REQUIRE_CONNECTED(wrapper); if (rb_thread_blocking_region(nogvl_read_query_result, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) { // an error occurred, mark this connection inactive MARK_CONN_INACTIVE(self); return rb_raise_mysql2_error(wrapper); } VALUE is_streaming = rb_hash_aref(rb_iv_get(self, "@query_options"), sym_stream); if(is_streaming == Qtrue) { result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_use_result, wrapper, RUBY_UBF_IO, 0); } else { result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, wrapper, RUBY_UBF_IO, 0); } if (result == NULL) { if (mysql_errno(wrapper->client) != 0) { MARK_CONN_INACTIVE(self); rb_raise_mysql2_error(wrapper); } // no data and no error, so query was not a SELECT return Qnil; } resultObj = rb_mysql_result_to_obj(result); // pass-through query options for result construction later rb_iv_set(resultObj, "@query_options", rb_funcall(rb_iv_get(self, "@query_options"), rb_intern("dup"), 0)); #ifdef HAVE_RUBY_ENCODING_H GetMysql2Result(resultObj, result_wrapper); result_wrapper->encoding = wrapper->encoding; #endif return resultObj; }
static VALUE do_send_query(void *args) { struct nogvl_send_query_args *query_args = args; mysql_client_wrapper *wrapper = query_args->wrapper; if (rb_thread_blocking_region(nogvl_send_query, args, RUBY_UBF_IO, 0) == Qfalse) { // an error occurred, we're not active anymore MARK_CONN_INACTIVE(self); return rb_raise_mysql2_error(wrapper); } return Qnil; }
/* call-seq: * client.async_result * * Returns the result for the last async issued query. */ static VALUE rb_mysql_client_async_result(VALUE self) { MYSQL_RES * result; VALUE resultObj; VALUE current, is_streaming; GET_CLIENT(self); /* if we're not waiting on a result, do nothing */ if (NIL_P(wrapper->active_thread)) return Qnil; REQUIRE_CONNECTED(wrapper); if ((VALUE)rb_thread_call_without_gvl(nogvl_read_query_result, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) { /* an error occurred, mark this connection inactive */ MARK_CONN_INACTIVE(self); return rb_raise_mysql2_error(wrapper); } is_streaming = rb_hash_aref(rb_iv_get(self, "@current_query_options"), sym_stream); if (is_streaming == Qtrue) { result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_use_result, wrapper, RUBY_UBF_IO, 0); } else { result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_store_result, wrapper, RUBY_UBF_IO, 0); } if (result == NULL) { if (mysql_errno(wrapper->client) != 0) { MARK_CONN_INACTIVE(self); rb_raise_mysql2_error(wrapper); } /* no data and no error, so query was not a SELECT */ return Qnil; } current = rb_hash_dup(rb_iv_get(self, "@current_query_options")); (void)RB_GC_GUARD(current); Check_Type(current, T_HASH); resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, Qnil); return resultObj; }
static VALUE disconnect_and_raise(VALUE self, VALUE error) { GET_CLIENT(self); MARK_CONN_INACTIVE(self); wrapper->connected = 0; /* Invalidate the MySQL socket to prevent further communication. * The GC will come along later and call mysql_close to free it. */ if (invalidate_fd(wrapper->client->net.fd) == Qfalse) { fprintf(stderr, "[WARN] mysql2 failed to invalidate FD safely, closing unsafely\n"); close(wrapper->client->net.fd); } rb_exc_raise(error); }
static void *nogvl_do_result(void *ptr, char use_result) { mysql_client_wrapper *wrapper = ptr; MYSQL_RES *result; if (use_result) { result = mysql_use_result(wrapper->client); } else { result = mysql_store_result(wrapper->client); } /* once our result is stored off, this connection is ready for another command to be issued */ MARK_CONN_INACTIVE(self); return result; }
static VALUE allocate(VALUE klass) { VALUE obj; mysql_client_wrapper * wrapper; obj = Data_Make_Struct(klass, mysql_client_wrapper, rb_mysql_client_mark, rb_mysql_client_free, wrapper); wrapper->encoding = Qnil; MARK_CONN_INACTIVE(self); wrapper->automatic_close = 1; wrapper->server_version = 0; wrapper->reconnect_enabled = 0; wrapper->connect_timeout = 0; wrapper->connected = 0; /* means that a database connection is open */ wrapper->initialized = 0; /* means that that the wrapper is initialized */ wrapper->refcount = 1; wrapper->client = (MYSQL*)xmalloc(sizeof(MYSQL)); return obj; }
static VALUE finish_and_mark_inactive(void *args) { VALUE self = args; MYSQL_RES *result; GET_CLIENT(self); if (!NIL_P(wrapper->active_thread)) { /* if we got here, the result hasn't been read off the wire yet so lets do that and then throw it away because we have no way of getting it back up to the caller from here */ result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_store_result, wrapper, RUBY_UBF_IO, 0); mysql_free_result(result); MARK_CONN_INACTIVE(self); } return Qnil; }
static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) { struct nogvl_send_query_args args; fd_set fdset; int fd, retval; int async = 0; VALUE opts, defaults, read_timeout; #ifdef HAVE_RUBY_ENCODING_H rb_encoding *conn_enc; #endif struct timeval tv; struct timeval* tvp; long int sec; VALUE result; GET_CLIENT(self); REQUIRE_OPEN_DB(wrapper); args.mysql = wrapper->client; // see if this connection is still waiting on a result from a previous query if (wrapper->active == 0) { // mark this connection active wrapper->active = 1; } else { rb_raise(cMysql2Error, "This connection is still waiting for a result, try again once you have the result"); } defaults = rb_iv_get(self, "@query_options"); if (rb_scan_args(argc, argv, "11", &args.sql, &opts) == 2) { opts = rb_funcall(defaults, intern_merge, 1, opts); rb_iv_set(self, "@query_options", opts); if (rb_hash_aref(opts, sym_async) == Qtrue) { async = 1; } } else { opts = defaults; } Check_Type(args.sql, T_STRING); #ifdef HAVE_RUBY_ENCODING_H conn_enc = rb_to_encoding(wrapper->encoding); // ensure the string is in the encoding the connection is expecting args.sql = rb_str_export_to_enc(args.sql, conn_enc); #endif if (rb_thread_blocking_region(nogvl_send_query, &args, RUBY_UBF_IO, 0) == Qfalse) { // an error occurred, we're not active anymore MARK_CONN_INACTIVE(self); return rb_raise_mysql2_error(wrapper); } read_timeout = rb_iv_get(self, "@read_timeout"); tvp = NULL; if (!NIL_P(read_timeout)) { Check_Type(read_timeout, T_FIXNUM); tvp = &tv; 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 %ld", sec); } tvp->tv_usec = 0; } if (!async) { // the below code is largely from do_mysql // http://github.com/datamapper/do fd = wrapper->client->net.fd; for(;;) { int fd_set_fd = fd; #if defined(_WIN32) && !defined(HAVE_RB_THREAD_BLOCKING_REGION) WSAPROTOCOL_INFO wsa_pi; // dupicate the SOCKET from libmysql int r = WSADuplicateSocket(fd, GetCurrentProcessId(), &wsa_pi); SOCKET s = WSASocket(wsa_pi.iAddressFamily, wsa_pi.iSocketType, wsa_pi.iProtocol, &wsa_pi, 0, 0); // create the CRT fd so ruby can get back to the SOCKET fd_set_fd = _open_osfhandle(s, O_RDWR|O_BINARY); #endif FD_ZERO(&fdset); FD_SET(fd_set_fd, &fdset); retval = rb_thread_select(fd_set_fd + 1, &fdset, NULL, NULL, tvp); #if defined(_WIN32) && !defined(HAVE_RB_THREAD_BLOCKING_REGION) // cleanup the CRT fd _close(fd_set_fd); // cleanup the duplicated SOCKET closesocket(s); #endif 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); } if (retval > 0) { break; } } result = rb_mysql_client_async_result(self); return result; } else { return Qnil; } }
static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) { struct nogvl_send_query_args args; fd_set fdset; int fd, retval; int async = 0; VALUE opts, defaults, read_timeout; GET_CLIENT(self); REQUIRE_OPEN_DB(wrapper); args.mysql = wrapper->client; // see if this connection is still waiting on a result from a previous query if (wrapper->active == 0) { // mark this connection active wrapper->active = 1; } else { rb_raise(cMysql2Error, "This connection is still waiting for a result, try again once you have the result"); } defaults = rb_iv_get(self, "@query_options"); if (rb_scan_args(argc, argv, "11", &args.sql, &opts) == 2) { opts = rb_funcall(defaults, intern_merge, 1, opts); rb_iv_set(self, "@query_options", opts); if (rb_hash_aref(opts, sym_async) == Qtrue) { async = 1; } } else { opts = defaults; } Check_Type(args.sql, T_STRING); #ifdef HAVE_RUBY_ENCODING_H rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding); // ensure the string is in the encoding the connection is expecting args.sql = rb_str_export_to_enc(args.sql, conn_enc); #endif if (rb_thread_blocking_region(nogvl_send_query, &args, RUBY_UBF_IO, 0) == Qfalse) { // an error occurred, we're not active anymore MARK_CONN_INACTIVE(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 fd = wrapper->client->net.fd; for(;;) { FD_ZERO(&fdset); FD_SET(fd, &fdset); 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); } if (retval > 0) { break; } } VALUE result = rb_mysql_client_async_result(self); return result; } else { return Qnil; } }
static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) { #ifndef _WIN32 struct async_query_args async_args; #endif struct nogvl_send_query_args args; int async = 0; VALUE opts, defaults; #ifdef HAVE_RUBY_ENCODING_H rb_encoding *conn_enc; #endif GET_CLIENT(self); REQUIRE_OPEN_DB(wrapper); args.mysql = wrapper->client; // see if this connection is still waiting on a result from a previous query if (wrapper->active == 0) { // mark this connection active wrapper->active = 1; } else { rb_raise(cMysql2Error, "This connection is still waiting for a result, try again once you have the result"); } defaults = rb_iv_get(self, "@query_options"); if (rb_scan_args(argc, argv, "11", &args.sql, &opts) == 2) { opts = rb_funcall(defaults, intern_merge, 1, opts); rb_iv_set(self, "@query_options", opts); if (rb_hash_aref(opts, sym_async) == Qtrue) { async = 1; } } else { opts = defaults; } Check_Type(args.sql, T_STRING); #ifdef HAVE_RUBY_ENCODING_H conn_enc = rb_to_encoding(wrapper->encoding); // ensure the string is in the encoding the connection is expecting args.sql = rb_str_export_to_enc(args.sql, conn_enc); #endif if (rb_thread_blocking_region(nogvl_send_query, &args, RUBY_UBF_IO, 0) == Qfalse) { // an error occurred, we're not active anymore MARK_CONN_INACTIVE(self); return rb_raise_mysql2_error(wrapper); } #ifndef _WIN32 if (!async) { async_args.fd = wrapper->client->net.fd; async_args.self = self; rb_rescue2(do_query, (VALUE)&async_args, disconnect_and_raise, self, rb_eException, (VALUE)0); return rb_mysql_client_async_result(self); } else { return Qnil; } #else // this will just block until the result is ready return rb_ensure(rb_mysql_client_async_result, self, finish_and_mark_inactive, self); #endif }