On Tuesday, November 7, 2023 at 2:32:28 AM UTC-5 Jan wrote:

Btw, I don't follow the sentence:

"the GC necessarily has to keep referents of the object-to-be-finalized 
live even if the object isn't referenced anymore"

That is true for objects not in cycles that need to be finalized as well. 
I'm not sure I follow the reasoning here ...

That fact alone isn't enough. When you couple that fact with the fact that 
cycles can be arbitrarily deep, then you have a problem.

Sorry, this is perhaps a weird framing since it's based on the 
implementation details. Where I'm starting from is the example at the top 
of this post, which is a cycle that has only a single finalizer. The point 
I'm trying to make is that even having just a single finalizer in a cycle 
is a problem. AFAICT, the dependency rules that both the Go GC and the 
Boehm GC state could still be respected if each strongly connected 
component of objects had just a single finalizer, since that entire 
component could be finalized and freed before the next, and the ordering of 
finalizers is preserved. (If I'm wrong about this point, disregard me. I'm 
pretty sure but not certain that one could design a GC that did this.) But 
having to identify such connected components would be a significant 
performance limitation.


Asking about it in Bard <http://bard.google.com>, it explains:

" 
In Go, objects that are in a cyclic structure and that are marked with a 
finalizer (with SetFinalizer) don't get garbage collected when there are no 
more live pointers to the cyclic structure because the garbage collector 
cannot determine a safe order to run the finalizers.
"

Which seems to match the  Boehm collector 
<https://www.hboehm.info/gc/finalization.html> explanation, described 
under "Topologically Ordered Finalization".

That is also true, but AFAICT only really applies to cycles containing more 
than one finalizer. If a cycle (and I think more specifically, a strongly 
connected component of objects) has just one finalizer, one could argue 
such an ordering problem doesn't exist (unless I'm missing something of 
course). Nonetheless, the cycle still isn't collected.


On Tuesday, November 7, 2023 at 3:51:58 AM UTC+1 Michael Knyszek wrote:

Yes, cycles containing a finalizer aren't guaranteed to be freed. As others 
have pointed out, this is documented. SetFinalizer is really designed for a 
narrow set of use-cases, so concessions were made for overall GC 
performance. This case is one of them.

IIUC, the core problem is a combination of the fact that the cycle can be 
arbitrarily deep and the GC necessarily has to keep referents of the 
object-to-be-finalized live even if the object isn't referenced anymore. 
The GC must follow the pointers in an object's referents, and eventually it 
may come upon the almost-dead object it started from. But at that point it 
likely has no knowledge of where it came from. It's just a pointer on a 
queue. A GC could be implemented that keeps track of where the pointers it 
follows came from, but such an implementation would be substantially less 
performant.

Other GCs make the same choice. See the Boehm collector 
<https://www.hboehm.info/gc/finalization.html>, for example.
On Monday, November 6, 2023 at 10:20:39 AM UTC-5 Harish Ganesan wrote:

Does this behaviour mean that, those memory will never be freed and keep 
piling on ? That can be disastrous.

On Monday, November 6, 2023 at 3:39:50 PM UTC+5:30 Jan wrote:

For what it's worth, a bit of "memory management" on structures in many 
cases is very ok (not sure if in your case). So for your cyclic structure 
with finalizers, requiring the user of your code to call some "Finalize()" 
method (some destructor method you define) that manually breaks the cycle, 
often is an ok solution. Fundamentally, it's the same as requiring someone 
to call Close() on an opened file (or any other resource, like sockets, db 
connections, etc).

As an anecdote, previously when I was doing C++ I was a big fan of 
referenced counted smart pointers (`shred_ptr<>`), which gets most of the 
benefit of GC, but with a much lower cost. They required manually breaking 
cycles, which I didn't find to be an issue at all in the great majority of 
the cases.

On Monday, November 6, 2023 at 11:01:04 AM UTC+1 Jan wrote:

I was very surprised by this behavior of SetFinalizer: and indeed if you 
remove the SetFinalizer one can see that s1 is freed, because the memory 
reported by `m.HeapAlloc` goes back down.

I think you already have the answer: the block that has the cycle (s1 and 
s2) have a SetFinalizer set, and it will never run, per documentation (I 
had never paid attention to this).

A suggestion to work around would be to move the stuff that needs a 
"Finalizer" to a leaf node, as in:

https://go.dev/play/p/WMMTdAza6aZ

But I understand this is not a solution if the finalizer needs to access 
the S1 (so, if the finalizer function needs any information that is not 
self-contained in `S1Data` in my example).
On Sunday, November 5, 2023 at 4:01:14 PM UTC+1 Soren Yang wrote:

As shown in the following code:

cyclic structure with finalizer <https://go.dev/play/p/Fn_h08y-L6b>

The s1 & s2 didn't been free, and finalizer didn't run. But when enable the 
line which have been commented, all run as expected(s1 & s2 been free)。

I have seen the comment in runtime.SetFinalizer: If a cyclic structure 
includes a block with a finalizer, that cycle is not guaranteed to be 
garbage collected and the finalizer is not guaranteed to run, because there 
is no ordering that respects the dependencies.

But why it haven't been free in first code?

-- 
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+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/41a3718b-4c9d-4065-95ab-53593925be77n%40googlegroups.com.

Reply via email to