panicrecover
思考
一、为什么会中止运行
func main() {
panic("EDDYCJY.")
}
输出结果:
$ go run main.go
panic: EDDYCJY.
goroutine 1 [running]:
main.main()
/Users/eddycjy/go/src/github.com/EDDYCJY/awesomeProject/main.go:4 +0x39
exit status 2
panicpanic
二、为什么不会中止运行
func main() {
defer func() {
if err := recover(); err != nil {
log.Printf("recover: %v", err)
}
}()
panic("EDDYCJY.")
}
输出结果:
$ go run main.go
2019/05/11 23:39:47 recover: EDDYCJY.
deferrecover
三、不设置 defer 行不
deferrecoverdefer
func main() {
if err := recover(); err != nil {
log.Printf("recover: %v", err)
}
panic("EDDYCJY.")
}
输出结果:
$ go run main.go
panic: EDDYCJY.
goroutine 1 [running]:
main.main()
/Users/eddycjy/go/src/github.com/EDDYCJY/awesomeProject/main.go:10 +0xa1
exit status 2
deferrecoverdefer
deferrecover
deferrecover
四、为什么起个 goroutine 就不行
func main() {
go func() {
defer func() {
if err := recover(); err != nil {
log.Printf("recover: %v", err)
}
}()
}()
panic("EDDYCJY.")
}
输出结果:
$ go run main.go
panic: EDDYCJY.
goroutine 1 [running]:
main.main()
/Users/eddycjy/go/src/github.com/EDDYCJY/awesomeProject/main.go:14 +0x51
exit status 2
Goroutine
源码
接下来我们将带着上述 4+1 个小思考题,开始对源码的剖析和分析,尝试从阅读源码中找到思考题的答案和更多为什么
数据结构
type _panic struct {
argp unsafe.Pointer
arg interface{}
link *_panic
recovered bool
aborted bool
}
panic_panicpanic_panicpanic
deferpanicpanic_panicpanicrecoverpanic
link
恐慌 panic
func main() {
panic("EDDYCJY.")
}
输出结果:
$ go run main.go
panic: EDDYCJY.
goroutine 1 [running]:
main.main()
/Users/eddycjy/go/src/github.com/EDDYCJY/awesomeProject/main.go:4 +0x39
exit status 2
panic
$ go tool compile -S main.go
"".main STEXT size=66 args=0x0 locals=0x18
0x0000 00000 (main.go:23) TEXT "".main(SB), ABIInternal, $24-0
0x0000 00000 (main.go:23) MOVQ (TLS), CX
0x0009 00009 (main.go:23) CMPQ SP, 16(CX)
...
0x002f 00047 (main.go:24) PCDATA $2, $0
0x002f 00047 (main.go:24) MOVQ AX, 8(SP)
0x0034 00052 (main.go:24) CALL runtime.gopanic(SB)
runtime.gopanic
func gopanic(e interface{}) {
gp := getg()
...
var p _panic
p.arg = e
p.link = gp._panic
gp._panic = (*_panic)(noescape(unsafe.Pointer(&p)))
for {
d := gp._defer
if d == nil {
break
}
// defer...
...
d._panic = (*_panic)(noescape(unsafe.Pointer(&p)))
p.argp = unsafe.Pointer(getargp(0))
reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz))
p.argp = nil
// recover...
if p.recovered {
...
mcall(recovery)
throw("recovery failed") // mcall should not return
}
}
preprintpanics(gp._panic)
fatalpanic(gp._panic) // should not return
*(*int)(nil) = 0 // not reached
}
Goroutinepanic_panicGoroutine_deferdeferreflectcalldeferrecovergorecoverpreprintpanicspanicfatalpanicexit(2)
panicGoroutine(g)._panicGoroutinedeferrecover
无法恢复的恐慌 fatalpanic
func fatalpanic(msgs *_panic) {
pc := getcallerpc()
sp := getcallersp()
gp := getg()
var docrash bool
systemstack(func() {
if startpanic_m() && msgs != nil {
...
printpanics(msgs)
}
docrash = dopanic_m(gp, pc, sp)
})
systemstack(func() {
exit(2)
})
*(*int)(nil) = 0
}
exitprintpanics
func printpanics(p *_panic) {
if p.link != nil {
printpanics(p.link)
print("\t")
}
print("panic: ")
printany(p.arg)
if p.recovered {
print(" [recovered]")
}
print("\n")
}
recoverfatal errorruntime.throwrecoverexit(2)
恢复 recover
func main() {
defer func() {
if err := recover(); err != nil {
log.Printf("recover: %v", err)
}
}()
panic("EDDYCJY.")
}
输出结果:
$ go run main.go
2019/05/11 23:39:47 recover: EDDYCJY.
recoverpanic
$ go tool compile -S main.go
"".main STEXT size=110 args=0x0 locals=0x18
0x0000 00000 (main.go:5) TEXT "".main(SB), ABIInternal, $24-0
...
0x0024 00036 (main.go:6) LEAQ "".main.func1·f(SB), AX
0x002b 00043 (main.go:6) PCDATA $2, $0
0x002b 00043 (main.go:6) MOVQ AX, 8(SP)
0x0030 00048 (main.go:6) CALL runtime.deferproc(SB)
...
0x0050 00080 (main.go:12) CALL runtime.gopanic(SB)
0x0055 00085 (main.go:12) UNDEF
0x0057 00087 (main.go:6) XCHGL AX, AX
0x0058 00088 (main.go:6) CALL runtime.deferreturn(SB)
...
0x0022 00034 (main.go:7) MOVQ AX, (SP)
0x0026 00038 (main.go:7) CALL runtime.gorecover(SB)
0x002b 00043 (main.go:7) PCDATA $2, $1
0x002b 00043 (main.go:7) MOVQ 16(SP), AX
0x0030 00048 (main.go:7) MOVQ 8(SP), CX
...
0x0056 00086 (main.go:8) LEAQ go.string."recover: %v"(SB), AX
...
0x0086 00134 (main.go:8) CALL log.Printf(SB)
...
通过分析底层调用,可得知主要是如下几个方法:
- runtime.deferproc
- runtime.gopanic
- runtime.deferreturn
- runtime.gorecover
gopanicGoroutinedeferreflectcallrecovergorecover
func gorecover(argp uintptr) interface{} {
gp := getg()
p := gp._panic
if p != nil && !p.recovered && argp == uintptr(p.argp) {
p.recovered = true
return p.arg
}
return nil
}
recoveredpanicrecoverpanicgopanic
func gopanic(e interface{}) {
...
for {
// defer...
...
pc := d.pc
sp := unsafe.Pointer(d.sp) // must be pointer so it gets adjusted during stack copy
freedefer(d)
// recover...
if p.recovered {
atomic.Xadd(&runningPanicDefers, -1)
gp._panic = p.link
for gp._panic != nil && gp._panic.aborted {
gp._panic = gp._panic.link
}
if gp._panic == nil {
gp.sig = 0
}
gp.sigcode0 = uintptr(sp)
gp.sigcode1 = pc
mcall(recovery)
throw("recovery failed")
}
}
...
}
gopanicrecover
_panicrecover_panicpanicpanicrecoverygprecovery
recovery
func recovery(gp *g) {
sp := gp.sigcode0
pc := gp.sigcode1
if sp != 0 && (sp < gp.stack.lo || gp.stack.hi < sp) {
print("recover: ", hex(sp), " not in [", hex(gp.stack.lo), ", ", hex(gp.stack.hi), "]\n")
throw("bad recovery")
}
gp.sched.sp = sp
gp.sched.pc = pc
gp.sched.lr = 0
gp.sched.ret = 1
gogo(&gp.sched)
}
gopanicrecoverysppcdeferdeferprocgogodeferproc
gp.sched.ret = 1
gp.sched.retdeferprocdeferprocdefer
gogo
// void gogo(Gobuf*)
// restore state from Gobuf; longjmp
TEXT runtime·gogo(SB),NOSPLIT,$8-4
MOVW buf+0(FP), R1
MOVW gobuf_g(R1), R0
BL setg<>(SB)
MOVW gobuf_sp(R1), R13 // restore SP==R13
MOVW gobuf_lr(R1), LR
MOVW gobuf_ret(R1), R0
MOVW gobuf_ctxt(R1), R7
MOVW $0, R11
MOVW R11, gobuf_sp(R1) // clear to help garbage collector
MOVW R11, gobuf_ret(R1)
MOVW R11, gobuf_lr(R1)
MOVW R11, gobuf_ctxt(R1)
MOVW gobuf_pc(R1), R11
CMP R11, R11 // set condition codes for == test, needed by stack split
B (R11)
GobufGoroutine(g)Gobuf
type gobuf struct {
sp uintptr
pc uintptr
g guintptr
ctxt unsafe.Pointer
ret sys.Uintreg
lr uintptr
bp uintptr
}
Goroutine
拓展
const(
OPANIC // panic(Left)
ORECOVER // recover()
...
)
...
func walkexpr(n *Node, init *Nodes) *Node {
...
switch n.Op {
default:
Dump("walk", n)
Fatalf("walkexpr: switch 1 unknown op %+S", n)
case ONONAME, OINDREGSP, OEMPTY, OGETG:
case OTYPE, ONAME, OLITERAL:
...
case OPANIC:
n = mkcall("gopanic", nil, init, n.Left)
case ORECOVER:
n = mkcall("gorecover", n.Type, init, nod(OADDR, nodfp, nil))
...
}
panicrecover
总结
panicrecover
defer
在最后,现在的你可以回答这几个思考题了吗?说出来了才是真的懂