Skip to content

Commit b024d9b

Browse files
committed
Fix a GC compaction issue with busy_handler
Previous discussion in sparklemotion#458 Storing `VALUE self` as context for `rb_sqlite3_busy_handler` is unsafe because as of Ruby 2.7, the GC compactor may move objects around which can lead to this reference pointing to either another random object or to garbage. Instead we can store the callback reference inside the malloced struct (`sqlite3Ruby`) which can't possibly move, and then inside the handler, get the callback reference from that struct. This however requires to define a mark function for the database object, and while I was at it, I implemented compaction support for it so we don't pin that proc.
1 parent 5361528 commit b024d9b

File tree

2 files changed

+31
-14
lines changed

2 files changed

+31
-14
lines changed

Diff for: ext/sqlite3/database.c

+30-14
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@
1212

1313
VALUE cSqlite3Database;
1414

15+
static void
16+
database_mark(void *ctx)
17+
{
18+
sqlite3RubyPtr c = (sqlite3RubyPtr)ctx;
19+
rb_gc_mark_movable(c->busy_handler);
20+
}
21+
1522
static void
1623
deallocate(void *ctx)
1724
{
@@ -30,16 +37,22 @@ database_memsize(const void *ctx)
3037
return sizeof(*c);
3138
}
3239

40+
static void
41+
database_update_references(void *ctx)
42+
{
43+
sqlite3RubyPtr c = (sqlite3RubyPtr)ctx;
44+
c->busy_handler = rb_gc_location(c->busy_handler);
45+
}
46+
3347
static const rb_data_type_t database_type = {
34-
"SQLite3::Backup",
35-
{
36-
NULL,
37-
deallocate,
38-
database_memsize,
48+
.wrap_struct_name = "SQLite3::Backup",
49+
.function = {
50+
.dmark = database_mark,
51+
.dfree = deallocate,
52+
.dsize = database_memsize,
53+
.dcompact = database_update_references,
3954
},
40-
0,
41-
0,
42-
RUBY_TYPED_WB_PROTECTED, // Not freed immediately because the dfree function do IOs.
55+
.flags = RUBY_TYPED_WB_PROTECTED, // Not freed immediately because the dfree function do IOs.
4356
};
4457

4558
static VALUE
@@ -202,10 +215,11 @@ trace(int argc, VALUE *argv, VALUE self)
202215
}
203216

204217
static int
205-
rb_sqlite3_busy_handler(void *ctx, int count)
218+
rb_sqlite3_busy_handler(void *context, int count)
206219
{
207-
VALUE self = (VALUE)(ctx);
208-
VALUE handle = rb_iv_get(self, "@busy_handler");
220+
sqlite3RubyPtr ctx = (sqlite3RubyPtr)context;
221+
222+
VALUE handle = ctx->busy_handler;
209223
VALUE result = rb_funcall(handle, rb_intern("call"), 1, INT2NUM(count));
210224

211225
if (Qfalse == result) { return 0; }
@@ -240,11 +254,13 @@ busy_handler(int argc, VALUE *argv, VALUE self)
240254
rb_scan_args(argc, argv, "01", &block);
241255

242256
if (NIL_P(block) && rb_block_given_p()) { block = rb_block_proc(); }
243-
244-
rb_iv_set(self, "@busy_handler", block);
257+
ctx->busy_handler = block;
245258

246259
status = sqlite3_busy_handler(
247-
ctx->db, NIL_P(block) ? NULL : rb_sqlite3_busy_handler, (void *)self);
260+
ctx->db,
261+
NIL_P(block) ? NULL : rb_sqlite3_busy_handler,
262+
(void *)ctx
263+
);
248264

249265
CHECK(ctx->db, status);
250266

Diff for: ext/sqlite3/database.h

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
struct _sqlite3Ruby {
77
sqlite3 *db;
8+
VALUE busy_handler;
89
};
910

1011
typedef struct _sqlite3Ruby sqlite3Ruby;

0 commit comments

Comments
 (0)