Skip to content

Commit ef5d61d

Browse files
committed
Support prepared_statement.close
Useful when caching prepared statements. Evicted statements can release server resources immediately rather than waiting for Ruby GC.
1 parent de7c375 commit ef5d61d

File tree

3 files changed

+39
-1
lines changed

3 files changed

+39
-1
lines changed

ext/mysql2/statement.c

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@ static void rb_mysql_stmt_mark(void * ptr) {
1717
rb_gc_mark(stmt_wrapper->client);
1818
}
1919

20+
static void *nogvl_stmt_close(void *ptr) {
21+
mysql_stmt_wrapper *stmt_wrapper = (mysql_stmt_wrapper *)ptr;
22+
if (stmt_wrapper->closed == 0) {
23+
mysql_stmt_close(stmt_wrapper->stmt);
24+
stmt_wrapper->closed = 1;
25+
}
26+
return NULL;
27+
}
28+
2029
static void rb_mysql_stmt_free(void * ptr) {
2130
mysql_stmt_wrapper* stmt_wrapper = (mysql_stmt_wrapper *)ptr;
2231
decr_mysql2_stmt(stmt_wrapper);
@@ -26,7 +35,7 @@ void decr_mysql2_stmt(mysql_stmt_wrapper *stmt_wrapper) {
2635
stmt_wrapper->refcount--;
2736

2837
if (stmt_wrapper->refcount == 0) {
29-
mysql_stmt_close(stmt_wrapper->stmt);
38+
nogvl_stmt_close(stmt_wrapper);
3039
xfree(stmt_wrapper);
3140
}
3241
}
@@ -94,6 +103,7 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
94103
rb_stmt = Data_Make_Struct(cMysql2Statement, mysql_stmt_wrapper, rb_mysql_stmt_mark, rb_mysql_stmt_free, stmt_wrapper);
95104
{
96105
stmt_wrapper->client = rb_client;
106+
stmt_wrapper->closed = 0;
97107
stmt_wrapper->refcount = 1;
98108
stmt_wrapper->stmt = NULL;
99109
}
@@ -442,6 +452,19 @@ static VALUE rb_mysql_stmt_affected_rows(VALUE self) {
442452
return ULL2NUM(affected);
443453
}
444454

455+
/* call-seq:
456+
* stmt.close
457+
*
458+
* Explicitly closing this will free up server resources immediately rather
459+
* than waiting for the garbage collector. Useful if you're managing your
460+
* own prepared statement cache.
461+
*/
462+
static VALUE rb_mysql_stmt_close(VALUE self) {
463+
GET_STATEMENT(self);
464+
rb_thread_call_without_gvl(nogvl_stmt_close, stmt_wrapper, RUBY_UBF_IO, 0);
465+
return Qnil;
466+
}
467+
445468
void init_mysql2_statement() {
446469
cMysql2Statement = rb_define_class_under(mMysql2, "Statement", rb_cObject);
447470

@@ -451,6 +474,7 @@ void init_mysql2_statement() {
451474
rb_define_method(cMysql2Statement, "fields", fields, 0);
452475
rb_define_method(cMysql2Statement, "last_id", rb_mysql_stmt_last_id, 0);
453476
rb_define_method(cMysql2Statement, "affected_rows", rb_mysql_stmt_affected_rows, 0);
477+
rb_define_method(cMysql2Statement, "close", rb_mysql_stmt_close, 0);
454478

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

ext/mysql2/statement.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ extern VALUE cMysql2Statement;
66
typedef struct {
77
VALUE client;
88
MYSQL_STMT *stmt;
9+
int closed;
910
int refcount;
1011
} mysql_stmt_wrapper;
1112

spec/mysql2/statement_spec.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,4 +664,17 @@
664664
expect(stmt.affected_rows).to eq 1
665665
end
666666
end
667+
668+
context 'close' do
669+
it 'should free server resources' do
670+
stmt = @client.prepare 'SELECT 1'
671+
expect(stmt.close).to eq nil
672+
end
673+
674+
it 'should raise an error on subsequent execution' do
675+
stmt = @client.prepare 'SELECT 1'
676+
stmt.close
677+
expect { stmt.execute }.to raise_error(Mysql2::Error)
678+
end
679+
end
667680
end

0 commit comments

Comments
 (0)