Panic source code interpretation
Preface
This article is ingo version go1.13.15 darwin/amd64
On
The role of panic
-
panic
Can change the control flow of the program and call itpanic
The remaining code of the current function will be stopped immediately and will be in the current function.Goroutine
The caller's recursive executiondefer
; -
recover
Can be abortedpanic
The program crash caused. It's only indefer
function that plays a role, invoked in other scopes will not play a role;
Give a chestnut
package main import "fmt" func main() { (1) func() { (2) panic("3") }() (4) }
Output
1
2
panic: 3goroutine 1 [running]:
.func1(...)
/Users/yj/Go/src/Go-POINT/panic/:9
()
/Users/yj/Go/src/Go-POINT/panic/:10 +0xee
panic
The remaining code of the current function will be stopped immediately, so 4 is not printed out
For recover
- panic will only trigger the defer of the current Goroutine;
- Recover will only take effect if it is called in defer;
- panic allows nesting multiple calls in defer;
package main import ( "fmt" "time" ) func main() { (1) defer func() { if err := recover(); err != nil { (err) } }() go func() { (2) panic("3") }() () (4) }
The chestnuts above, becauserecover
andpanic
Not in the samegoroutine
so it won't be captured
Nested demos
func main() { defer ("in main") defer func() { defer func() { panic("3 panic again and again") }() panic("2 panic again") }() panic("1 panic once") }
Output
in main
panic: 1 panic once
panic: 2 panic again
panic: 3 panic again and againgoroutine 1 [running]:
...
Multiple callspanic
It won't affect it eitherdefer
The function is executed normally, so usedefer
It is generally safe to do the finishing work.
Panic usage scenario
- error: Foreseeable error
- panic: Unforeseen exception
It should be noted that you should use it as much as possibleerror
, not usingpanic
andrecover
. Only when the program cannot continue running, it should be usedpanic
andrecover
mechanism.
panic
There are two reasonable use cases.
1. An error that cannot be restored occurred, and the program cannot continue to run at this time. An example is that the web server cannot bind the required port. In this case, panic should be used because if the port cannot be bound, nothing can be done.
2. A programming error occurred. Suppose we have a method that receives pointer parameters and others call it using nil as the parameter. In this case, we can use panic because this is a programming error: a method that can only receive legal pointers is called with the nil parameter.
In general, we should not report ordinary errors by calling panic functions, but should only use it as a way to report fatal errors. When certain scenarios that should not happen, we should call panic.
To summarizepanic
Use scenarios:
1. Null pointer reference
2. Subscript crosses the boundary
3. Divider is 0
4. Branches that should not appear, such as default
5. The input should not cause function errors
Check out the implementation
Let's take a look first_panic
Structure of
// _panic saves an active panic// // This marks go:notinheap because the value of _panic must be on the stack// // The argp and link fields are stack pointers, but do not need special processing when the stack grows: because they are pointer types and// The _panic value is only on the stack, and normal stack pointer adjustments will handle them.// //go:notinheap type _panic struct { argp // Pointer to defer call parameter during panic; cannot be moved - liblink known arg interface{} // Panic parameters link *_panic // link link to earlier panic recovered bool // Is the panic ending aborted bool // Is the panic ignored}
link
Point to save ingoroutine
Previous in the linked listpanic
Link List
gopanic
The compiler willpanic
Disguise itgopanic
, see the execution process:
1. Create a new oneruntime._panic
and add to the locationGoroutine
of_panic
The front of the linked list;
2. Continuously from the current Goroutine in the loop_defer
Get the linked listruntime._defer
And callRun delayed call function;
3. CallAbort the entire procedure;
// Implementation of predeclared function panicfunc gopanic(e interface{}) { gp := getg() // Determine whether it is on the system stack or on the user stack // If executed on the system or signal stack, getg() will return g0 or gsignal of the current m // Therefore, you can use == gp to determine the stack // The panic on the system stack cannot be restored if != gp { print("panic: ") printany(e) print("\n") throw("panic on system stack") } // If panic occurs while malloc is in progress, it cannot be restored if != 0 { print("panic: ") printany(e) print("\n") throw("panic during malloc") } // Panic cannot be restored when preemption is prohibited if != "" { print("panic: ") printany(e) print("\n") print("preempt off reason: ") print() print("\n") throw("panic during preemptoff") } // Panic cannot be restored when g is locked on m if != 0 { print("panic: ") printany(e) print("\n") throw("panic holding locks") } // Here is what can be recovered var p _panic = e // panic saves the corresponding message and points to the previous panic link saved in the goroutine link list = gp._panic gp._panic = (*_panic)(noescape((&p))) (&runningPanicDefers, 1) for { // Start taking the defer call of the current goroutine one by one d := gp._defer // No defer, exit the loop if d == nil { break } // If defer started with an early panic or Goexit (and, because we go back here, this triggered a new panic), // Take defer out of the linked list. Earlier panic or Goexit will not continue to run. if { if d._panic != nil { d._panic.aborted = true } d._panic = nil = nil gp._defer = freedefer(d) continue } // Mark deferred as started // If stack growth or garbage collection occurs before reflectcall starts execution // Mark the defer has started execution, but it is still saved in the list, so traceback can find and update the parameter frame of this defer // Tag whether defer has been executed = true // Record the running delayed panic. // If there is a new panic during the delayed call, then this panic // D will be found in the list and the tag d._panic (this panic) will abort. d._panic = (*_panic)(noescape((&p))) = (getargp(0)) reflectcall(nil, (), deferArgs(d), uint32(), uint32()) = nil // reflectcall has no panic. Delete d if gp._defer != d { throw("bad defer entry in panic") } d._panic = nil = nil gp._defer = // trigger shrinkage to test stack copy. See stack_test.go:TestStackPanic //GC() pc := sp := () // must be pointer so it gets adjusted during stack copy freedefer(d) if { (&runningPanicDefers, -1) gp._panic = // Ignored panic will be marked, but it remains in the list // Move them out of the list here for gp._panic != nil && gp._panic.aborted { gp._panic = gp._panic.link } if gp._panic == nil { // Must be done by signal = 0 } // Pass information about recovery frames gp.sigcode0 = uintptr(sp) gp.sigcode1 = pc // Call recovery and re-enter the scheduling loop and no longer return mcall(recovery) // If you cannot re-enter the scheduling loop, the error cannot be restored throw("recovery failed") // mcall should not return } } // Consume all defer calls and panic conservatively // Because it is not safe to call any user code after freezing, we call preprintpanics to call // All necessary Error and String methods to prepare panic strings before startpanic. preprintpanics(gp._panic) fatalpanic(gp._panic) // Should not return *(*int)(nil) = 0 // Unable to reach} // reflectcall calls fn with a copy of n parameter bytes pointed to by arg.// After fn returns, reflectcall copies the n-retoffset result bytes back to arg+retoffset before returning.// If the result byte is re-copyed, the caller should pass the parameter frame type as argtype so that the call can perform appropriate write barriers during copying.// reflect Packet passes frame type. In the runtime package, only one call copies the result back, i.e. cgocallbackg1,// And it does not pass the frame type, which means there is no call write barrier. See the page for the call for reasons.// // Package reflect access to this symbol via linknamefunc reflectcall(argtype *_type, fn, arg , argsize uint32, retoffset uint32)
Clarify the process
1. Under processingpanic
During this period, we will first determine the current situationpanic
Type, determinepanic
Whether it can be recovered;
- Panic on the system stack cannot be restored
- If panic occurs while malloc is in progress, it cannot be restored
- Panic cannot be restored when preemption is prohibited
- Panic cannot be restored when g is locked on m
2. Recoverablepanic
,panic
oflink
Point togoroutine
Previous in the linked listpanic
Link list;
3. Loop to get the current one by onegoroutine
ofdefer
Call;
- If the defer starts with early panic or Goexit, then the defer is taken away from the linked list, and earlier panic or Goexit will not be able to continue running, that is, the previous panic is terminated and aborted is set to true. When performing recovery below, goexit will not be cancelled;
- Recovered will be marked in gorecover, see below. When recovered is marked true, the recovery function triggers the scheduling of Goroutine, and the sp, pc and function return values will be prepared before the scheduling;
- When the delay function
recover
Have apanic
When , 1 will be returned, whenWhen the return value of the function is 1, the code generated by the compiler will jump directly to the caller's function before returning and execute it.
, jump to
After the function, the program has already
panic
The normal logic has been restored. andFunctions can also be from
runtime._panic
The call is retrieved from the structurepanic
Incomingarg
The parameter is returned to the caller.
// Expand the stack after the defer function calls recovery after panic occurs. Then arrange to continue running.// Just like the caller of the defer function returns normally.func recovery(gp *g) { // Info about defer passed in G struct. sp := gp.sigcode0 pc := gp.sigcode1 // d's arguments need to be in the stack. if sp != 0 && (sp < || < sp) { print("recover: ", hex(sp), " not in [", hex(), ", ", hex(), "]\n") throw("bad recovery") } // Make deferproc return for this d // Return to 1 at this time. Calling the function will jump to the standard return end = sp = pc = 0 = 1 gogo(&) }
existrecovery
In the function, useg
Two status codes in the backtracking stack pointersp
and restore the program counterpc
Go to the scheduler and callgogo
Rescheduleg
,Willg
Restore to callrecover
The location of the function,goroutine
Continue to execute,recovery
During the scheduling process, the return value of the function will be set to 1. Calling the function will jump to the standard return end.
func deferproc(siz int32, fn *funcval) { // arguments of fn follow fn ... // deferproc returns 0 normally. // a deferred func that stops a panic // makes the deferproc return 1. // the code the compiler generates always // checks the return value and jumps to the // end of the function if deferproc returns != 0. return0() // No code can go here - the C return register has // been set and must not be clobbered. }
When the delay functionrecover
Have apanic
When , 1 will be returned, whenWhen the return value of the function is 1, the code generated by the compiler will jump directly to the caller's function before returning and execute it.
, jump to
After the function, the program has already
panic
The normal logic has been restored. andFunctions can also be from
runtime._panic
The call is retrieved from the structurepanic
Incomingarg
The parameter is returned to the caller.
gorecover
The compiler willrecover
Disguise itgorecover
ifrecover
Been executed correctly, that isgorecover
,Sorecovered
Will be marked true
// go/src/runtime/ // Execute predeclared function recovery.// Segmented stacks are not allowed because it requires reliably finding the stack segments of its caller.// // TODO(rsc): Once we commit to CopyStackAlways, // this doesn't need to be nosplit. //go:nosplit func gorecover(argp uintptr) interface{} { // Must be run in the function during panic as part of the defer call. // Must be called from the topmost function (the function used in the defer statement). // is the parameter pointer of the top-level defer function call. // Compare the argp reported by the caller, and if it matches, the caller can recover. gp := getg() p := gp._panic if p != nil && ! && argp == uintptr() { // Tag recovered = true return } return nil }
Under normal circumstances, it will modifyruntime._panic
ofrecovered
Field,The function does not contain the logic of restoring the program, the recovery of the program is from
Functions are responsible.
gorecover
Willrecovered
Mark true, thengopanic
You can passmcall
Callrecovery
and re-enter the scheduling loop
fatalpanic
Implements a program that cannot be restored to crash, which will pass before aborting the program
Print out all
panic
Message and parameters passed in when calling:
// go/src/runtime/ // fatalpanic implements irrecoverable panic. Similar to fatalthrow,// If msgs != nil, fatalpanic is still able to print panic's message// and reduce runningPanicDeferss when main exits// //go:nosplit func fatalpanic(msgs *_panic) { // Return to the program count register pointer pc := getcallerpc() // Return to the stack pointer sp := getcallersp() // Return to the current G gp := getg() var docrash bool // Switch to the system stack to avoid stack growth, which can lead to worse things if the runtime state is poor systemstack(func() { if startpanic_m() && msgs != nil { // If there are panic messages and startpanic_m, you can try printing them // Setting startpanic_m will block the exit of main. // So now we can start reducing runningPanicDefers (&runningPanicDefers, -1) printpanics(msgs) } docrash = dopanic_m(gp, pc, sp) }) if docrash { // By crashing outside of the above systemstack call, the debugger will not be confused when generating backtracks. // Function crashes are marked nosplit to avoid stack growth. crash() } // Launch from the system systemstack(func() { exit(2) }) *(*int)(nil) = 0 // not reached } // Print out the currently active panicfunc printpanics(p *_panic) { if != nil { printpanics() print("\t") } print("panic: ") printany() if { print(" [recovered]") } print("\n") }
Summarize
A summary from [panic and recover]
1. The compiler will be responsible for converting keywords;
1.panic
andrecover
Convert toand
;
2.defer
Convert tofunction;
3. Callingdefer
Called at the end of the functionfunction;
2. Encounter during operationWhen the method is
Goroutine
The linked list is taken out in turnruntime._defer
structure and execute;
3. If you encounter a delayed execution function when callingWill
_panic.recovered
Mark astrue
And returnpanic
parameters;
1. After this call is over,Will be from
runtime._defer
Extract program counter from structurepc
and stack pointersp
And callFunctions are restored;
2、Will be passed in according to
pc
andsp
Jump back;
3. The code generated by the compiler will be foundThe return value is not
0
, it will jump backand restore to the normal execution process;
4. If not encounteredIt will go through all
runtime._defer
and call it at the endAbort the program, print
panic
parameters and return error code2
;
refer to
【panic and recover】/golang/docs/part2-foundation/ch05-keyword/golang-panic-recover/
【Panishment and Recovery Built-in Functions】/under-the-hood/zh-cn/part1basic/ch03lang/panic/
【Implementation of Go language panic/recover】/p/72779197
【panic and recover】/golang/di-6-ke-chang-yong-guan-jian-zi/panic-and-recover
【After looking through the source code, I thoroughly figured out panic and recover】/p/763bfbd4ed8c
This is the end of this article about the detailed explanation of the panic source code interpretation in Go. For more related panic source code content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!