Featured image of post Golang 1.24: runtime.AddCleanup

Golang 1.24: runtime.AddCleanup

 

Previously, I wrote an article about the runtime.SetFinalizer that is used to be called when an object is cleaned up, but some issues with this function cause it to be used less frequently.

https://github.com/golang/go/issues/67535

  • SetFinalizer must always refer to the first word of an allocation. This means programmers must know what an ‘allocation’ is, whereas that distinction isn’t generally exposed in the language.
  • There cannot be more than one finalizer on any object.
  • Objects with finalizers involved in any reference cycle will silently fail to be freed, and the finalizer will never run.
  • Objects with finalizers require at least two GC cycles to be freed.

For the above reasons, a new function runtime.AddCleanUp has been added in Golang 1.24 to replace runtime.SetFinalizer.

Neither runtime.AddCleanup nor runtime.SetFinalizer is guaranteed to execute.

The design goal of AddCleanup is to address many of the problems with runtime.SetFinalizer, in particular avoids object resurrection, thus allowing for the timely cleanup of objects, and supporting cyclic cleanup of objects.
API

1
func AddCleanup[T, S any](ptr *T, cleanup func(S), arg S) Cleanup

Based on this, a Demo for RAII (Resource Acquisition Is Initialization) can be written. In the go weak article, we implemented a fixed-length cache, changed the code a bit, add a newElemWeak method that automatically calls delete to remove the key from the cache when the oldest elem is evicted. We don’t need to manage it manually.

1
2
3
4
5
6
7
func (c *WeakCache) newElemWeak(elem *list.Element) weak.Pointer[list.Element] {
	elemWeak := weak.Make(elem)
	runtime.AddCleanup(elem, func(name string) {
		delete(c.cache, name)
	}, elem.Value.(*CacheItem).key)
	return elemWeak
}

A Few Things to Keep in Mind

AddCleanup places a few constraints on ptr and supports attaching multiple cleanup functions to the same pointer. However, if ptr is reachable from cleanup or arg, it will never be reclaimed (memory leak), and the cleanup function will never run. For the moment, this scenario doesn’t pan out. However, a pattern like GODEBUG=gccheckmark=1 may detect this in the future.
For example
https://gist.github.com/hxzhouh/5bb1d55259dcb4dab87b37beaef9bea2

fmt.Println("close f") will not be printed, and the file will not be closed.

When binding multiple cleanups to a single ptr, its running order may be variable since cleanup runs in a separate goroutine.

In particular, if several objects point to each other and become unreachable simultaneously, their cleanup functions can all be run and in any order. This is true even if the objects form a loop (runtime.SetFinalizer creates a memory leak in this case).
for example
https://gist.github.com/hxzhouh/ca402c723faa78726baba7e56bff573a

References

  1. https://github.com/golang/go/issues/67535
Licensed under CC BY-NC-SA 4.0
Last updated on Dec 17, 2024 18:37 CST
Built with Hugo
Theme Stack designed by Jimmy