Skip to content

Commit eed9c84

Browse files
committed
fix: GC problem
1 parent d7ac9a9 commit eed9c84

File tree

2 files changed

+36
-24
lines changed

2 files changed

+36
-24
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [Unreleased]
9+
10+
### Fixed
11+
12+
- Potential problems with garbage collection of the `result` array and other `VALUE`s
13+
814
## [0.2.0] - 2024-12-27
915

1016
### Fixed

ext/json_scanner/json_scanner.c

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,15 @@ typedef struct
8080
int current_path_len;
8181
int max_path_len;
8282
// Easier to use a Ruby array for result than convert later
83-
VALUE points_list;
83+
volatile VALUE points_list;
8484
// by depth
8585
size_t *starts;
86-
// VALUE rb_err;
86+
// volatile VALUE rb_err;
8787
yajl_handle handle;
8888
} scan_ctx;
8989

9090
// FIXME: This will cause memory leak if ruby_xmalloc raises
91-
scan_ctx *scan_ctx_init(VALUE path_ary, int with_path, int symbolize_path_keys)
91+
scan_ctx *scan_ctx_init(VALUE path_ary)
9292
{
9393
int path_ary_len;
9494
scan_ctx *ctx;
@@ -98,6 +98,7 @@ scan_ctx *scan_ctx_init(VALUE path_ary, int with_path, int symbolize_path_keys)
9898
path_ary_len = rb_long2int(rb_array_len(path_ary));
9999
// Check types early before any allocations, so exception is ok
100100
// TODO: Fix this, just handle errors
101+
// I'm not sure if it's possible that another Ruby thread changes path_ary items between these two loops
101102
for (int i = 0; i < path_ary_len; i++)
102103
{
103104
int path_len;
@@ -145,8 +146,6 @@ scan_ctx *scan_ctx_init(VALUE path_ary, int with_path, int symbolize_path_keys)
145146

146147
ctx = ruby_xmalloc(sizeof(scan_ctx));
147148

148-
ctx->with_path = with_path;
149-
ctx->symbolize_path_keys = symbolize_path_keys;
150149
ctx->max_path_len = 0;
151150

152151
paths = ruby_xmalloc(sizeof(paths_t) * path_ary_len);
@@ -216,18 +215,19 @@ scan_ctx *scan_ctx_init(VALUE path_ary, int with_path, int symbolize_path_keys)
216215
ctx->paths_len = path_ary_len;
217216
ctx->current_path = ruby_xmalloc2(sizeof(path_elem_t), ctx->max_path_len);
218217

219-
ctx->current_path_len = 0;
220-
ctx->points_list = rb_ary_new_capa(path_ary_len);
221-
for (int i = 0; i < path_ary_len; i++)
222-
{
223-
rb_ary_push(ctx->points_list, rb_ary_new());
224-
}
225-
226218
ctx->starts = ruby_xmalloc2(sizeof(size_t), ctx->max_path_len + 1);
219+
return ctx;
220+
}
221+
222+
// resets temporary values in the config
223+
void scan_ctx_reset(scan_ctx *ctx, volatile VALUE points_list, int with_path, int symbolize_path_keys)
224+
{
225+
ctx->current_path_len = 0;
227226
// ctx->rb_err = Qnil;
228227
ctx->handle = NULL;
229-
230-
return ctx;
228+
ctx->points_list = points_list;
229+
ctx->with_path = with_path;
230+
ctx->symbolize_path_keys = symbolize_path_keys;
231231
}
232232

233233
void scan_ctx_free(scan_ctx *ctx)
@@ -266,10 +266,10 @@ typedef enum
266266
} value_type;
267267

268268
// noexcept
269-
VALUE create_point(scan_ctx *sctx, value_type type, size_t length, size_t curr_pos)
269+
volatile VALUE create_point(scan_ctx *sctx, value_type type, size_t length, size_t curr_pos)
270270
{
271271
VALUE values[3];
272-
VALUE point = rb_ary_new_capa(3);
272+
volatile VALUE point = rb_ary_new_capa(3);
273273
// noexcept
274274
values[1] = RB_ULONG2NUM(curr_pos);
275275
switch (type)
@@ -306,12 +306,12 @@ VALUE create_point(scan_ctx *sctx, value_type type, size_t length, size_t curr_p
306306
}
307307

308308
// noexcept
309-
VALUE create_path(scan_ctx *sctx)
309+
volatile VALUE create_path(scan_ctx *sctx)
310310
{
311-
VALUE path = rb_ary_new_capa(sctx->current_path_len);
311+
volatile VALUE path = rb_ary_new_capa(sctx->current_path_len);
312312
for (int i = 0; i < sctx->current_path_len; i++)
313313
{
314-
VALUE entry;
314+
volatile VALUE entry;
315315
switch (sctx->current_path[i].type)
316316
{
317317
case PATH_KEY:
@@ -337,7 +337,7 @@ void save_point(scan_ctx *sctx, value_type type, size_t length)
337337
// TODO: Abort parsing if all paths are matched and no more mathces are possible: only trivial key/index matchers at the current level
338338
// TODO: Don't re-compare already matched prefixes; hard to invalidate, though
339339
// TODO: Might fail in case of no memory
340-
VALUE point = Qundef;
340+
volatile VALUE point = Qundef;
341341
int match;
342342
for (int i = 0; i < sctx->paths_len; i++)
343343
{
@@ -534,9 +534,9 @@ VALUE scan(int argc, VALUE *argv, VALUE self)
534534
yajl_handle handle;
535535
yajl_status stat;
536536
scan_ctx *ctx;
537-
VALUE err = Qnil, result;
537+
volatile VALUE err = Qnil, result;
538538
// Turned out callbacks can't raise exceptions
539-
// VALUE callback_err;
539+
// volatile VALUE callback_err;
540540
#if RUBY_API_VERSION_MAJOR > 2 || (RUBY_API_VERSION_MAJOR == 2 && RUBY_API_VERSION_MINOR >= 7)
541541
rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS, argc, argv, "21:", &json_str, &path_ary, &with_path_flag, &kwargs);
542542
#else
@@ -561,7 +561,14 @@ VALUE scan(int argc, VALUE *argv, VALUE self)
561561
#else
562562
json_text_len = RSTRING_LEN(json_str);
563563
#endif
564-
ctx = scan_ctx_init(path_ary, with_path, symbolize_path_keys);
564+
ctx = scan_ctx_init(path_ary);
565+
// Need to keep a ref to result array on the stack to prevent it from being GC-ed
566+
result = rb_ary_new_capa(ctx->paths_len);
567+
for (int i = 0; i < ctx->paths_len; i++)
568+
{
569+
rb_ary_push(result, rb_ary_new());
570+
}
571+
scan_ctx_reset(ctx, result, with_path, symbolize_path_keys);
565572

566573
handle = yajl_alloc(&scan_callbacks, NULL, (void *)ctx);
567574
if (kwargs != Qnil) // it's safe to read kwargs_values only if rb_get_kwargs was called
@@ -589,7 +596,6 @@ VALUE scan(int argc, VALUE *argv, VALUE self)
589596
yajl_free_error(handle, (unsigned char *)str);
590597
}
591598
// callback_err = ctx->rb_err;
592-
result = ctx->points_list;
593599
scan_ctx_free(ctx);
594600
yajl_free(handle);
595601
if (err != Qnil)

0 commit comments

Comments
 (0)