diff --git a/ext/mysql2/statement.c b/ext/mysql2/statement.c index c8eef3da9..0aa36741c 100644 --- a/ext/mysql2/statement.c +++ b/ext/mysql2/statement.c @@ -17,6 +17,15 @@ static void rb_mysql_stmt_mark(void * ptr) { rb_gc_mark(stmt_wrapper->client); } +static void *nogvl_stmt_close(void *ptr) { + mysql_stmt_wrapper *stmt_wrapper = (mysql_stmt_wrapper *)ptr; + if (stmt_wrapper->closed == 0) { + mysql_stmt_close(stmt_wrapper->stmt); + stmt_wrapper->closed = 1; + } + return NULL; +} + static void rb_mysql_stmt_free(void * ptr) { mysql_stmt_wrapper* stmt_wrapper = (mysql_stmt_wrapper *)ptr; decr_mysql2_stmt(stmt_wrapper); @@ -26,7 +35,7 @@ void decr_mysql2_stmt(mysql_stmt_wrapper *stmt_wrapper) { stmt_wrapper->refcount--; if (stmt_wrapper->refcount == 0) { - mysql_stmt_close(stmt_wrapper->stmt); + nogvl_stmt_close(stmt_wrapper); xfree(stmt_wrapper); } } @@ -94,6 +103,7 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) { rb_stmt = Data_Make_Struct(cMysql2Statement, mysql_stmt_wrapper, rb_mysql_stmt_mark, rb_mysql_stmt_free, stmt_wrapper); { stmt_wrapper->client = rb_client; + stmt_wrapper->closed = 0; stmt_wrapper->refcount = 1; stmt_wrapper->stmt = NULL; } @@ -442,6 +452,19 @@ static VALUE rb_mysql_stmt_affected_rows(VALUE self) { return ULL2NUM(affected); } +/* call-seq: + * stmt.close + * + * Explicitly closing this will free up server resources immediately rather + * than waiting for the garbage collector. Useful if you're managing your + * own prepared statement cache. + */ +static VALUE rb_mysql_stmt_close(VALUE self) { + GET_STATEMENT(self); + rb_thread_call_without_gvl(nogvl_stmt_close, stmt_wrapper, RUBY_UBF_IO, 0); + return Qnil; +} + void init_mysql2_statement() { cMysql2Statement = rb_define_class_under(mMysql2, "Statement", rb_cObject); @@ -451,6 +474,7 @@ void init_mysql2_statement() { rb_define_method(cMysql2Statement, "fields", fields, 0); rb_define_method(cMysql2Statement, "last_id", rb_mysql_stmt_last_id, 0); rb_define_method(cMysql2Statement, "affected_rows", rb_mysql_stmt_affected_rows, 0); + rb_define_method(cMysql2Statement, "close", rb_mysql_stmt_close, 0); sym_stream = ID2SYM(rb_intern("stream")); diff --git a/ext/mysql2/statement.h b/ext/mysql2/statement.h index 0c1d54c55..65bbbae22 100644 --- a/ext/mysql2/statement.h +++ b/ext/mysql2/statement.h @@ -6,6 +6,7 @@ extern VALUE cMysql2Statement; typedef struct { VALUE client; MYSQL_STMT *stmt; + int closed; int refcount; } mysql_stmt_wrapper; diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index bbb493fce..47ac6e9ca 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -664,4 +664,17 @@ expect(stmt.affected_rows).to eq 1 end end + + context 'close' do + it 'should free server resources' do + stmt = @client.prepare 'SELECT 1' + expect(stmt.close).to eq nil + end + + it 'should raise an error on subsequent execution' do + stmt = @client.prepare 'SELECT 1' + stmt.close + expect { stmt.execute }.to raise_error(Mysql2::Error) + end + end end