在Go语言中进行Finalizer测试

huangapple go评论75阅读模式
英文:

Finalizer testing in Go

问题

有没有合理的方法编写测试用例来测试终结器行为?

我正在尝试在Go中实现一个内存敏感的规范化映射/缓存。由于没有"软引用"的概念(并且因为我的对象图始终形成一个有向无环图),我使用一个小型的接口/框架来跟踪用户空间中的引用计数:

type ReferenceCounted interface {
   RefCount() int
   IncRef()
   DecRef() (bool, error)
}

type Finalizable interface {
   ReferenceCounted
   Finalize()
}

type AbstractCounted struct {
     // unexported fields
}

这个设计的工作原理是,你有一个嵌入了AbstractCounted的结构体,并实现了Finalizable.Finalize()方法 - 这两者共同使得该结构体实现了Finalizable接口。然后有一个函数func MakeRef(obj Finalizable) *Reference,它返回一个指向结构体的指针,该结构体接收一个方法func Get() Finalizable,用于获取引用的目标,并在初始化时通过增加底层对象的引用计数,然后设置一个终结器(通过runtime.SetFinalizer())来减少引用计数。AbstractCountedFinalizable实现在引用计数达到零时调用嵌入它的结构体的Finalize()方法。

所以,现在一切都可以像Java中的软引用/引用队列一样工作,除了这里是引用计数,而不是基于活动词法作用域的标记/扫描来找到"软引用"可达的对象。

这看起来很好!但是 - 我想编写一个测试用例...

我完全理解终结器的调用是延迟的,并且根据reflect包的文档,不对运行它们做出任何保证。其他具有运行时垃圾回收和终结器的语言(如C#、VB、Java、Python等)也是如此。

然而,在所有这些其他语言中,请求显式GC(通过runtime.GC()函数)似乎会导致终结器运行。由于Go中不是这种情况,我无法找到一种方法来编写一个能触发终结器的测试用例。

有没有什么诀窍或代码片段(我可以接受这与当前实现有关,即可能在将来失效!),可以可靠地触发这些终结器,以便我可以编写我的测试用例?

英文:

TLDR: Is there any way to reasonably write test cases to test finalizer behaviors?

I'm trying to implement a memory-sensitive canonicalizing map / cache in Go. Since there is no notion of "soft reference" (and because my object graph will always form a DAG), I'm accomplishing this with a tiny interface/framework that tracks counts of references in the userland:

type ReferenceCounted interface {
   RefCount() int
   IncRef()
   DecRef() (bool, error)
}

type Finalizable interface {
   ReferenceCounted
   Finalize()
}

type AbstractCounted struct {
     // unexported fields
}

The way this works is that you have a struct which embeds AbstractCounted, and implement Finalizable.Finalize() - these together make that struct receive the Finalizable interface. Then there is a function func MakeRef(obj Finalizable) *Reference which returns a pointer to a struct which receives a method func Get() Finalizable, which gets the reference target, and which is initialized by incrementing the ref count on the unerlying object, then setting a finalizer (via runtime.SetFinalizer()) which will decrement the reference. AbstractCounted's Finalizable implementation in turn calls Finalize() on the struct which embeds it when the ref count reaches zero.

So, everything is set up to work very much like soft references / ref queues in Java would now, with the exception that it's reference counting and not a mark/sweep rooted in active lexical scopes that is finding things "softly" reachable.

It seems to work great! But - I would like to write a test case...

I understand fully that finalizer invocation is deferred, and that no guarantees are made about running them per the reflect package docs. The situation is the same in other languages with runtime gc and finalizers (C#, VB, Java, Python, etc) as well.

However, in all of those other languages, requesting explicit GC (here through the runtime.GC() function) does seem to cause finalizers to get run. Since that's not the case in Go, I cannot figure out a way to write a test case that will trigger the finalizers.

Is there any trick or code fragment (I'm OK with this being current-implementation dependent, ie. could-break-in-future!) that will reliably trigger those finalizers so I can write my test?

答案1

得分: 4

你无法直接触发一个Finalizer,所以你能做的最好的办法是确保GC已经启动了runtime.GC(),然后等待Finalizer运行。

如果你查看runtime/mfinal_test.go,你会发现一些通过通道等待Finalizer调用的测试代码:

runtime.SetFinalizer(y, func(z *objtype) { fin <- true })
runtime.GC()
select {
case <-fin:
case <-time.After(4 * time.Second):
    t.Errorf("finalizer of next string in memory didn't run")
}
英文:

You can't explicitly trigger a Finalizer, so the best you can manage is to make sure the GC is started with runtime.GC(), and wait for the finalizer to run.

If you look at runtime/mfinal_test.go, there are some tests that wait for finalizer calls via a channel:

runtime.SetFinalizer(y, func(z *objtype) { fin &lt;- true })
runtime.GC()
select {
case &lt;-fin:
case &lt;-time.After(4 * time.Second):
	t.Errorf(&quot;finalizer of next string in memory didn&#39;t run&quot;)
}

huangapple
  • 本文由 发表于 2015年6月24日 03:26:23
  • 转载请务必保留本文链接:https://go.coder-hub.com/31011765.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定