golang之recover时显示调用栈信息

156 阅读1分钟
package main

import (
	"fmt"
	"os"
	"runtime"
	"sync"
	"time"
)

func main() {
	defer fmt.Println("defer main") // will this be called when panic?
	var user = os.Getenv("USER_")

	wg := sync.WaitGroup{}
	wg.Add(1)
	go func(wgPtr *sync.WaitGroup) {
		defer func() {
			fmt.Println("go defer")
			wgPtr.Done()
		}()
		defer func() {
			fmt.Println("defer here")
		}()

		defer func() {
			fmt.Println("defer caller")
			if x := recover(); x != nil {
				fmt.Println("recover success.")
				buf := make([]byte, 1<<16)
				runtime.Stack(buf, true)
				fmt.Println("buf", string(buf))
			}
		}()

		if user == "" {
			panic("should set user env.")
		}
		fmt.Println("after panic")
	}(&wg)
	wg.Wait()
	time.Sleep(1 * time.Second)
	fmt.Printf("get result %v\r\n", user)
}

输出结果:

dev@dev-VirtualBox test $ go run test_panic_recover.go 
defer caller
recover success.
buf goroutine 6 [running]:
main.main.func1.3()
        /home/dev/test/test_panic_recover.go:31 +0x154
panic(0x4a2ce0, 0x4de010)
        /usr/local/go/src/runtime/panic.go:969 +0x166
main.main.func1(0xc000014070, 0x0, 0xc000014110)
        /home/dev/test/test_panic_recover.go:37 +0x129
created by main.main
        /home/dev/test/test_panic_recover.go:17 +0x13b

goroutine 1 [semacquire]:
sync.runtime_Semacquire(0xc000014118)
        /usr/local/go/src/runtime/sema.go:56 +0x42
sync.(*WaitGroup).Wait(0xc000014110)
        /usr/local/go/src/sync/waitgroup.go:130 +0x64
main.main()
        /home/dev/test/test_panic_recover.go:41 +0x149

defer here
go defer
get result 
defer main

其中调用栈第一行的信息 /home/dev/test/test_panic_recover.go:31,其实就是runtime.Stack(buf, true)的位置,以后的信息就是panic的发生地方和调用地方等等。

如果recover代码不是和panic在一个协程里,那么发生panic时就无法触发recover了。如下例子:

package main

import (
	"fmt"
	"os"
	"sync"
	"time"
)

func main() {
	defer fmt.Println("defer main") // will this be called when panic?
	var user = os.Getenv("USER_")
	defer func() {
		fmt.Println("defer caller")
		if x := recover(); x != nil {
			fmt.Println("recover success.")
		}
	}()

	wg := sync.WaitGroup{}
	wg.Add(1)
	go func(wgPtr *sync.WaitGroup) {
		defer func() {
			fmt.Println("go defer")
			wgPtr.Done()
		}()
		defer func() {
			fmt.Println("defer here")
		}()

		if user == "" {
			panic("should set user env.")
		}
		fmt.Println("after panic")
	}(&wg)
	wg.Wait()
	time.Sleep(1 * time.Second)
	fmt.Printf("get result %v\r\n", user)
}

输出结果:

dev@dev-VirtualBox test $ go run test_panic_recover.go 
defer here
go defer
panic: should set user env.

goroutine 6 [running]:
main.main.func2(0x0, 0x0, 0xc000014100)
	/home/dev/test/test_panic_recover.go:32 +0x10a
created by main.main
	/home/dev/test/test_panic_recover.go:22 +0x157
exit status 2

panic发生在子协程,recover在主协程,不在同一个协程里,整个程序就panic了,没有输出“recover success”就停止了。