5

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

情况如下:

在 Ferret C 代码中的某些地方,我将“令牌”返回给 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);
}

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

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

令牌 = @input.next

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

在 Ruby 领域,我会执行类似 w = token.text.scan('\w+') 的操作

当我在 while 1 循环中运行此代码(以隔离我的问题)时,在某个时刻(大约当我的 ruby​​ 进程内存占用量达到 256MB 时,可能是某个 GC 阈值),Ruby 因错误而死,例如

在终止对象上调用的扫描方法

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

我对 Ruby C 扩展知之甚少,不知道 Data_Wrap_Struct 返回的对象会发生什么。在我看来,Ruby土地中的分配,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);

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

使用此代码,它的工作原理是我不再收到终止的对象错误。

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

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

谢谢。

4

2 回答 2

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 来重现此错误,而不必分配太多垃圾收集器启动的对象。这不会解决问题,但可能会使诊断更简单。

于 2010-01-05T17:10:49.373 回答
0

也许标记为易失性:

http://www.justskins.com/forums/chasing-a-garbage-collection-bug-98766.html

也许您的编译将其引用保存在注册表中而不是堆栈中...我认为在 README.EXT 中提到了一些方法来强制对象永远不会被 GC,但是...问题仍然是为什么很早就收集了...

于 2010-08-01T05:17:28.283 回答