本篇聚焦 Ruby bridge 與 C Core 之間的生命週期協作:memory pool
如何與 Ruby GC 配合,extern_ctx
如何穩定保存外部狀態,以及如何避免資源洩漏。筆者將以 Mongory 的實作為藍本,整理設計原則、落地策略與常見陷阱。
reset
。mongory_value.origin
指向外部(Ruby VALUE)原始物件,供 recover 與 GC 標記。context
),會被安全地橋接至 C,再傳回到自定義 custom matcher 做使用。extern_ctx
與 key 緩存(string_map/symbol_map),並掛入 mark_list
。reset
;reset/free
。Ruby 端透過自有結構包一層 C 的 pool,生命週期由 wrapper 控制:
static rb_mongory_memory_pool_t *rb_mongory_memory_pool_new() {
rb_mongory_memory_pool_t *pool = malloc(sizeof(rb_mongory_memory_pool_t));
mongory_memory_pool *base = mongory_memory_pool_new();
memcpy(&pool->base, base, sizeof(mongory_memory_pool));
free(base);
return pool;
}
釋放時,三種 pool 依序釋放,避免殘留:
static void rb_mongory_matcher_free(void *ptr) {
rb_mongory_matcher_t *wrapper = (rb_mongory_matcher_t *)ptr;
mongory_memory_pool *pool = wrapper->pool;
mongory_memory_pool *scratch_pool = wrapper->scratch_pool;
mongory_memory_pool *trace_pool = wrapper->trace_pool;
pool->free(pool);
scratch_pool->free(scratch_pool);
if (trace_pool) {
trace_pool->free(trace_pool);
}
xfree(wrapper);
}
核心原則:C 側永不直接管理 Ruby 物件的生命週期;僅保存其 VALUE 至 origin
,並確保 Ruby GC 能標記到。
static bool gc_mark_array_cb(mongory_value *value, void *acc) {
(void)acc;
if (value && value->origin) rb_gc_mark((VALUE)value->origin);
return true;
}
static void rb_mongory_matcher_mark(void *ptr) {
rb_mongory_matcher_t *self = (rb_mongory_matcher_t *)ptr;
if (!self) return;
self->mark_list->each(self->mark_list, NULL, gc_mark_array_cb);
}
落地策略:
mongory_value
包裝,並掛入 mark_list
。origin
指回 Ruby VALUE,協助 recover 與 GC;禁止在 C 側複製 Ruby 物件內容。extern_ctx
是由 Ruby 使用者傳入的上下文對象,需伴隨 matcher 的整體生命週期:
static void rb_mongory_matcher_parse_argv(rb_mongory_matcher_t *self, int argc, VALUE *argv) {
VALUE condition, kw_hash;
rb_scan_args(argc, argv, "1:", &condition, &kw_hash);
const ID ctx_id[1] = { rb_intern("context") };
VALUE kw_vals[1] = { Qundef };
if (kw_hash != Qnil) {
rb_get_kwargs(kw_hash, ctx_id, 1, 0, kw_vals);
}
if (kw_vals[0] != Qundef) {
self->ctx = kw_vals[0];
} else {
self->ctx = rb_funcall(cMongoryMatcherContext, rb_intern("new"), 0);
}
VALUE converted_condition = rb_funcall(inMongoryConditionConverter, rb_intern("convert"), 1, condition);
self->condition = rb_to_mongory_value_deep(self->pool, converted_condition);
self->mark_list->push(self->mark_list, self->condition);
mongory_value *store_ctx = mongory_value_wrap_u(self->pool, (void *)self->ctx);
store_ctx->origin = (void *)self->ctx;
self->mark_list->push(self->mark_list, store_ctx);
}
重點:
self->ctx
以 VALUE 保存於 wrapper,同步再以 mongory_value
包裝存入 matcher pool,並加入 mark_list
。match?
:data 走 shallow,分配於 scratch pool;執行完立即 reset
,確保不殘留。trace
:建立臨時 trace pool,印出後釋放;或 enable_trace
後重複利用但每次 reset
。static VALUE rb_mongory_matcher_match(VALUE self, VALUE data) {
rb_mongory_matcher_t *self_wrapper;
TypedData_Get_Struct(self, rb_mongory_matcher_t, &rb_mongory_matcher_type, self_wrapper);
mongory_matcher *matcher = self_wrapper->matcher;
mongory_memory_pool *scratch_pool = self_wrapper->scratch_pool;
mongory_memory_pool *trace_pool = self_wrapper->trace_pool;
mongory_value *data_value = rb_to_mongory_value_shallow(scratch_pool, data);
if (rb_mongory_error_handling(scratch_pool, "Match failed")) {
return Qnil;
}
bool result = mongory_matcher_match(matcher, data_value);
if (trace_pool) {
mongory_matcher_print_trace(matcher);
trace_pool->reset(trace_pool);
mongory_matcher_enable_trace(matcher, trace_pool);
}
scratch_pool->reset(scratch_pool);
return result ? Qtrue : Qfalse;
}
trace
記錄,以在 free
時一併處理。origin
回填並 push 至 mark_list
。忘記 push 到 mark_list
origin
懸掛;mongory_value
包裝並 push。在 reset
後仍持有 shallow 值
在 converter 做行為邏輯
忽略 trace_pool
的 reset/free
reset
,不再使用則 disable_trace
+ 釋放。mongory_value
,設置 origin
,push 至 mark_list
。reset
。reset/free
。# 1) 建立 CMatcher:condition deep;extern_ctx 保存,並掛入 mark_list
matcher = Mongory::CMatcher.new({ age: { "$gt" => 30 } }, context: { tenant_id: "t1" })
# 2) 匹配:data shallow;scratch_pool 事後 reset
data = { name: "Alice", age: 31 }
matched = matcher.match?(data)
# 3) 觀測:啟用 trace,逐次輸出並重置 trace_pool
matcher.enable_trace
matcher.trace(data)
matcher.print_trace
GC × memory pool 的協同關鍵有三:以 pool 管控記憶體、以 origin
/mark_list
協同 Ruby GC、以 extern_ctx
穩定承載外部語境
把責任邊界釐清(資料給 converter、行為給 adapter),搭配明確的池化規則與檢查清單,就能在高效與安全間取得平衡。