You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Ruby扩展开发:如何在C/Rust侧保存引用避免GC回收?

问题:Ruby扩展缓存对象的GC回收问题

我正在开发一个客户端缓存库,使用HashMap缓存对象,以下是相关代码示例:

Rust 实现代码

fn insert(key: RString, obj: AnyObject) -> AnyObject {
    let rust_struct = self.get_data_mut(&*STORE_WRAPPER);
    rust_struct
        .hash_map
        .insert(key.unwrap().to_string(), Arc::new(obj));

    NilClass::new().into()
}

pub extern "C" fn Init_ruby_example() {
    Class::new("ObjectStore", None).define(|klass| {
        klass.def("insert", insert);
    });
}

Ruby 使用示例

store = ObjectStore.new
def insert(store)
  store.insert('key1', 'some-val')
  store.insert('key2', Bar.new)
end

查询逻辑

insert(store)
store.get('key1') # 期望返回'some-val'
store.get('key2') # 期望返回Bar对象

当前遇到的问题是:缓存的'some-val'和Bar对象可能因为Ruby侧没有引用而被GC回收。我可以固定值对象避免GC压缩时移动,但不知道如何在Ruby侧保持引用防止回收。rb_gc_register_address和rb_gc_unregister_address看似可行,但后者需要遍历所有注册对象,性能较差,请问该如何解决?


解决方案

1. 利用Ruby GC的标记机制(推荐方案)

Ruby的GC采用标记-清除算法,只要你的ObjectStore实例在Ruby侧有引用,你可以通过扩展的数据类型标记回调,让GC在标记ObjectStore时自动标记缓存中的所有Ruby对象,这样这些对象就不会被回收。

具体实现步骤:

  • 给ObjectStore关联的Rust结构体定义一个dmark回调函数,在函数内部遍历HashMap中的所有AnyObject,调用rb_gc_mark标记它们。
  • 通过rb_data_typed_struct将这个标记逻辑绑定到ObjectStore类上,确保GC能触发标记。

示例代码:

use rb_sys::{self, rb_data_type_t, rb_data_type_funcs_t, rb_gc_mark};
use std::collections::HashMap;
use std::sync::Arc;

#[derive(Default)]
struct ObjectStoreData {
    hash_map: HashMap<String, Arc<AnyObject>>,
}

// 定义数据类型的函数表
static mut OBJECT_STORE_DATA_TYPE: rb_data_type_t = rb_data_type_t {
    wrap_struct_name: b"ObjectStoreData\0".as_ptr() as _,
    function: Some(rb_data_type_funcs_t {
        dmark: Some(object_store_dmark),
        dfree: Some(object_store_dfree),
        dsize: None,
        compact: None,
        reserved: [0; 4],
    }),
    flags: rb_sys::RUBY_TYPED_FREE_IMMEDIATELY,
    reserved: [0; 1],
};

// GC标记回调:遍历缓存对象并标记
unsafe extern "C" fn object_store_dmark(data: *mut std::os::raw::c_void) {
    let store_data = &mut *(data as *mut ObjectStoreData);
    for obj in store_data.hash_map.values() {
        rb_gc_mark(obj.as_rb_value());
    }
}

// 内存释放回调
unsafe extern "C" fn object_store_dfree(data: *mut std::os::raw::c_void) {
    let _store_data = Box::from_raw(data as *mut ObjectStoreData);
}

// 初始化ObjectStore类时绑定数据结构
pub extern "C" fn Init_ruby_example() {
    let klass = Class::new("ObjectStore", None);
    klass.define(|klass| {
        klass.def("initialize", initialize);
        klass.def("insert", insert);
        klass.def("get", get);
    });
}

// 初始化方法:绑定数据结构到Ruby实例
fn initialize(klass: Class) -> AnyObject {
    let data = ObjectStoreData::default();
    DataWrapStruct::new(klass, data, &mut OBJECT_STORE_DATA_TYPE).into()
}

// 修改后的insert方法
fn insert(key: RString, obj: AnyObject, self_obj: AnyObject) -> AnyObject {
    let store_data = self_obj.get_data_mut::<ObjectStoreData>(&mut OBJECT_STORE_DATA_TYPE);
    store_data.hash_map.insert(key.unwrap().to_string(), Arc::new(obj));
    NilClass::new().into()
}

2. 用Ruby实例变量持有引用

在ObjectStore的Ruby实例中维护一个隐藏的实例变量(如@__cached_objects),将所有插入的对象存入这个数组。Ruby的GC会自动跟踪数组内的对象,避免它们被回收。

实现时,在Rust的insert方法中,除了写入HashMap,还要把对象添加到这个实例变量的数组里:

fn insert(key: RString, obj: AnyObject, self_obj: AnyObject) -> AnyObject {
    let store_data = self_obj.get_data_mut::<ObjectStoreData>(&mut OBJECT_STORE_DATA_TYPE);
    store_data.hash_map.insert(key.unwrap().to_string(), Arc::new(obj.clone()));

    // 获取或创建缓存数组
    let cached_arr = match self_obj.instance_variable_get("@__cached_objects") {
        Ok(arr) => arr,
        Err(_) => {
            let arr = Array::new();
            self_obj.instance_variable_set("@__cached_objects", arr.clone()).unwrap();
            arr
        }
    };
    cached_arr.push(obj).unwrap();

    NilClass::new().into()
}

这种方式无需手动处理GC标记,但要注意:如果缓存对象数量大,数组会占用额外内存;删除缓存时需要从数组中移除对应对象,否则会造成内存泄漏。

3. 不推荐使用rb_gc_register_address

rb_gc_register_address需要手动管理对象的注册与注销,注销时遍历所有对象会带来明显的性能开销,且容易因遗漏注销操作导致内存泄漏,因此不建议采用这种方案。


内容的提问来源于stack exchange,提问作者Yang

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 05:26:19