Skip to content

Commit

Permalink
Support prepared_statement.close
Browse files Browse the repository at this point in the history
Useful when caching prepared statements. Evicted statements can release
server resources immediately rather than waiting for Ruby GC.
  • Loading branch information
jeremy committed Sep 9, 2015
1 parent a0a9e4e commit e254cd6
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 1 deletion.
26 changes: 25 additions & 1 deletion ext/mysql2/statement.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
}
}
Expand Down Expand Up @@ -109,6 +118,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;
}
Expand Down Expand Up @@ -430,13 +440,27 @@ static VALUE fields(VALUE self) {
return field_list;
}

/* 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);

rb_define_method(cMysql2Statement, "param_count", param_count, 0);
rb_define_method(cMysql2Statement, "field_count", field_count, 0);
rb_define_method(cMysql2Statement, "execute", execute, -1);
rb_define_method(cMysql2Statement, "fields", fields, 0);
rb_define_method(cMysql2Statement, "close", rb_mysql_stmt_close, 0);

sym_stream = ID2SYM(rb_intern("stream"));

Expand Down
1 change: 1 addition & 0 deletions ext/mysql2/statement.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ extern VALUE cMysql2Statement;
typedef struct {
VALUE client;
MYSQL_STMT *stmt;
int closed;
int refcount;
} mysql_stmt_wrapper;

Expand Down
13 changes: 13 additions & 0 deletions spec/mysql2/statement_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -595,4 +595,17 @@
end
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

0 comments on commit e254cd6

Please sign in to comment.