使用 Ruby C 扩展进行垃圾收集
我正在通过 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 中,此代码与以下内容相关:
token = @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 中的作业 land, token =, 应该创建对它的引用。
我的“解决方法”/“修复”是在 @input 引用的对象,并将令牌文本存储在其中,以 获得额外的参考。所以 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 字符串,这样 token.text 的实例就不会被删除 直到下一次调用@input.next(此时一个不同的 token.text 替换 curtoken 中的旧值)。
我的问题是:为什么之前不起作用?不应该 Data_Wrap_Structure 返回一个对象,当在 Ruby 域中分配时, 有一个有效的引用并且不会被 Ruby 删除?
谢谢。
I am working my way through Ferret (Ruby port of Lucene) code to solve
a bug. Ferret code is mainly a C extension to Ruby. I am running into
some issues with the garbage collector. I managed to fix it, but I
don't completely understand my fix =) I am hoping someone with deeper
knowledge of Ruby and C extension (this is my 3rd day with Ruby) can
elaborate. Thanks.
Here is the situation:
Some where in Ferret C code, I am returning a "Token" to Ruby land.
The code looks like
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 calls rb_gc_mark(token->text) and frt_token_free
just frees the token with free(token)
In Ruby, this code correlates to the following:
token = @input.next
Basically, @input is set to some object, calling the next method on it
triggers the get_token C call, which returns a token object.
In Ruby land, I then do something like w = token.text.scan('\w+')
When I run this code inside a while 1 loop (to isolate my problem), at
some point (roughly when my ruby process mem footprint goes to 256MB,
probably some GC threshold), Ruby dies with errors like
scan method called on terminated object
Or just core dumps. My guess was that token.text was garbage collected.
I don't know enough about Ruby C extension to know what happens with
Data_Wrap_Struct returned objects. Seems to me the assignment in Ruby
land, token =, should create a reference to it.
My "work-around"/"fix" is to create a Ruby instance variable in the
object referred to by @input, and stores the token text in there, to
get an extra reference to it. So the C code looks like
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);
So now I've created a "curtoken" in the input instance variable, and
saved a copy of the text there... I've taken care to remove/delete
this reference in the free callback of the class for @input.
With this code, it works in that I no longer get the terminated object
error.
The fix seems to make sense to me -- it keeps an extra ref in curtoken
to the token.text string so an instance of token.text won't be removed
until the next time @input.next is called (at which time a different
token.text replaces the old value in curtoken).
My question is: why did it not work before? Shouldn't
Data_Wrap_Structure return an object that, when assigned in Ruby land,
has a valid reference and not be removed by Ruby?
Thanks.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
当Ruby垃圾收集器被调用时,它有一个标记阶段和一个清除阶段。标记阶段通过标记来标记系统中的所有对象:
以及一些对此讨论不重要的其他对象。然后,清除阶段会销毁所有不可访问的对象(即那些未标记的对象)。
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 来重现此错误,而不必分配垃圾收集器启动的大量对象。这不会解决问题,但可能会使诊断更简单。
When the Ruby garbage collector is invoked, it has a mark phase and a sweep phase. The mark phase marks all objects in the system by marking:
as well as a number of other objects that are not important to this discussion. The sweep phase then destroys any objects that are not accessible (i.e. those that were not marked).
Data_Wrap_Struct returns a reference to an object. As long as that reference is available to ruby code (e.g. stored in a local variable) or is on the stack (referred to by a local C variable), the object should not be swept.
It's looks like from what you've posted that token->text is getting garbage collected. But why is it getting collected? It must not be getting marked. Is the Token object itself getting marked? If it is, then token->text should be getting marked. Try setting a breakpoint or printing a message in the token's mark function to see.
If the token is not getting marked, then the next step is to figure out why. If it is getting marked, then the next step is to figure out why the string returned by the text() method is getting swept (maybe it's not the same object that is getting marked).
Also, are you sure that it is the token's text member that is causing the exception? Looking at:
http://github.com/dbalmain/ferret/blob/master/ruby/ext/r_analysis.c
I see that the token and the token stream both have text() methods. The TokenStream struct doesn't hold a reference to its text object (it can't, as it's a C struct with no knowledge of ruby). Thus, the Ruby object wrapping the C struct needs to hold the reference (and this is being done with rb_ivar_set).
The RToken struct shouldn't need to do this, because it marks its text member in its mark function.
One more thing: you may be able to reproduce this bug by calling GC.start explicitly in your loop rather than having to allocate so many objects that the garbage collector kicks in. This won't fix the problem but might make diagnosis simpler.
也许标记为易失性:
http://www.justskins .com/forums/chasing-a-garbage-collection-bug-98766.html
也许你的编译将其引用保留在注册表中而不是堆栈中......我认为在 README.EXT 中提到了一些方法强制一个对象永远不会被GC,但是......问题仍然是为什么它被提前收集......
perhaps mark as volatile:
http://www.justskins.com/forums/chasing-a-garbage-collection-bug-98766.html
maybe your compile is keeping its reference in a registry instead of the stack...there is some way mentioned I think in README.EXT to force an object to never be GC'ed, but...the question still remains as to why it's being collected early...