Calling a function as a go routine produces different call stack from go routine as anonymous func

Issue

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()?

Solution

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.

Answered By – torek

Answer Checked By – Terry (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.