Ruby 3.3.5升级后C扩展出现GC阶段对象分配错误的解决问询
Ruby 3.3.5 C扩展"object allocation during garbage collection phase"错误解决办法
问题背景
将Ruby应用升级至3.3.5版本后,C扩展在错误处理流程中触发object allocation during garbage collection phase错误。错误发生在rb_protect捕获Ruby代码异常后,调用OT_ERROR_LNG_2_MQ函数处理错误信息时——该函数调用Ruby标准方法full_message创建新对象,而Ruby 3.3.5的GC性能优化改动导致此操作在GC运行阶段被禁止。降级至3.2.5版本后错误消失。
核心代码分析
异常捕获代码
mk_inline VALUE NS(sRescue) ( MK_RT_ARGS MK_MNG const mng, VALUE(*proc)(ANYARGS), VALUE data ) { int state; VALUE ret = rb_protect(proc, data, &state); if (state) { OT_ERROR_LNG_2_MQ(mng); } return ret; }
错误处理触发点
错误由full_message调用及前置的对象分配触发:
... } else if (rb_obj_is_kind_of(ex, rb_eStandardError) == Qtrue) { static VALUE sym_highlight = 0; if (!sym_highlight) sym_highlight = ID2SYM(rb_intern_const("highlight")); VALUE opt = rb_hash_new(); // 此处分配Hash对象 rb_hash_aset(opt, sym_highlight, Qfalse); VALUE msg = rb_funcallv_kw(ex,MK(id_full_message),1,&opt,RB_PASS_KEYWORDS); // full_message内部也会分配对象 MkErrorSetC (err, VAL2STRN(msg), MK(Get_Call_Proc)(0,NULL), -1); rb_set_errinfo(Qnil); } ...
Ruby 3.3.5 GC改动影响
Ruby 3.3.5的GC优化调整了运行逻辑,可能导致错误处理路径更频繁地与GC阶段重叠:
- 减少了major GC的触发频率,但调整了触发条件,可能在异常处理的低优先级路径中恰好遇到GC运行
- 新增了写屏障、可变宽度分配等机制,对对象分配的时机检测更严格,禁止在GC阶段执行对象分配操作
解决方案
1. 预分配复用对象
将错误处理中需要的对象提前在扩展初始化阶段创建并标记为全局变量,避免在错误处理时动态分配:
// 全局静态变量,预初始化 static VALUE sym_highlight; static VALUE full_message_opt; // 扩展初始化函数中完成预分配 void Init_your_extension() { sym_highlight = ID2SYM(rb_intern_const("highlight")); full_message_opt = rb_hash_new(); rb_hash_aset(full_message_opt, sym_highlight, Qfalse); // 标记为全局变量,防止被GC回收 rb_global_variable(&sym_highlight); rb_global_variable(&full_message_opt); } // 修改错误处理代码 } else if (rb_obj_is_kind_of(ex, rb_eStandardError) == Qtrue) { VALUE msg = rb_funcallv_kw(ex, MK(id_full_message), 1, &full_message_opt, RB_PASS_KEYWORDS); MkErrorSetC(err, VAL2STRN(msg), MK(Get_Call_Proc)(0,NULL), -1); rb_set_errinfo(Qnil); }
2. 临时禁用GC
在错误处理前后禁用/启用GC,确保对象分配操作不在GC阶段执行:
// 封装错误处理逻辑到单独函数,用rb_protect保护 static VALUE do_error_processing(VALUE mng) { VALUE ex = rb_errinfo(); // 原错误处理逻辑 if (rb_obj_is_kind_of(ex, rb_eStandardError) == Qtrue) { static VALUE sym_highlight = 0; if (!sym_highlight) sym_highlight = ID2SYM(rb_intern_const("highlight")); VALUE opt = rb_hash_new(); rb_hash_aset(opt, sym_highlight, Qfalse); VALUE msg = rb_funcallv_kw(ex,MK(id_full_message),1,&opt,RB_PASS_KEYWORDS); MkErrorSetC (err, VAL2STRN(msg), MK(Get_Call_Proc)(0,NULL), -1); rb_set_errinfo(Qnil); } return Qnil; } // 修改OT_ERROR_LNG_2_MQ函数 void OT_ERROR_LNG_2_MQ(MK_MNG const mng) { rb_gc_disable(); int state; // 用rb_protect确保即使处理出错也能恢复GC rb_protect(do_error_processing, (VALUE)mng, &state); rb_gc_enable(); if (state) { // 处理错误处理过程中出现的异常 rb_set_errinfo(Qnil); fprintf(stderr, "Failed to process error message\n"); } }
3. 简化错误信息获取(备选)
如果不需要full_message的完整格式化信息,可改用更轻量的to_s或message方法,减少对象分配:
// 替换full_message调用 VALUE msg = rb_funcall(ex, rb_intern("to_s"), 0);
内容的提问来源于stack exchange,提问作者Andreas Otto
相关产品推荐
相关产品推荐

