Filed https://github.com/golang/go/issues/72948, https://github.com/golang/go/issues/72949, and https://github.com/golang/go/issues/72950 to track the suggested improvements.
On Wednesday, March 19, 2025 at 9:52:17 AM UTC-4 Gavra wrote: Thanks Michael. My opinion is 1. It should be mentioned explicitly in the doc that a blocking finalizer will cause future objects with finalizers to leak. Probably next to the "A single goroutine runs all finalizers for a program..." section. Sure, I filed a bug to add a sentence to the documentation and an example to the GC guide. 2. Go should provide a way to know about it when it happens. For example, we could have runtime.LastFinalizer() which returns {StartTime, Trace}. Users may choose to monitor it to detect the issue and its source. I'm not sure it makes sense to add a new API for this. A new API is always a complicated affair, whereas the runtime/metrics package and existing diagnostics (like possibly an analysis in the execution trace tooling) can support detecting common issues without having users change their code (much). Also, I'm personally interested in an explicit debug mode that detects issues with finalizers and cleanups, since that can also be far more thorough. A bespoke API would likely only be able to help with some narrower set of issues. 3. I am not sure if it is intentional, but the goroutine dump indicates finq only if debug=1 is used. It's classified as a system goroutine, but it is supposed be re-classified as a user goroutine while it is executing user code, such that it shows up in regular stack traces. There may be a bug in this reclassification. Do you have a reproducer? That would help isolate the issue. 4. Maybe we can add pprof/sys to indicate such metrics/potential issues? Mentioned above, but yes. Not sure a pprof profile is a good fit, though I suppose you could imagine a "finalizer duration" profile. 5. Regarding the single routine vs multi-routine. Yes, this is clear. Did you consider having a finalizer routine per package? I think this is a significant deficit since it break isolation. Either way, I find providing 1 and 2 sufficient. Unfortunately no, this was not considered. Reference queues a la Java probably should have been discussed more in the design for AddCleanup. Simultaneously, given that we control goroutine scheduling in the runtime, I do think there are ways to make this better in the future without having each package manage its own queue. Do you want me to start a formal discussion in golang/go or will you take it from here? I think the issues I filed are sufficient, but feel free to file more if you disagree. Thanks again. Thank you for reporting back to this thread! On Wednesday, 19 March 2025 at 15:24:46 UTC+2 Michael Knyszek wrote: > Unfortunately, this is pretty much how such features tend to be designed to keep overheads low. Er, sorry, this is actually a little more nuanced. Creating a new goroutine for each finalizer/cleanup could cause a different problem of generating too many goroutines (so, too much memory being used) in addition to the cost of creating and scheduling new goroutines, and once that behavior is relied on it's very difficult to change. For example, context.AfterFunc has this behavior, and that's an example where the new goroutine overhead has been reported to us as causing performance issues as well. I suspect there just isn't going to be one right answer for all cases. Cleanups and finalizers are relatively low level, so the scales tilted more toward having users do their own scheduling, at the risk of the blocking issues you ran into. (As well as the fact that cleanups and finalizers have more loose guarantees in general.) On Wednesday, March 19, 2025 at 9:05:30 AM UTC-4 Michael Knyszek wrote: Sorry you ran into this issue. Unfortunately, this is pretty much how such features tend to be designed to keep overheads low. You make a good point that we should have more diagnostics to detect when this happens. We can add a `runtime/metrics` metric, or perhaps add extend the capabilities of an experimental debug mode I'm working on for cleanups and finalizers (https://go.dev/cl/634599). At the very least, I'll make note of this in https://go.dev/doc/gc-guide. Note also that even though `runtime.AddCleanup` is documented as running cleanups concurrently, 1. In Go 1.24 we don't actually do that, so it won't help you to switch in the short term. I have a patch for Go 1.25 to do that properly. ( https://go.dev/cl/650697) 2. Even once we can run cleanups concurrently, it's still hazardous to spend a long time in a cleanup. The Go 1.25 implementation will not run each cleanup in its own goroutine, as that is just too expensive and very hard to change. You can still exhaust the finite number of goroutines used to run cleanups. Java Cleaners have a similar limitation in that each Cleaner runs all its actions on a single thread. ("The execution of the cleaning action is performed by a thread associated with the cleaner. [...] The thread runs until all registered cleaning actions have completed and the cleaner itself is reclaimed by the garbage collector.", from https://docs.oracle.com/javase/9/docs/api/java/lang/ref/Cleaner.html) There is a difference here in that you can create as many cleaners as you want, AFAICT. I'll note that we might be able to do something more complicated in the future in which we "detach" a cleanup goroutine if we detect that it blocks or runs too long at runtime. But just to set expectations, I don't think there's a high likelihood that we'll do something like this any time soon. On Wednesday, March 19, 2025 at 8:49:59 AM UTC-4 Gavra wrote: Yes, even if it was only a few ms, they should have run it in a goroutine. On Wednesday, 19 March 2025 at 14:17:56 UTC+2 Robert Engels wrote: The docs: “ A single goroutine runs all finalizers for a program, sequentially. If a finalizer must run for a long time, it should do so by starting a new goroutine.” So the code did not follow the api docs - as apparently the close() is indefinite. On Mar 19, 2025, at 6:46 AM, Gavra <gav...@gmail.com> wrote: Yes, I intend to open a bug for them. I agree that one should not relay on the execution of finalizers. But, the fact that the runtime just piles them up because one package did wrong is outrageous. By the way, I am not sure why but I can see the mfinal routine only when running my program from Goland; When I build it manually the routine is invisible using pprof. On Wednesday, 19 March 2025 at 13:38:04 UTC+2 Robert Engels wrote: In fact the code you reference - the close() - does things the Go docs warn specifically not to do. You may be better off using runtime.AddCleanup() On Mar 19, 2025, at 6:32 AM, Robert Engels <ren...@ix.netcom.com> wrote: In principle, I would argue that there is a correctness problem. You should not rely on finalizers ever - they are catches and often optional - so the design relying on finalizers to run is what is broken. In the real world they can make solving certain problems much easier - especially with shared resources. I know Java has deprecated them in lieu of other technologies like Cleaners. On Mar 19, 2025, at 6:19 AM, Gavra <gav...@gmail.com> wrote: https://github.com/hirochachacha/go-smb2/blob/c8e61c7a5fa7bcd1143359f071f9425a9f4dda3f/client.go#L1063 We are looking for why exactly it blocked (probably incorrect ctx) but I think this close should run in a goroutine. On Wednesday, 19 March 2025 at 12:29:34 UTC+2 Brian Candler wrote: On Wednesday, 19 March 2025 at 09:55:58 UTC Gavra wrote: This finalizer blocks the runtime's finalizer goroutine Out of interest, what made it block? Was the finalizer doing some channel communication, for example? -- 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 visit https://groups.google.com/d/msgid/golang-nuts/5c7c034d-3bc6-4fe5-82bb-a318310bd82fn%40googlegroups.com <https://groups.google.com/d/msgid/golang-nuts/5c7c034d-3bc6-4fe5-82bb-a318310bd82fn%40googlegroups.com?utm_medium=email&utm_source=footer> . -- 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 visit https://groups.google.com/d/msgid/golang-nuts/6AABBB52-5BC6-4874-900B-C423E37952C4%40ix.netcom.com <https://groups.google.com/d/msgid/golang-nuts/6AABBB52-5BC6-4874-900B-C423E37952C4%40ix.netcom.com?utm_medium=email&utm_source=footer> . -- 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 visit https://groups.google.com/d/msgid/golang-nuts/75da8ea2-c099-483c-969a-ab99a91da2a4n%40googlegroups.com <https://groups.google.com/d/msgid/golang-nuts/75da8ea2-c099-483c-969a-ab99a91da2a4n%40googlegroups.com?utm_medium=email&utm_source=footer> . -- 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 visit https://groups.google.com/d/msgid/golang-nuts/d7ff0b24-91f1-4bfa-84c7-3ccd99901ba3n%40googlegroups.com.