package main
import (
"sync"
"runtime"
)
type S struct {
chs chan int
}
var wg sync.WaitGroup
func worker(s *S) {
for i := range s.chs {
println("In worker, ch = ", i)
}
wg.Done()
}
func main() {
s := S{make(chan int)}
runtime.SetFinalizer(&s, func(ss *S) {
println("Finalizer")
close(ss.chs)
})
wg.Add(1)
go worker(&s)
for i := 0; i < 1; i++ {
s.chs <- 1
}
runtime.GC()
wg.Wait()
}
Output (go 1.8.3):
In worker, ch = 1
Finalizer
I expect this program to deadlock. runtime.GC()
will not collect s
, since worker()
holds a reference to s.chs
.
However it terminates with go 1.8.3. In the finalizer of s
, even close(s.chs)
is called successfully.
I wonder if it has something special do with range
and GC.
Thanks very much.
I'm not 100% sure if this is what is going on, but from the runtime
godoc, second to last paragraph for SetFinalizer
:
For example, if p points to a struct that contains a file descriptor d, and p has a finalizer that closes that file descriptor, and if the last use of p in a function is a call to syscall.Write(p.d, buf, size), then p may be unreachable as soon as the program enters syscall.Write. The finalizer may run at that moment, closing p.d, causing syscall.Write to fail because it is writing to a closed file descriptor (or, worse, to an entirely different file descriptor opened by a different goroutine). To avoid this problem, call runtime.KeepAlive(p) after the call to syscall.Write.
Which suggests to me that the Finalizer
can possibly run as early as immediately after the last usage of the var
in question.
I've never thought of this possibility myself, but technically, the GC would be able to know if it could collect a variable long before the variable's scope is gone. Consider this simplistic example.
func main() {
str := "Hello World"
fmt.Println(str)
someMainLoop()
// nothing after this, but someMainLoop() continues until stopped manually
}
There is no reason why str
couldn't be collected by the GC. It is never used again, and it is very possible it knows this.