I dug into your example and I think I see two problems.

The first problem is that `S` gets allocated in the tiny allocator, and so 
its lifetime may get batched together with something long-lived. This 
creates some inconsistent behaviors in the non-Goexit case. (I have an 
experimental debug mode in the works that will help flag such issues in the 
future.) Making `S` bigger or contain a pointer fixes this.

The second problem is that the variable `s` stays live until after the 
condition, since it's used by Fprint. The compiler doesn't understand that 
`Goexit` exits the function, because it's just a normal function 
implemented in the runtime. Therefore, when the GC is executed from Goexit, 
`s` is actually live on the stack still, so the runtime.GC calls inside the 
defer fail to reclaim it. To prove this point: put a `return` after 
`runtime.Goexit`. For me, this makes the program consistently call both 
AddCleanup callbacks.

On Wednesday, April 9, 2025 at 8:55:41 AM UTC-4 Yaroslav Brustinov wrote:

> Thanks for the explanation!
> I ended up ignoring cleanup checks if test failed (=> runtime.Goexit() is 
> called)
> IMHO it's somewhat counter-intuitive that after calling runtime.Goexit() 
> something is still held...
>
> On Friday, April 4, 2025 at 7:18:32 AM UTC+3 Robert Engels wrote:
>
>> And as Sean pointed out, the reason it is never run is that the object 
>> being cleaned up is still referenced (I didn’t verify this)
>>
>> On Apr 3, 2025, at 5:28 PM, Yaroslav Brustinov <y.bru...@gmail.com> 
>> wrote:
>>
>> 
>>
>> I was sure the doc means the case when a program exits *too quickly,* 
>> before cleanup has a chance to run, or am I wrong?
>> Otherwise, why need to specify "not guaranteed to run before program 
>> exit."? It obviously can't run after program exits :)
>>
>> In the code example above, I can wait indefinitely for cleanup to run, it 
>> will not trigger:
>>
>> for range 20 { // <-- can increase this arbitrary
>> runtime.GC()
>> if releaseFlag.Load() {
>> fmt.Println("released, cond:", goexit)
>> return
>> }
>> time.Sleep(10 * time.Millisecond)
>> }
>>  
>>
>> On Thursday, April 3, 2025 at 8:50:24 PM UTC+3 robert engels wrote:
>>
>>> It states this in the API docs:
>>>
>>> "The cleanup(arg) call is not always guaranteed to run; in particular 
>>> it is not guaranteed to run before program exit."
>>>
>>> On Apr 3, 2025, at 10:23 AM, Yaroslav Brustinov <y.bru...@gmail.com> 
>>> wrote:
>>>
>>> Hello, experts.
>>>
>>> Given following code as example:
>>>
>>> package main
>>>
>>> import (
>>> "fmt"
>>> "io"
>>> "runtime"
>>> "sync/atomic"
>>> "time"
>>> )
>>>
>>> type S struct {
>>> foo int
>>> }
>>>
>>> var released1 atomic.Bool
>>> var released2 atomic.Bool
>>>
>>> func releaseCb(releaseFlag *atomic.Bool) {
>>> fmt.Println("release CB")
>>> releaseFlag.Store(true)
>>> }
>>>
>>> func deferredCheckRelease(goexit bool, releaseFlag *atomic.Bool) {
>>> for range 20 {
>>> runtime.GC()
>>> if releaseFlag.Load() {
>>> fmt.Println("released, cond:", goexit)
>>> return
>>> }
>>> time.Sleep(10 * time.Millisecond)
>>> }
>>> fmt.Println("not released, cond:", goexit)
>>> }
>>>
>>> func f(goexit bool, releaseFlag *atomic.Bool) {
>>> defer deferredCheckRelease(goexit, releaseFlag)
>>> s := &S{1}
>>> runtime.AddCleanup(s, releaseCb, releaseFlag)
>>> if goexit {
>>> // releaseFlag will not be set
>>> runtime.Goexit()
>>> }
>>> // releaseFlag will be set
>>> fmt.Fprint(io.Discard, s)
>>> }
>>>
>>> func main() {
>>> go f(true, &released1)
>>> go f(false, &released2)
>>> time.Sleep(time.Second)
>>> }
>>>
>>> As comment inside mentions, release flag is not set if runtime.Goexit() 
>>> executed.
>>>
>>> Maybe it's because runtime.AddCleanup is not "guaranteed" to run?
>>> In such case would be great to clarify (in docs?) in which cases it 
>>> might not run.
>>> People might rely on the callback...
>>>
>>>
>>>
>>>
>>> -- 
>>> 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/a0df1e83-2e25-4de2-89b5-25de4e892670n%40googlegroups.com
>>>  
>>> <https://groups.google.com/d/msgid/golang-nuts/a0df1e83-2e25-4de2-89b5-25de4e892670n%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/d4f12d3c-bf22-4710-bafd-2df9429cb727n%40googlegroups.com
>>  
>> <https://groups.google.com/d/msgid/golang-nuts/d4f12d3c-bf22-4710-bafd-2df9429cb727n%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/1ce705b2-3417-44d2-a56e-5f3a6d8f6ab4n%40googlegroups.com.

Reply via email to