在 Go 版的 Mongory,C 的記憶體池(memory pool)與 Go 的 GC 必須協同工作:既要確保不洩漏,也要避免過早釋放。本篇整理生命週期設計、runtime.SetFinalizer 的使用方式、以及可驗證的測試思路
runtime.SetFinalizer 在 Go 邊界上兜住遺漏的 Free 呼叫,避免長生物件在 mongory-go/matcher.go 中,為 cgo.Matcher 掛上 finalizer:
func NewCMatcher(condition map[string]any, context *any) (CMatcher, error) {
matcher, err := cgo.NewMatcher(condition, context)
if err != nil { return nil, err }
runtime.SetFinalizer(matcher, func(m *cgo.Matcher) { m.Free() })
return matcher, nil
}
Free(),GC 最終會回收 Go 物件並觸發 finalizer,執行 C 資源釋放Free() 時機,finalizer 是保險,不應成為釋放的唯一路徑記憶體池統一管理跨界 handle,Reset/Free 會刪除所有 handle,避免洩漏:
type MemoryPool struct {
CPoint *C.mongory_memory_pool
handles []rcgo.Handle
}
...
func (m *MemoryPool) Reset() {
C.go_mongory_memory_pool_reset(m.CPoint)
for _, h := range m.handles { h.Delete() }
m.handles = m.handles[:0]
}
Reset():用於短期工作集(例如每次 Match 後),清理暫時性轉換資源Free():釋放整個 pool(通常在物件生命週期結束時)NewMatcher 會建立長期 pool 與 scratch pool,供重複匹配使用Match 結束後 scratch.Reset(),保證後續呼叫不會積累垃圾matcher.Free(),若遺漏,finalizer 兜底alloc/free 是否平衡Match/Explain/Trace,觀察 RSS 與最終值runtime.GC() 與 runtime.KeepAlive() 控制可回收點,驗證 finalizer 會觸發且不二次釋放Free() 可重入(判空)或在呼叫端去重NewHandle 都必須被 trackHandle,並隨 Reset/Free 刪除C 的記憶體池讓配置高效,而 Go 的 GC 讓使用便捷。兩者交界的關鍵是「明確的所有權」與「集中回收」:以 MemoryPool 為核心,結合 Reset/Free 與 SetFinalizer 作為最後保險,讓生命週期可預期、可驗證