Garbage collection cannot own your service's resource limits
Go cleanup callbacks follow memory reachability. Scarce files and external handles still need explicit release, bounded ownership and resource accounting.
A service should release a scarce external resource when its owner finishes using it. Waiting until a memory wrapper becomes unreachable answers a different question. Go's cleanup callbacks can provide a useful fallback, but they cannot turn garbage collection into a timetable for returning file descriptors or other limited resources. The business consequence is availability: a process can exhaust an external allowance while its memory use still looks unremarkable.
This November 2026 retrospective looks back at the Go team's 6 March 2025 explanation of cleanups and weak pointers. It discussed low-level tools available in Go 1.24. The distinction worth retaining is between the lifetime of a Go object and the ownership of whatever that object represents outside the managed heap.
Reachability does not measure resource pressure
A wrapper object may be small while the resource behind it is expensive. An illustrative file-processing service could allocate little Go memory per open file and still retain many file descriptors. Losing the last reference to each wrapper does not establish when those descriptors become available again. A memory collector has no application-level promise to release them before the next request arrives.
The mismatch can become more serious under concurrency. If each accepted job acquires a resource and relies on eventual cleanup, accepting more jobs increases outstanding ownership without giving the service a clear release point. Limiting allocations is not the same as limiting resources. Admission control needs to count the resource whose shortage would prevent useful work.
A deterministic ownership design names the code responsible for release and the point where responsibility ends. In an ordinary function, an explicit Close with suitable deferred release may express that boundary. If ownership crosses a goroutine or a returned object, the contract needs to say who closes it and whether the caller may continue using it afterward. The language cannot infer that product decision from reachability.
These examples describe proposed designs, not an incident or load experiment. They explain why resource accounting belongs beside request concurrency and failure handling. A service needs to know whether it can accept another job now; an eventual opportunity for cleanup cannot answer that admission question.
What the cleanup mechanism contributes
The March article describes runtime.AddCleanup attaching a callback to an object and invoking it later with a separate argument. It uses memory mappings and weak-reference cache examples to explain why that separation matters. Keeping the original object out of the callback's argument avoids some of the object-resurrection problems associated with finalizers.
That design still needs care. The current runtime documentation, consulted separately in November 2026, says that a reference from the callback or its argument back to the object prevents collection and therefore prevents cleanup. It also documents concurrent execution and no specified ordering. These are reference semantics, not a guarantee that a callback will run promptly or before process exit.
For a library author, that makes cleanup useful as defensive recovery around a properly specified explicit release method. It may recover a forgotten resource during a long-running process. It cannot certify that forgetting release is harmless. The library should expose a clear normal path and make double release safe through its actual state-management design.
An explicit close and a cleanup callback can race unless the implementation coordinates them. Stopping a registered cleanup is not a substitute for reasoning about ownership and an already running callback. A proposed wrapper needs one release decision shared by both paths, with synchronization appropriate to the resource. This article supplies no implementation claimed to be tested.
Cache-like ownership has a different tradeoff
There are cases where reachability is a useful signal. Suppose an illustrative cache keeps reusable objects without needing to keep every object alive indefinitely. A weak reference can allow reuse while another part of the application still owns the value. Once nobody needs it, retaining it purely for the cache may be unnecessary.
Here the value can be reconstructed. A missing cache entry is an expected outcome, not the loss of a mandatory release deadline. The design can accept garbage collection influencing how long reuse remains possible, provided correctness does not depend on the entry surviving. That is a different contract from holding a scarce resource until the next customer request.
Weak references also do not automatically provide a complete cache policy. Keys and bookkeeping can remain, and application demand can still exceed a sensible bound. Decide which data can disappear, how stale entries are removed and what happens when reconstruction is expensive. Reachability can support that policy without replacing every explicit limit.
Explicit ownership need not burden every caller
The strongest objection is ergonomic: requiring every user of a wrapper to understand low-level lifetime rules can make a library difficult to use. That is a reason to improve its interface. A callback-based helper that contains acquisition and release can keep ownership local. A bounded pool may centralize the resource budget. The suitable abstraction depends on whether callers need the resource beyond one operation.
Cleanup can remain behind that interface as a fallback. Document which behavior is guaranteed by normal use and which recovery is opportunistic. Avoid describing all release paths as equivalent when only one has a deterministic relationship to completed work. That distinction helps operators interpret resource pressure without blaming the collector for an ownership gap.
For custom software development, ask for a resource to be traced from acquisition to normal release, including the error path and any ownership transfer. Count outstanding resources independently of heap memory. Keep cleanups where delayed recovery is acceptable, and make the path that protects service capacity explicit.
Paul