Skip to content

refactor to using runtime.Cleanup #15596

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from

Conversation

cuiweixie
Copy link
Contributor

@cuiweixie cuiweixie commented Aug 16, 2025

What type of PR is this?
Other

Why runtime.AddCleanup?

Go 1.23 introduced runtime.AddCleanup as a simpler, safer, and lower-overhead alternative to runtime.SetFinalizer for cases where you just want to run some cleanup code when an object becomes unreachable.

Problems with runtime.SetFinalizer

  1. Complex semantics
    SetFinalizer allows the finalizer function to access the object’s memory, which means the GC must keep the object alive until the finalizer finishes. This can:

    • Increase GC pressure
    • Cause memory leaks if the finalizer accidentally keeps the object alive
    • Make GC timing and ordering more complicated
  2. Unpredictable execution
    Finalizers run at an unspecified time, possibly much later than the object becomes unreachable.

  3. Misuse risk
    Because you can still read/write the object in the finalizer, people sometimes use it for logic that depends on object state — which is fragile and can break if GC behavior changes.


What runtime.AddCleanup does differently

  • No access to the object’s memory
    When the cleanup function runs, the object has already been collected or is about to be collected. You can’t dereference it.

  • Lower runtime overhead
    The GC doesn’t need to keep the object alive for the cleanup function, making cleanup cheaper.

  • Safer semantics
    Because you can’t touch the object, there’s no risk of accidentally resurrecting it or depending on its state.

  • Intended purpose
    It’s for releasing external resources (file descriptors, network connections, C handles, etc.) when an object is no longer used — not for inspecting object state.


Example

type Resource struct {
    fd int
}

func NewResource(fd int) *Resource {
    r := &Resource{fd: fd}
    runtime.AddCleanup(r, func() {
        fmt.Printf("Closing fd %d\n", fd)
        syscall.Close(fd)
    })
    return r
}

Here:

  • We capture only the fd value in the closure.
  • The cleanup runs when *Resource is unreachable.
  • We never touch r itself in the cleanup.

Summary Table

Feature SetFinalizer AddCleanup
Can access object ✅ Yes ❌ No
GC keeps object alive ✅ Yes ❌ No
Overhead Higher Lower
Safety Risk of misuse Safer
Intended use Complex GC-aware logic Simple resource cleanup

If you want, I can also give you a migration guide for turning your SetFinalizer-based code into AddCleanup-based code, with patterns for cases where you need to access object state.

Do you want me to prepare that migration guide?

Which issues(s) does this PR fix?

Fixes #

Other notes for review

Acknowledgements

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants