What are finalizers missing?

219 views
Skip to first unread message

Tyler Compton

unread,
Feb 14, 2020, 2:10:01 PM2/14/20
to golang-nuts
The conventional wisdom I see in the Go community is to avoid using finalizers to manage file descriptors, C memory, or any other non-Go memory resource. The generally accepted rationale (at least to my understanding) is that the garbage collector cannot be expected to run the finalizer promptly, and it may never be run at all.

This is where I start speculating. I assume this is because the garbage collector has no understanding of C memory, file descriptors, or any other kind of resource. If we're running out of memory due to a malloc in C, there's no way for the garbage collector to know which Go objects it can finalize in order to get that memory back. However, as a thought experiment, if we were able to tell the garbage collector (say, through a function in the runtime package) that a certain Go object owns X number of file descriptors or X bytes of C memory, could the garbage collector be augmented to free these objects (and thus run their finalizers) intelligently? Would doing so allow us to use finalizers for these resources with confidence?

I don't mean this to be a proposal or suggestion. I'm asking more as an opportunity to learn.

Robert Engels

unread,
Feb 14, 2020, 2:18:26 PM2/14/20
to Tyler Compton, golang-nuts

I suggest you look at PhantomReference in Java, and the Cleaner/Disposer classes/patterns for how this would work. Go doesn't have PhantomReferences, so it's moot at this time.
--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/CAA%3DXfu3xrghcLhrj3La1niVnHs07DdCUUDfWjpVTyTUkhMwTjA%40mail.gmail.com.



Tyler Compton

unread,
Feb 14, 2020, 3:58:00 PM2/14/20
to Robert Engels, golang-nuts
Thanks for the pointer! I took a look at PhantomReferences and Cleaners, and I see how they could be a more robust way to free resources for an object. However, I don't see a mechanism by which they can express to the garbage collector how much "pressure" they exert on a finite resource (file descriptors, memory not managed by the Java GC, etc). I don't know very much about the Java GC, but in a scenario where we have a few Java objects with a low amount of memory usage but with a large number of (for example) file descriptors, the Java GC might just decide not to run because it has no knowledge of the fact that running would help free up file descriptors.

In other words, I see how PhantomReferences and Cleaners are less brittle than runtime.SetFinalizer, but not how they're more capable of managing resources that the GC is not already aware of.

Tyler Compton

unread,
Feb 14, 2020, 4:31:54 PM2/14/20
to golang-nuts
I found a relevant blog post by David Crawshaw that touches on the topic of a garbage collector that can be extended to understand resources other than Go-managed memory: https://crawshaw.io/blog/tragedy-of-finalizers

Robert Engels

unread,
Feb 14, 2020, 4:40:53 PM2/14/20
to Tyler Compton, golang-nuts
You are correct this it is not a silver bullet, but one thing to note about Java - at the stdlib level, if it attempts to allocate a resource and that returns a 'resource exhausted' (at least for memory), it will force a GC, at which point the PhantomReferences processing is forced to occur, allowing the system to free resources.

Don't quote me, but I do believe there were some other recent runtime changes so that other 'resource exhausted' (like file descriptors) force the same process for similar reasons.

Typically though, Java is continually processing PhantomReferences when appropriate so it isn't really an issue (that is, enough other things trigger this process).



-----Original Message-----
From: Tyler Compton
Sent: Feb 14, 2020 2:57 PM
To: Robert Engels
Cc: golang-nuts
Subject: Re: [go-nuts] What are finalizers missing?

Thanks for the pointer! I took a look at PhantomReferences and Cleaners, and I see how they could be a more robust way to free resources for an object. However, I don't see a mechanism by which they can express to the garbage collector how much "pressure" they exert on a finite resource (file descriptors, memory not managed by the Java GC, etc). I don't know very much about the Java GC, but in a scenario where we have a few Java objects with a low amount of memory usage but with a large number of (for example) file descriptors, the Java GC might just decide not to run because it has no knowledge of the fact that running would help free up file descriptors.

In other words, I see how PhantomReferences and Cleaners are less brittle than runtime.SetFinalizer, but not how they're more capable of managing resources that the GC is not already aware of.

On Fri, Feb 14, 2020 at 11:17 AM Robert Engels <ren...@ix.netcom.com> wrote:

I suggest you look at PhantomReference in Java, and the Cleaner/Disposer classes/patterns for how this would work. Go doesn't have PhantomReferences, so it's moot at this time.
-----Original Message-----
From: Tyler Compton
Sent: Feb 14, 2020 1:09 PM
To: golang-nuts
Subject: [go-nuts] What are finalizers missing?

The conventional wisdom I see in the Go community is to avoid using finalizers to manage file descriptors, C memory, or any other non-Go memory resource. The generally accepted rationale (at least to my understanding) is that the garbage collector cannot be expected to run the finalizer promptly, and it may never be run at all.

This is where I start speculating. I assume this is because the garbage collector has no understanding of C memory, file descriptors, or any other kind of resource. If we're running out of memory due to a malloc in C, there's no way for the garbage collector to know which Go objects it can finalize in order to get that memory back. However, as a thought experiment, if we were able to tell the garbage collector (say, through a function in the runtime package) that a certain Go object owns X number of file descriptors or X bytes of C memory, could the garbage collector be augmented to free these objects (and thus run their finalizers) intelligently? Would doing so allow us to use finalizers for these resources with confidence?

I don't mean this to be a proposal or suggestion. I'm asking more as an opportunity to learn.

--

robert engels

unread,
Feb 14, 2020, 7:25:13 PM2/14/20
to Robert Engels, Tyler Compton, golang-nuts
One other note - I am not a fan of PhantomReferences/Cleaners. As per this thread https://groups.google.com/forum/#!topic/golang-nuts/9br5GgucQcU I agree that Finalizers are much simpler to understand and deal with.

I think this could easily be achieved where certain errors in the runtime are trapped, and a GC performed, then the operation retried, before failing the operation. I think Go’s ‘polling’ nature makes this more transparent and easier to implement than Java.

robert engels

unread,
Feb 14, 2020, 7:26:28 PM2/14/20
to Robert Engels, Tyler Compton, golang-nuts
One additional point, SoftReferences are a different matter - they make dynamic caches in large complex system easier to implement if not a bit more unpredictable.

robert engels

unread,
Feb 14, 2020, 7:40:04 PM2/14/20
to Robert Engels, Tyler Compton, golang-nuts
This actually sounds like a decent Go library - something that monitor open file handles (or other registered resources) of the process, and then forces a finalizer stage before the OS limits would be hit.

On Feb 14, 2020, at 6:24 PM, robert engels <ren...@ix.netcom.com> wrote:

Jake Montgomery

unread,
Feb 15, 2020, 1:52:23 PM2/15/20
to golang-nuts
On Friday, February 14, 2020 at 2:10:01 PM UTC-5, Tyler Compton wrote:
The conventional wisdom I see in the Go community is to avoid using finalizers to manage file descriptors, C memory, or any other non-Go memory resource. The generally accepted rationale (at least to my understanding) is that the garbage collector cannot be expected to run the finalizer promptly, and it may never be run at all.

 
This is where I start speculating. I assume this is because the garbage collector has no understanding of C memory, file descriptors, or any other kind of resource. If we're running out of memory due to a malloc in C, there's no way for the garbage collector to know which Go objects it can finalize in order to get that memory back.

I don't think your analysis is correct. IIRC, the spec makes no guarantees about finalizers ever actually being run. But in practice they will all be found and run after the first garbage collection after the object becomes unreachable. (Assuming the app continues to run.) So it's not a matter of  the go runtime not knowing "which Go objects it can finalize in order to get that [a resource] back", its simply a matter of the variable pace of GC. In theory GC can happen as infrequently as every 2 minutes. So if you are ok with you non-Go resource lingering for that long, then use finalizers. In most cases that is not sufficient though.

One other, minor, consideration, is also that setting a finalizer will cause the object to escape to the heap.

Tyler Compton

unread,
Feb 18, 2020, 12:30:21 PM2/18/20
to Jake Montgomery, golang-nuts
Good point, I had forgotten about the 2 minute rule the GC has.

So it's not a matter of  the go runtime not knowing "which Go objects it can finalize in order to get that [a resource] back", its simply a matter of the variable pace of GC. In theory GC can happen as infrequently as every 2 minutes. So if you are ok with you non-Go resource lingering for that long, then use finalizers. In most cases that is not sufficient though.

I think you're touching on the point I'm trying to make. The variable pace of the GC is okay for Go memory, because it can adjust that pace if necessary to keep up with garbage production. The GC can't make pace adjustments based on the consumption of any non-Go-memory resources, which means we can only count on a maximum of 2 minutes between GC runs.

I could certainly see situations where there's no way the GC could make a smart enough decision to fit an application's needs, however. For example, if your program writes to a file that another program then uses, you would want to make sure that the file is closed before that other program is spun up. Even a 1 second GC delay might not work here.

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.

Robert Engels

unread,
Feb 18, 2020, 1:08:06 PM2/18/20
to Tyler Compton, Jake Montgomery, golang-nuts
You should always use an explicit Close() - a Close() performed by a finalizer should only be use to simplify the case of a shared resource that would typically involve manual reference counting (which is very susceptible to bugs - especially in a concurrent environment).

If you have a highly concurrent environment with lots of shared files (enough to run out of resources), you probably need explicit resource management.

-----Original Message-----
From: Tyler Compton
Sent: Feb 18, 2020 11:29 AM
To: Jake Montgomery
Cc: golang-nuts
Reply all
Reply to author
Forward
0 new messages