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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
|
// The implementation of the predeclared function panic.
func gopanic(e any) {
// 获取当前正在运行的goroutine
gp := getg()
// 调用gopanic()应该是在用户栈上发生的
if gp.m.curg != gp {
print("panic: ")
printany(e)
print("\n")
// panic 在系统栈上
throw("panic on system stack")
}
// 申请内存期间不允许panic
if gp.m.mallocing != 0 {
print("panic: ")
printany(e)
print("\n")
// malloc 期间 panic
// malloc 是在申请内存期间
throw("panic during malloc")
}
// 抢占期间不允许panic
if gp.m.preemptoff != "" {
print("panic: ")
printany(e)
print("\n")
print("preempt off reason: ")
print(gp.m.preemptoff)
print("\n")
throw("panic during preemptoff")
}
// M持有锁期间不允许panic
if gp.m.locks != 0 {
print("panic: ")
printany(e)
print("\n")
throw("panic holding locks")
}
// 1) 创建一个_panic结构并初始化
// 可以看出panic和defer结构都是用的用户内存空间
// 初始化一个panic结构体,在栈上分配
var p _panic
p.arg = e // 保存panic的参数,该参数是panic(v any)
// _panic通过link链接其他panic
// 记录goroutine上的最后一个panic,也就是形成一个panic的链表
p.link = gp._panic
// g._panic 保存的是最新的panic
// 特意使用noescape函数来避免p逃逸,应为panic本身就是与栈的状态强相关的
// 当前panic追加到goroutine的panic链表上,记录最新的一个panic
gp._panic = (*_panic)(noescape(unsafe.Pointer(&p)))
// 使用原子锁标记panic正在发生,用于runtime.main()函数等待
// 当runtime.main()即将要结束时,需要循环等待panic完成。
// 具体代码参看 runtime.main() 函数。
atomic.Xadd(&runningPanicDefers, 1)
// 2) open code defers 形式扫描栈,收集信息,已备后面循环gp.defer
// By calculating getcallerpc/getcallersp here, we avoid scanning the
// gopanic frame (stack scanning is slow...)
//
// 通过这里计算getcallerpc/getcallersp,我们避免扫描gopanic帧(堆栈扫描很慢......)
// 将最近的一个open coded defer栈帧添加到_defer链表中,可能是一组defer函数组成的一个_defer结构。
addOneOpenDeferFrame(gp, getcallerpc(), unsafe.Pointer(getcallersp()))
// 在这个循环中逐个调用链表中的defer函数,并检测recover的状态。
// 如果所有的defer函数都执行完后还是没有recover,则循环就会结束,
// 最后的fatalpanic()函数就会结束当前进程。
for {
// 取最新的一个defer
// 这里可以看看出取出的defer不一定是当前发生panic注册的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.
//
// d.started为真,表明当前是一个嵌套的panic,
// 也就是在原有panic或Goexit()函数执行defer函数的时候又触发了panic
// 因为触发panic的defer函数还没有执行完,所以还没有从链表中移除。
// 这里会把d相关的旧的_panic设置为aborted,然后把d从链表中移除,并通过freedefer()函数释放defer。
if d.started { // 首次触发panic这里为 false,多次触发这里为 true
// d._panic记录的是前一个panic,
// 在执行d.fn()函数的时候又发生了panic的情况。
if d._panic != nil {
// 把前一个panic标记为已终止状态
d._panic.aborted = true // 已终止
}
d._panic = nil
// 不是open defers形式时,则直接回收当前defer结构体即可,
// 因为当前gopanic()正是当前defer函数中触发,因此直接结束本次循环即可。
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).
//
// 当前defer已执行完,即将被回收。
d.fn = nil
gp._defer = d.link
// 因为当前defer中发生了panic,后续代码不会被执行,直接回收defer即可
freedefer(d)
continue
}
// 否则应该去runOpenDeferFrame回收
}
// 后续的3大块逻辑就是:调用defer函数、释放_defer结构和检测recover。
// 2.1) 调用defer函数
// 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 executing d.fn.
//
// 如果defer函数又触发了panic,新的panic遍历defer链表时,就能够通过started
// 的值确定确定该defer函数已经被调用过了,避免重复调用。
d.started = true // 标记当前defer被panic触发
// 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赋值,将d关联到当前panic对象p,
// 使用noescape函数避免p逃逸,这一步是为了后续嵌套的panic能够通过d._panic找到上一个panic
d._panic = (*_panic)(noescape(unsafe.Pointer(&p))) // 当前defer的_panic记录触发的panic是那个
done := true
if d.openDefer {
// 当前是open code defers模式时
// 运行当前defer的fn函数,done=true表示所有的defer已经运行完
// 通过返回值来判断目标栈帧上的open coded defer已经完全执行,并且没有recover,
// 就再次调用addOneOpenDeferFrame()函数把下一个open coded defer栈帧添加到_defer链表
done = runOpenDeferFrame(gp, d) // 这里运行的是一组defer
// 因为上面运行的d.fn()函数,可能其中存在recover()函数,所以要判断recovered字段
if done && !d._panic.recovered {
// done=true,并且当前这个panic并没有被恢复时
// 再去寻找后面函数的defer
// 从调用栈的栈顶开始回溯扫描,直到找到一个带有open coded defer的栈帧,
// 为该栈帧分配一个_defer结构,为各个字段赋值后添加到defer链表中合适的位置。
// 不管目标栈帧上有几个open coded defer函数,只分配一个_defer结构,
// 因为后续通过runOpenDeferFrame()函数来执行的时候,会一并执行栈帧上的所有
// open coded defer函数。添加到_defer链表中的位置是根据目标栈帧在调用栈中的位置
// 计算的,而不是添加到头部。
addOneOpenDeferFrame(gp, 0, nil)
}
} else {
// defer是堆或栈分布时
// getargp():设置为当前gopanic()函数栈帧上args to callee区间的起始地址,
// recover()函数通过这个值来判断自身是否直接被defer函数调用
p.argp = unsafe.Pointer(getargp()) // 该值在fn执行函数中用于recover函数比较判断
// 执行defer注册的函数,如果这里又发生了panic时则当前defer并未被清除,又会从新gopanic
d.fn()
}
// 为什么前面设置了argp值这里又要清除掉?
// 因此d.fn()执行的defer函数中可能会有recover()函数,需要判断argp的值
p.argp = nil // 清除
// 2.2) 释放_defer结构
// Deferred function did not panic. Remove d.
//
// 调用完d.fn()函数后,不应该会出现gp._defer不等于d这种情况。
// 假如在d.fn()函数执行的过程中没有造成新的panic,那么所有新注册的defer都应该在
// d.fn()函数返回的时候被deferreturn()函数移出链表。
// 假如d.fn()函数执行过程中造成了新的panic,若没有recover,则不会再回到这里,
// 若经recover之后再回到这里,则所有在d.fn()函数执行过程中注册的defer也
// 都应该在d.fn()函数返回之前被移除链表。
if gp._defer != d {
throw("bad defer entry in panic")
}
// 当前defer执行完需要把_panic标记为nil,后面会删除这个defer
d._panic = nil
// trigger shrinkage to test stack copy. See stack_test.go:TestStackPanic
//GC()
// 把pc和sp字段保存在局部变量中,供接下来检测执行recover时使用
// 此处额sp类型必须时指针,因为后续如果栈被移动,只有指针类型会得到更新
pc := d.pc // 要恢复的PC和SP的值
sp := unsafe.Pointer(d.sp) // must be pointer so it gets adjusted during stack copy
if done {
// done=true,这一组defer函数已执行完
// 因此defer函数可以被释放掉
d.fn = nil
gp._defer = d.link // 从goroutine._defer上移除自己
freedefer(d) // 回收defer
}
// 2.3)检测 recover() 是否标记了panic
// 如果 d.fn() 函数成功地执行了recover(),则当前_panic对象的p的recovered字段就会被设置为true
// 此处通过检测后就会执行recover逻辑。这里才是recover()函数的具体实现功能,跳转到指定代码处。
if p.recovered {
// 2.3.1) recover()生效
// 1. 移除当前_panic,因为当前_panic已被恢复
// 2. 向前寻找移除被标记为aborted为真的_panic
gp._panic = p.link // 把当前panic从goroutine._panic上移除掉自己
// gp._panic != nil:后面还有 panic
// gp._panic.goexit:由runtime.Goexit()函数触发
// gp._panic.aborted:当前panic已被终止
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.
//
// 正常恢复将 bypass/abort到 Goexit。 相反,我们返回到 Goexit 的处理循环。
gp.sigcode0 = uintptr(gp._panic.sp) // 并没有值
gp.sigcode1 = uintptr(gp._panic.pc)
// mcall函数从当前g栈切换到g0栈,在执行recovery函数,恢复后是接到从当前最新panic的处往下执行
// recovery函数判断栈溢出后,直接把sp和pc值赋值给当前goroutine的sched然后使用gogo函数再次被调度起来接到执行
mcall(recovery) // 这种情况应该报错
// 从这里跳转到deferreturn函数
throw("bypassed recovery failed") // mcall should not return 应该永远不会返回到这里
}
// 解锁panic标志,runtime.main()可以继续执行
atomic.Xadd(&runningPanicDefers, -1)
// After a recover, remove any remaining non-started,
// open-coded defer entries, 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. We only remove up to
// the first started (in-progress) open defer entry, not
// including the current frame, since any higher entries will
// be from a higher panic in progress, and will still be
// needed.
d := gp._defer
var prev *_defer
if !done {
// Skip our current frame, if not done. It is
// needed to complete any remaining defers in
// deferreturn()
//
// 跳过当前帧,如果还没有完成。需要在deferreturn()中完成所有剩余的延迟
// open coded defer时发生了recover时。
prev = d
d = d.link
}
for d != nil {
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
//
// 这个defer已经启动,但是我们正在它内部进行一个 defer-panic-recovery
// 所以不要删除它或任何进一步的defer条目
break
}
if d.openDefer {
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.
//
// 循环移除链表头部所有已经标记为aborted的_panic
// 这里可以看出当最后一个panic被恢复前面的所有已标记aborted中止的panic会被移除。
for gp._panic != nil && gp._panic.aborted {
gp._panic = gp._panic.link
}
// 如果没有发生panic,则此时gp._panic应该为nil,不为nil就表明发生了嵌套的panic
// 而且只是内层的panic被recover
if gp._panic == nil { // must be done with signal
gp.sig = 0
}
// Pass information about recovering frame to recovery.
// 要恢复的PC和SP的值,也就是当前defer注册的后一条指令处,是一条JMP指令
// 跳转去执行deferreturn()函数。
gp.sigcode0 = uintptr(sp) // 需要恢复到的SP地址,也就是注册defer函数的栈顶寄存器
gp.sigcode1 = pc // 需要恢复到IP地址,也就是注册defer函数的下一条指令处
// recovery函数负责用存储在sigcode0和sigcode1中的sp和pc恢复gp的执行状态
mcall(recovery) // mcall函数切换到系统g0栈去调用gogo函数执行跳转
throw("recovery failed") // mcall should not return
}
}
// 所有的defer注册函数都没有recover,最后会到这里去打印错误信息
// 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.
//
// gp._panic:保存的最新的panic
preprintpanics(gp._panic) // 把panic的参数解析放入arg
// 打印panic信息,并结束当前进程
fatalpanic(gp._panic) // should not return
// 向一个nil的地址写入值,会报错,下面这行代码是防止万一,不应该执行到这里在前一个函数中会结束调进程
*(*int)(nil) = 0 // not reached
}
|