Finalizer statistics

389 views Asked by At

Is there a way to obtain the total number of finalizers registered using runtime.SetFinalizer and which have not yet run?

We are considering adding a struct with a registered finalizer to some of our products to release memory allocated using malloc, and the object could potentially have a relatively high allocation rate. It would be nice if we could monitor the number of finalizers, to make sure that they do not pile up and trigger out-of-memory errors (like they tend to with other garbage collectors).

(I'm aware that explicit deallocation would avoid this problem, but we cannot change the existing code, which does not call a Close function or something like that.)

2

There are 2 answers

1
AudioBubble On

You can keep keep a count of these objects by incrementing and decrementing a unexported package variable when a new object is created and finalized, respectively.

For example:

package main

import (
    "fmt"
    "runtime"
    "sync/atomic"
)

var totalObjects int32

func TotalObjects() int32 {
    return atomic.LoadInt32(&totalObjects)
}

type Object struct {
    p uintptr // C allocated pointer
}

func NewObject() *Object {
    o := &Object{
    }
    // TODO: perform other initializations
    atomic.AddInt32(&totalObjects, 1)
    runtime.SetFinalizer(o, (*Object).finalizer)
    return o
}

func (o *Object) finalizer() {
    atomic.AddInt32(&totalObjects, -1)
    // TODO: perform finalizations
}

func main() {
    fmt.Println("Total objects:", TotalObjects())
    for i := 0; i < 100; i++ {
        _ = NewObject()
        runtime.GC()
    }
    fmt.Println("Total objects:", TotalObjects())
}

https://play.golang.org/p/n35QABBIcj

0
John Doe On

It's possible to make a wrapper on runtime.SetFinalizer which does the counting for you. Of course, it's a question of using it everywhere where you use SetFinalizer.

In case this is problematic, you can also modify SetFinalizer source code directly, but that requires a modified Go compiler.

Atomic integers are used as SetFinalizer may be called on different threads, and otherwise a counter may not be accurate as without those a race condition could possibly occur. Golang guarantees that finalizers are called from a single goroutine, so it's not needed for inner function.

https://play.golang.org/p/KKCH2UwTFYw

package main

import (
    "fmt"
    "reflect"
    "runtime"
    "sync/atomic"
)

var finalizersCreated int64
var finalizersRan int64

func SetFinalizer(obj interface{}, finalizer interface{}) {
    finType := reflect.TypeOf(finalizer)
    funcType := reflect.FuncOf([]reflect.Type{finType.In(0)}, nil, false)
    f := reflect.MakeFunc(funcType, func(args []reflect.Value) []reflect.Value {
        finalizersRan++
        return reflect.ValueOf(finalizer).Call([]reflect.Value{args[0]})
    })
    runtime.SetFinalizer(obj, f.Interface())
    atomic.AddInt64(&finalizersCreated, 1)
}

func main() {
    v := "a"
    SetFinalizer(&v, func(a *string) {
        fmt.Println("Finalizer ran")
    })
    fmt.Println(finalizersRan, finalizersCreated)
    runtime.GC()
    fmt.Println(finalizersRan, finalizersCreated)
}