panic
panic è una funzione built-in di Go. Quando si incontrano errori non recuperabili, il programma spesso lancia un panic, come il comune accesso a puntatore nullo:
func main() {
var a *int
*a = 1
}Eseguendo il codice sopra, il programma lancerà il seguente panic e si fermerà:
panic: runtime error: invalid memory address or nil pointer dereferenceIn alcuni casi, chiamiamo manualmente la funzione panic per far uscire il programma, evitando così conseguenze più gravi. Spesso usiamo un'altra funzione built-in recover per catturare panic, in combinazione con defer.
func main() {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
var a *int
*a = 1
}Perché la funzione recover deve essere usata dentro defer, e cosa fa esattamente recover? Queste domande troveranno risposta nei contenuti seguenti.
Struttura
panic ha anche una struttura corrispondente a runtime, ovvero runtime._panic. La sua struttura non è complessa:
type _panic struct {
argp unsafe.Pointer // puntatore agli argomenti della chiamata defer eseguita durante panic; non può essere spostato - noto a liblink
arg any // argomento del panic
link *_panic // collegamento al panic precedente
pc uintptr // dove tornare a runtime se questo panic viene bypassato
sp unsafe.Pointer // dove tornare a runtime se questo panic viene bypassato
recovered bool // se questo panic è terminato
aborted bool // se questo panic è stato interrotto
goexit bool
}La sua struttura è molto simile a defer:
linkpunta alla prossima struttura_panicpcesppuntano al contesto di esecuzione della funzione chiamante per il recupero futuroargè il parametro della funzionepanicargppunta al parametro didefer; quando si verificapanic, triggera l'esecuzione dideferabortedindica se è stato interrotto
panic, come defer, esiste come lista concatenata nella goroutine:
type g struct {
_panic *_panic // panic più interno - offset noto a liblink
_defer *_defer // defer più interno
}
Panico
Sia che chiamiamo attivamente la funzione panic, sia che il programma verifichi un panic, alla fine si entra nella funzione runtime.gopanic:
func gopanic(e any)All'inizio, controlla prima se il parametro è nil. Se è nil, crea un errore di tipo runtime.PanicNilError:
if e == nil {
if debug.panicnil.Load() != 1 {
e = new(PanicNilError)
} else {
panicnil.IncNonDefault()
}
}Poi aggiunge il panic corrente alla testa della lista della goroutine:
var p _panic
p.arg = e
p.link = gp._panic
gp._panic = (*_panic)(noescape(unsafe.Pointer(&p)))Successivamente entra nel ciclo for per iniziare a elaborare uno per uno la lista defer della goroutine corrente:
for {
d := gp._defer
if d == nil {
break
}
if d.started {
if d._panic != nil {
d._panic.aborted = true
}
d._panic = nil
}
d.started = true
d._panic = (*_panic)(noescape(unsafe.Pointer(&p)))
...
}Se il defer corrente è già stato triggerato da un altro panic, cioè _defer.started == true, allora il panic precedente non verrà eseguito. Poi esegue la funzione corrispondente a defer:
p.argp = unsafe.Pointer(getargp())
d.fn()
p.argp = nil
d._panic = nil
d.fn = nil
gp._defer = d.link
freedefer(d)Dopo l'esecuzione, ricicla la struttura defer corrente e continua con il prossimo defer. Quando tutti i defer sono stati eseguiti e nessuno è stato recuperato, si entra nella funzione runtime.fatalpanic, che è unrecoverable, cioè non recuperabile:
func fatalpanic(msgs *_panic) {
pc := getcallerpc()
sp := getcallersp()
gp := getg()
var docrash bool
systemstack(func() {
if startpanic_m() && msgs != nil {
runningPanicDefers.Add(-1)
printpanics(msgs)
}
docrash = dopanic_m(gp, pc, sp)
})
if docrash {
crash()
}
systemstack(func() {
exit(2)
})
*(*int)(nil) = 0 // non raggiunto
}Durante questo processo, printpanics stampa le informazioni sul panic. Le informazioni sullo stack di chiamate che vediamo di solito sono output da questa funzione. Infine, la funzione runtime.exit esce dal programma tramite la system call _ExitProcess.
Recupero
Chiamando la funzione built-in recover, durante la compilazione si trasforma in una chiamata alla funzione runtime.gorecover:
func gorecover(argp uintptr) any {
gp := getg()
p := gp._panic
if p != nil && !p.goexit && !p.recovered && argp == uintptr(p.argp) {
p.recovered = true
return p.arg
}
return nil
}La sua implementazione è molto semplice, fa solo una cosa: p.recovered = true. Il codice che gestisce effettivamente la logica di recupero è nella funzione gopanic:
for {
...
d.fn()
...
if p.recovered {
...
}
}La logica di recupero è dopo l'esecuzione di defer. A questo punto si capisce perché la funzione recover può essere usata solo in defer. Se si usa recover al di fuori di defer, gp._panic sarebbe nil, e naturalmente p.recovered non verrebbe impostato a true, quindi nella funzione gopanic non si entrerebbe nella logica di recupero.
if p.recovered {
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")
}Durante il recupero, si puliscono dalla lista concatenata quei panic che sono stati interrotti. Poi si entra nella funzione runtime.recovery, e runtime.gogo torna al normale flusso logico della funzione utente:
func recovery(gp *g) {
// Informazioni sul defer passate nella struttura G.
sp := gp.sigcode0
pc := gp.sigcode1
gp.sched.sp = sp
gp.sched.pc = pc
gp.sched.lr = 0
gp.sched.ret = 1
gogo(&gp.sched)
}Un punto importante da notare è questa riga di codice:
gp.sched.ret = 1Imposta il valore ret a 1. Dai commenti della funzione runtime.deferproc si può vedere:
func deferproc(fn func()) {
...
// deferproc restituisce 0 normalmente.
// una funzione defer che ferma un panic
// fa restituire 1 a deferproc.
// il codice generato dal compilatore controlla sempre
// il valore di ritorno e salta alla
// fine della funzione se deferproc restituisce != 0.
return0()
}Il codice intermedio generato dal compilatore controlla se questo valore è 1. Se lo è, esegue direttamente la funzione runtime.deferreturn. Di solito questa funzione viene eseguita solo prima del ritorno della funzione, il che spiega perché dopo recover la funzione ritorna direttamente.
