SoFunction
Updated on 2025-03-03

Detailed explanation of panic source code in go

Panic source code interpretation

Preface

This article is ingo version go1.13.15 darwin/amd64On

The role of panic

  • panicCan change the control flow of the program and call itpanicThe remaining code of the current function will be stopped immediately and will be in the current function.GoroutineThe caller's recursive executiondefer
  • recoverCan be abortedpanicThe program crash caused. It's only indeferfunction 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: 3

goroutine 1 [running]:
.func1(...)
        /Users/yj/Go/src/Go-POINT/panic/:9
()
        /Users/yj/Go/src/Go-POINT/panic/:10 +0xee

panicThe 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, becauserecoverandpanicNot in the samegoroutineso 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 again

goroutine 1 [running]:
...

Multiple callspanicIt won't affect it eitherdeferThe function is executed normally, so usedeferIt 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 usingpanicandrecover. Only when the program cannot continue running, it should be usedpanicandrecovermechanism.

panicThere 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 summarizepanicUse 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_panicStructure 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}

linkPoint to save ingoroutinePrevious in the linked listpanicLink List

gopanic

The compiler willpanicDisguise itgopanic, see the execution process:

1. Create a new oneruntime._panicand add to the locationGoroutineof_panicThe front of the linked list;

2. Continuously from the current Goroutine in the loop_deferGet the linked listruntime._deferAnd 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 processingpanicDuring this period, we will first determine the current situationpanicType, determinepanicWhether 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. RecoverablepanicpanicoflinkPoint togoroutinePrevious in the linked listpanicLink list;

3. Loop to get the current one by onegoroutineofdeferCall;

  • 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 functionrecoverHave apanicWhen , 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 toAfter the function, the program has alreadypanicThe normal logic has been restored. andFunctions can also be fromruntime._panicThe call is retrieved from the structurepanicIncomingargThe 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(&)
}

existrecoveryIn the function, usegTwo status codes in the backtracking stack pointerspand restore the program counterpcGo to the scheduler and callgogoRescheduleg,WillgRestore to callrecoverThe location of the function,goroutineContinue to execute,recoveryDuring 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 functionrecoverHave apanicWhen , 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 toAfter the function, the program has alreadypanicThe normal logic has been restored. andFunctions can also be fromruntime._panicThe call is retrieved from the structurepanicIncomingargThe parameter is returned to the caller.

gorecover

The compiler willrecoverDisguise itgorecover

ifrecoverBeen executed correctly, that isgorecover,SorecoveredWill 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._panicofrecoveredField,The function does not contain the logic of restoring the program, the recovery of the program is fromFunctions are responsible.

gorecoverWillrecoveredMark true, thengopanicYou can passmcallCallrecoveryand re-enter the scheduling loop

fatalpanic

Implements a program that cannot be restored to crash, which will pass before aborting the programPrint out allpanicMessage 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.panicandrecoverConvert toand

2.deferConvert tofunction;

3. CallingdeferCalled at the end of the functionfunction;

2. Encounter during operationWhen the method isGoroutineThe linked list is taken out in turnruntime._deferstructure and execute;

3. If you encounter a delayed execution function when callingWill_panic.recoveredMark astrueAnd returnpanicparameters;

1. After this call is over,Will be fromruntime._deferExtract program counter from structurepcand stack pointerspAnd callFunctions are restored;

2、Will be passed in according topcandspJump back

3. The code generated by the compiler will be foundThe return value is not0, it will jump backand restore to the normal execution process;

4. If not encounteredIt will go through allruntime._deferand call it at the endAbort the program, printpanicparameters 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!