使用Ruby C Extension进行垃圾收集

Ove*_*ked 5 ruby garbage-collection ferret

我正在通过Ferret(Lucene的Ruby端口)代码来解决一个bug.Ferret代码主要是Ruby的C扩展.我遇到了垃圾收集器的一些问题.我设法修复它,但我不完全理解我的修复=)我希望有更深入的Ruby和C扩展知识的人(这是我在Ruby的第3天)可以详细说明.谢谢.

情况如下:

在Ferret C代码的某些地方,我将返回一个"Token"到Ruby的土地.代码看起来像

static VALUE get_token (...)
{
  ...
  RToken *token = ALLOC(RToken);
  token->text = rb_str_new2("some text");
  return Data_Wrap_Struct(..., &frt_token_mark, &frt_token_free, token);
}
Run Code Online (Sandbox Code Playgroud)

frt_token_mark调用rb_gc_mark(token-> text)和frt_token_free只用free(令牌)释放令牌

在Ruby中,此代码与以下内容相关:

token = @ input.next

基本上,@ input设置为某个对象,在其上调用下一个方法会触发get_token C调用,该调用返回一个令牌对象.

在Ruby领域,我做了类似w = token.text.scan('\ w +')的事情.

当我在一个1循环内运行此代码(以隔离我的问题)时,在某些时候(大约当我的ruby进程mem足迹达到256MB,可能是一些GC阈值)时,Ruby会因为错误而死

扫描方法调用终止对象

或者只是核心转储.我的猜测是token.text被垃圾收集了.

我不太了解Ruby C扩展,知道Data_Wrap_Struct返回对象会发生什么.在我看来,Ruby land,token =中的赋值应该创建对它的引用.

我的"解决方法"/"修复"是在@input引用的对象中创建一个Ruby实例变量,并将令牌文本存储在那里,以获得对它的额外引用.所以C代码看起来像

RToken *token = ALLOC(RToken);
token->text = rb_str_new2(tk->text);
/* added code: prevent garbage collection */
rb_ivar_set(input, id_curtoken, token->text);
return Data_Wrap_Struct(cToken, &frt_token_mark, &frt_token_free, token);
Run Code Online (Sandbox Code Playgroud)

所以现在我在输入实例变量中创建了一个"curtoken",并在那里保存了一个文本的副本...我已经注意在@input的类的免费回调中删除/删除这个引用.

使用此代码,它的工作原理是我不再获得终止对象错误.

修复似乎对我有意义 - 它为token.text字符串保留了额外的引用,因此在下次调用@input.next之前不会删除token.text的实例(此时a不同的token.text替换curtoken中的旧值.

我的问题是:为什么以前不起作用?不应该Data_Wrap_Structure返回一个对象,当在Ruby域中分配时,该对象具有有效的引用而不被Ruby删除?

谢谢.

Pau*_*nan 3

当Ruby垃圾收集器被调用时,它有一个标记阶段和一个清除阶段。标记阶段通过标记来标记系统中的所有对象:

  1. ruby 堆栈帧引用的所有对象(例如局部变量)
  2. 所有全局可访问的对象(例如,由常量或全局变量引用)及其子对象/引用对象,以及
  3. 堆栈上的引用所引用的所有对象,以及这些对象的子对象/引用对象。

以及一些对此讨论不重要的其他对象。然后,清除阶段会销毁所有不可访问的对象(即那些未标记的对象)。

Data_Wrap_Struct 返回对对象的引用。只要该引用可用于 ruby​​ 代码(例如,存储在局部变量中)或位于堆栈上(由局部 C 变量引用),则不应清除该对象。

从您发布的内容看来,令牌->文本正在被垃圾收集。但为什么会被收集呢?一定不能被标记。Token 对象本身是否被标记?如果是,那么 token->text 应该被标记。尝试在令牌的标记函数中设置断点或打印消息来查看。

如果令牌没有被标记,那么下一步就是找出原因。如果它被标记,那么下一步就是找出为什么 text() 方法返回的字符串被清除(也许它不是被标记的同一个对象)。

另外,您确定是令牌的文本成员导致了异常吗?看着:

http://github.com/dbalmain/ferret/blob/master/ruby/ext/r_analysis.c

我看到令牌和令牌流都有 text() 方法。TokenStream 结构体不保存对其文本对象的引用(它不能,因为它是一个不了解 ruby​​ 的 C 结构体)。因此,包装 C 结构的 Ruby 对象需要保存引用(这是通过 rb_ivar_set 完成的)。

RToken 结构不需要这样做,因为它在其标记函数中标记其文本成员。

还有一件事:您可以通过在循环中显式调用 GC.start 来重现此错误,而不必分配垃圾收集器启动的大量对象。这不会解决问题,但可能会使诊断更简单。