Go 中的终结器测试

Finalizer testing in Go

TLDR: 有什么方法可以合理地编写测试用例来测试finalizer行为吗?

我正在尝试在 Go 中实现内存敏感的规范化映射/缓存。由于没有 "soft reference" 的概念(并且因为我的对象图总是形成一个 DAG),我用一个微小的 interface/framework 来完成这个,它跟踪用户空间中的引用计数:

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,它 returns 一个指向接收方法 func Get() Finalizable 的结构的指针,它获取引用目标,并通过递增底层对象的引用计数来初始化,然后设置一个终结器(通过 runtime.SetFinalizer()),这将减少引用。当引用计数达到零时,AbstractCountedFinalizable 实现依次调用嵌入它的结构 Finalize()

因此,一切都设置得非常像 Java 中的软引用/引用队列现在的工作方式,除了它是引用计数而不是植根于活动词法范围的 mark/sweep那就是找到 "softly" 可达的东西。

看起来效果不错!但是 - 我想写一个测试用例......

我完全理解终结器调用是延迟的,并且不保证 运行 根据 reflect 包文档对它们进行调用。 运行time gc 和终结器(C#、VB、Java、Python 等)的其他语言也是如此。

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

是否有任何技巧或代码片段(我认为这是依赖于当前实现的,即未来可能会中断!)可以可靠地触发这些终结器以便我可以编写测试?

您无法显式触发终结器,因此您可以管理的最佳方法是确保 GC 从 runtime.GC() 开始,并等待终结器到 运行。

如果您查看 runtime/mfinal_test.go,有一些测试通过通道等待终结器调用:

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")
}