0%

《Golang》Panic和Recover的实现原理

panic

在《Defer的实现原理》一文中已经发现了,panic会被编译器翻译成runtime.gopanic函数

下面看看它

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
func gopanic(e interface{}) {
gp := getg()
if gp.m.curg != gp {
print("panic: ")
printany(e)
print("\n")
throw("panic on system stack")
}

if gp.m.mallocing != 0 {
print("panic: ")
printany(e)
print("\n")
throw("panic during malloc")
}
if gp.m.preemptoff != "" {
print("panic: ")
printany(e)
print("\n")
print("preempt off reason: ")
print(gp.m.preemptoff)
print("\n")
throw("panic during preemptoff")
}
if gp.m.locks != 0 {
print("panic: ")
printany(e)
print("\n")
throw("panic holding locks")
}

var p _panic // 新建一个panic结构
p.arg = e // 赋值panic的参数
p.link = gp._panic // 跟defer一样,形成链表
gp._panic = (*_panic)(noescape(unsafe.Pointer(&p))) // panic赋值给当前g的_panic变量

atomic.Xadd(&runningPanicDefers, 1)

// By calculating getcallerpc/getcallersp here, we avoid scanning the
// gopanic frame (stack scanning is slow...)
addOneOpenDeferFrame(gp, getcallerpc(), unsafe.Pointer(getcallersp()))

for { // 开始遍历执行所有defer
d := gp._defer
if d == nil { // 一个defer都没有,直接退出循环
break
}

// If defer was started by earlier panic or Goexit (and, since we're back here, that triggered a new panic),
// take defer off list. An earlier panic will not continue running, but we will make sure below that an
// earlier Goexit does continue running.
if d.started {
if d._panic != nil {
d._panic.aborted = true
}
d._panic = nil
if !d.openDefer {
// For open-coded defers, we need to process the
// defer again, in case there are any other defers
// to call in the frame (not including the defer
// call that caused the panic).
d.fn = nil
gp._defer = d.link
freedefer(d)
continue
}
}

// Mark defer as started, but keep on list, so that traceback
// can find and update the defer's argument frame if stack growth
// or a garbage collection happens before reflectcall starts executing d.fn.
d.started = true

// Record the panic that is running the defer.
// If there is a new panic during the deferred call, that panic
// will find d in the list and will mark d._panic (this panic) aborted.
d._panic = (*_panic)(noescape(unsafe.Pointer(&p))) // 将panic存到defer结构中

done := true
if d.openDefer {
done = runOpenDeferFrame(gp, d)
if done && !d._panic.recovered {
addOneOpenDeferFrame(gp, 0, nil)
}
} else {
p.argp = unsafe.Pointer(getargp(0)) // 取出defer函数的参数
reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz)) // 执行defer函数
}
p.argp = nil

// reflectcall did not panic. Remove d.
if gp._defer != d {
throw("bad defer entry in panic")
}
d._panic = nil

// trigger shrinkage to test stack copy. See stack_test.go:TestStackPanic
//GC()

pc := d.pc // 保存pc,以备后面恢复
sp := unsafe.Pointer(d.sp) // must be pointer so it gets adjusted during stack copy 保存sp,以备后面恢复
if done {
d.fn = nil
gp._defer = d.link
freedefer(d)
}
if p.recovered { // 如果panic被defer中的recover捕获
gp._panic = p.link // 当前g的panic移向下一个panic,所以同一个panic只能被recover一次
if gp._panic != nil && gp._panic.goexit && gp._panic.aborted {
// A normal recover would bypass/abort the Goexit. Instead,
// we return to the processing loop of the Goexit.
gp.sigcode0 = uintptr(gp._panic.sp)
gp.sigcode1 = uintptr(gp._panic.pc)
mcall(recovery)
throw("bypassed recovery failed") // mcall should not return
}
atomic.Xadd(&runningPanicDefers, -1)

if done {
// Remove any remaining non-started, open-coded
// defer entries after a recover, since the
// corresponding defers will be executed normally
// (inline). Any such entry will become stale once
// we run the corresponding defers inline and exit
// the associated stack frame.
d := gp._defer
var prev *_defer
for d != nil {
if d.openDefer {
if d.started {
// This defer is started but we
// are in the middle of a
// defer-panic-recover inside of
// it, so don't remove it or any
// further defer entries
break
}
if prev == nil {
gp._defer = d.link
} else {
prev.link = d.link
}
newd := d.link
freedefer(d)
d = newd
} else {
prev = d
d = d.link
}
}
}

gp._panic = p.link
// Aborted panics are marked but remain on the g.panic list.
// Remove them from the list.
for gp._panic != nil && gp._panic.aborted {
gp._panic = gp._panic.link
}
if gp._panic == nil { // must be done with signal
gp.sig = 0
}
// Pass information about recovering frame to recovery.
gp.sigcode0 = uintptr(sp)
gp.sigcode1 = pc
mcall(recovery) // 调用recovery函数,跳到defer对应的runtime.deferprocStack的后一行执行,发现返回值是1,接着走向deferreturn完成所有defer的执行
throw("recovery failed") // mcall should not return
}
}

// ran out of deferred calls - old-school panic now
// Because it is unsafe to call arbitrary user code after freezing
// the world, we call preprintpanics to invoke all necessary Error
// and String methods to prepare the panic strings before startpanic.
preprintpanics(gp._panic) // 所有defer运行完之后, 准备panic的打印信息

fatalpanic(gp._panic) // should not return 抛出panic并打印panic,然后异常退出进程
*(*int)(nil) = 0 // not reached
}

panic跟defer一样,形成了一个链表,当panic发生的时候,所有defer会被遍历一次执行

如果发现某个defer中存在recover捕获,则跳出循环,直接进入deferreturn执行,完成剩余未执行的所有defer递归调用,然后结束函数的执行。

如果没有任何recover,则所有defer会被遍历执行,执行完后抛出panic并打印panic,然后异常退出进程

recover

接着看看recover,recover被翻译成了runtime.gorecover函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func gorecover(argp uintptr) interface{} {
// Must be in a function running as part of a deferred call during the panic.
// Must be called from the topmost function of the call
// (the function used in the defer statement).
// p.argp is the argument pointer of that topmost deferred function call.
// Compare against argp reported by caller.
// If they match, the caller is the one who can recover.
gp := getg()
p := gp._panic // 获取当前的panic,需要捕获这个panic
if p != nil && !p.goexit && !p.recovered && argp == uintptr(p.argp) {
p.recovered = true // p标记为已被捕获,gopanic函数中就会进行相应的处理,
return p.arg // 返回panic发生时,传给panic的参数
}
return nil
}

这个函数做的事情很简单,仅仅是标记panic被捕获,相应的处理在gopanic函数中

总结

  1. 如果发生panic,且被recover,则recover前面的所有defer会在panic中执行完,后面的所有defer在recover之后执行完。就是说不管是否panic,所有的defer都会执行
  2. panic后面的代码不能得到执行
  3. g中panic如果没有被recover,会导致整个进程异常退出,其他任何地方都得不到执行机会了
  4. 父g不能recover子g的panic,他们是独立的(panic链表、defer链表都是在g结构体下的),实际上他们是平等的,称不上父子关系,这里的父子表达的是启动关系

下篇预告

逃逸分析




微信关注我,及时接收最新技术文章