Meandering Trajectory

panic과 recover 본문

컴퓨터/GoLang

panic과 recover

latentis 2019. 2. 3. 13:12

panic

Go에서 에러 처리는 반환값을 통해 이루어지는 것이 일반적이다. 하지만 프로그램 수행을 더 이상 진행할 수 없을 정도의 심각한 에러가 발생한 경우 Go 런타임은 panic을 발생시킨다. 예를 들어 아래 프로그램은 고의로 "index out of range" 예외를 일으키고 있는데

package main

import (
    "fmt"
)

func fn() {
    a := []int{1,2,3};
    fmt.Println(a[3]);
}

func main() {
    fn()   
}

이때 Go 프로그램은 panic을 발생시키고 그 결과는 다음과 같다.

panic: runtime error: index out of range

goroutine 1 [running]:
main.fn()
	/tmp/sandbox410484699/main.go:9 +0x20
main.main()
	/tmp/sandbox410484699/main.go:13 +0x20

panic에 대한 별도의 처리가 없는 프로그램에서는 panic이 발생하면 해당 에러가 발생한 위치[각주:1]를 보여준 뒤 프로그램이 종료된다.

또 panic을 강제로 일으킬 수도 있다.

panic("더 이상의 진행은 무리야!")

panic을 호출할 때 임의의 문자열을 파라미터로 사용할 수 있다.[각주:2]

recover

하지만 panic이 발생한다고 하더라도 이렇게 대책없이 프로그램이 죽는 것은 프로그래머가 원하는 바가 아닐 수 있다. 이를테면 프로그래머는 그때까지 프로그램이 수행하던 중간 결과를 저장한 뒤 프로그램이 종료되기를 바랄 수 있다. 혹은 해당 프로그램이 서버 프로그램이라 어떻게든 계속 서비스를 하는 것이 중요하다면 panic이 발생했을 때 적절한 정리 작업을 한 뒤 계속 실행되기를 원할 수 있다.

이런 용도로 사용하는 것이 바로 recover 함수다. 아래 예를 보자

package main

import (
    "fmt"
)

func fn() {

    defer func() {
        if e := recover(); e != nil {
            fmt.Println(e);
        }
    }()


    a := []int{1,2,3};
    fmt.Println(a[3]);
}

func main() {
    fn()   
}

파란색 글씨로 표시된 코드가 추가됐다. panic이 발생하면 Go 프로램은 호출 스택을 정리(stack unwinding)하고 종료되는데 recover 함수가 호출되는 순간 이 정리작업(stack unwinding)이 중단[각주:3]된다. 따라서 종료되기 전 추가 작업을 할 수 있게 된다.

위 예제에서는 단순히 panic의 파라미터로 전달된 문자열을 출력하는 일만 하고 있지만, 프로그램이 종료되기 전에 중간 결과를 저장하고 싶다면 코드를 다음과 같이 수정하면 된다.

defer func() {
   if e := recover(); e != nil {
       fmt.Println(e);

// 다음 함수 안에는 현재까지의 실행 결과를 저장하는 코드가 들어 있음.
saveCurrentResult();
   }
 
}()

한가지 주의할 점이 있는데 그것은 recover 함수는 정상적인 상황에서 호출되면 아무 일도 하지않고 nil을 반환한다는 것이다. 따라서 recover를 그냥 호출하는 것은 아무 의미가 없다. 반드시 defer와 함께 호출하여 stack unwinding이 발생하는 도중 실행되도록 해야[각주:4] 한다.

위 예제 코드에서 "index out of range" 에러가 발생하는

fmt.Println(a[3]);

이 코드가 실행되면

  • stack unwinding 과정에서 defer로 호출된 anonymous 함수의 내부에 있는 recover() 함수가 호출되고,
  • stack unwinding이 진행되므로 recover의 반환값은 nil이 아닌 문자열이 된다.
결과적으로 if 문 안에 있는

fmt.Println(e);

// 다음 함수 안에는 현재까지의 실행 결과를 저장하는 코드가 들어 있음.

saveCurrentResult();

panic 메시지를 출력하는 코드와 결과를 저장하는 함수가 호출된다.

정리

panic이 발생하는 두가지 상황

  1. 실행을 계속하기 어려운 예외적인 상황 (예: index out of range, segmentation fault)
  2. 사용자가 직접 panic 함수를 호출한 경우

panic 발생 시 프로그램의 기본 동작

  • 호출 경로(call trace) 출력 후 프로그램 종료

프로그램 종료를 바라지 않거나 종료 전 특별한 일을 하기를 바란다면?

  • 내부에서 recover를 호출하고 panic에 대한 예외처리를 하는 함수 작성
  • 적절한 위치에서 해당 함수를 defer 호출


  1. 단순히 에러가 발생한 라인만 보여주는 것이 아니라 호출 경로 전체를 출력한다. [본문으로]
  2. 에러도 파라미터로 사용하는 것이 가능하다. [본문으로]
  3. 이런 동작을 볼 때 panic/recover는 C++이나 Java이 try/catch와 유사하다. [본문으로]
  4. stack unwinding이 진행되면 호출 경로 상에 있는 각 함수별 defer 함수가 수행된다. [본문으로]

'컴퓨터 > GoLang' 카테고리의 다른 글

if 문의 조건절  (0) 2018.04.08
인터페이스 타입과 nil  (0) 2018.02.10
Go가 내 에러를 먹었어염! ㅠ.ㅠ  (0) 2017.12.02
Go 코드에서의 에러 처리  (0) 2017.11.18
new와 make  (0) 2017.11.12
Comments