I have a function named PrintCaller() which calls runtime.Caller() and skipping one frame to obtain and print the callers (of PrintCaller's) file name and line number. This works as expected when ran synchronously, and if called asynchronous as an anonymous function. However, if ran with just the go
keyword, the stack frame of the caller is replaced with some internal function call.
For example, this is the function:
func printCaller(wait chan bool) {
_, fileName, line, _ := runtime.Caller(1)
fmt.Printf("Filename: %s, line: %d\n", fileName, line)
}
If I call is like this:
func main() {
printCaller()
go func(){printCaller()}()
go printCaller()
}
The output is:
Filename: /tmp/sandbox297971268/prog.go, line: 19
Filename: /tmp/sandbox297971268/prog.go, line: 22
Filename: /usr/local/go-faketime/src/runtime/asm_amd64.s, line: 1374
Working example here: https://play.golang.org/p/Jv21SVDY2Ln
Why does this happen when I call go PrintCaller()
, but not when I call go func(){PrintCaller()}()
? Also, is there any way to make this work using go PrintCaller()
?
The output you've seen is what one would expect, given the inner workings of the Go runtime system:
A goroutine, such as the main one that calls your own main
in package main
, but also including routines started by go somefunc()
, is actually called from some machine-specific startup routine. On the playground that's the one in src/runtime/asm_amd64.s
.
When you define a closure, such as:
f := func() {
// code
}
this creates an anonymous function. Calling it:
f()
calls that anonymous function, from whatever the caller is. This is true regardless of whether the closure is assigned to a variable (as with f
above) or just called right away, or later with defer
, or whatever:
defer func() {
// code ...
}()
So, writing:
go func() {
// code ...
}()
simply calls the anonymous function here from that same machine-specific startup. If that function then calls your printCaller
function, which uses runtime.Caller(1)
to skip over your printCaller
function and find its caller, it finds the anonymous function:
Filename: /tmp/sandbox297971268/prog.go, line: 22
for instance.
But when you write:
go printCaller()
you are invoking the function named printCaller
from the machine-specific goroutine startup code.
Since printCaller
prints the name of its caller, which is this machine-specific startup code, that's what you see.
There's a big caveat here and that is that runtime.Caller
is allowed to fail. That's why it returns a boolean ok
along with the pc uintptr, file string, line int
values. There's no guarantee that the machine-specific assembly caller will be find-able.