[Go] panic recover

윤동환·2024년 1월 25일
0

Go

목록 보기
18/18
post-thumbnail

Go 에서는 예상치 못한 에러가 발생했을 때 panic이 발생합니다.
이 때 process가 종료되게되는데 panic시 프로세스가 종료되지 않도록 해주는 함수가 recover()입니다.

프로세스 강제 종료를 막기 위해 시도하였으나 process가 계속 죽어서 panic과 recover의 동작 방식을 조금 더 이해하기 위해 예시 코드 글을 작성합니다.

1. 단순 panic and recover

package main

import (
    "fmt"
)

func main() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered from panic in deferred func:", r)
        }
    }()

    fmt.Println("1")

    panic("Something went wrong")

    fmt.Println("3")

    defer func() {
        fmt.Println("5")
    }()
}

결과

1
Recovered from panic in deferred func: Something went wrong
프로세스 종료

panic이 발생한 시점 이후 코드인3과 defer인 5는 출력되지않았습니다.
원래 defer는 스택 구조로써 정상적이라면
1. fmt.Println("1")
2. fmt.Println("3")
3. (stack구조인 후입 선출)
fmt.Println("5")
4. (recover 조건 없이 출력이 된다면)
fmt.Println("Recovered from panic in deferred func:", r)
순서로 출력이 됐겠지만

panic 발생하여 1만 출력후 이미 스택에 있던 첫번째 defer만 실행이 되고 프로세스가 종료됩니다.

동작 예시 코드

func main() {
    defer func() {
    	fmt.Println("Recovered from panic in deferred func")
    }()

    fmt.Println("1")

    fmt.Println("3")

    defer func() {
        fmt.Println("5")
    }()
}

1
3
5
Recovered from panic in deferred func
프로세스 종료

2. 반복 panic and recover

func main() {
    for {
        time.Sleep(time.Second)
        defer func() {
            if r := recover(); r != nil {
                fmt.Println("Recovered from panic in deferred func:", r)
            }
        }()
        
        fmt.Println("1")
        
        panic("Something went wrong")
        
        fmt.Println("3")
        
        defer func() {
            fmt.Println("5")
        }()
    }
}

결과

(1초 sleep 후)
1
Recovered from panic in deferred func: Something went wrong
프로세스 종료

3. func으로 감싼 반복 panic and recover

2번에서 for문 안에서 panic이 발생하고 다시 recover를 하였는데 process가 종료할까요?

그 이유는 panic이 발생했을 때 해당 함수를 종료시키기 때문입니다.

이를 해결하기 위해서 main process안에 임의이 func으로 감싸주겠습니다.

func main() {
    for {
        func() {
            defer func() {
                if r := recover(); r != nil {
                    fmt.Println("Recovered from panic in deferred func:", r)
                }
            }()
            
            fmt.Println("1")
            
            panic("Something went wrong")
            
            fmt.Println("3")
            
            defer func() {
                fmt.Println("5")
            }()
        }()
        time.Sleep(time.Second)
    }
}

결과

1
Recovered from panic in deferred func: Something went wrong
1
Recovered from panic in deferred func: Something went wrong
반복...

4. 여러번 발생하는 panic

func makePanic() {
    panic("2 Something went wrong")
}

func main() {
    for {
        defer func() {
            if r := recover(); r != nil {
                fmt.Println("[1] Recovered from panic in deferred func:", r)
            }

            defer func() {
                if r := recover(); r != nil {
                    fmt.Println("[2] Recovered from panic in deferred func:", r)
                }
            }()

            fmt.Println("1")

            //          panic("2 Something went wrong")
            makePanic()

            fmt.Println("3")

            defer func() {
                fmt.Println("5")
            }()
        }()
        panic("1 Something went wrong")
        time.Sleep(time.Second)
    }
}

결과

[1] Recovered from panic in deferred func: 1 Something went wrong
1
[2] Recovered from panic in deferred func: 2 Something went wrong

1번 패닉이 발생하고나서 가장 밖의 defer 내의 1번 recover가 되어도 defer block은 아직 끝나지 않았기 때문에 1을 출력 후 makePanic()에서 다시 한번 panic에 빠지고, recover되며 defer block 또한 종료가 됩니다.

5. 여러번 발생하는 panic 2

package main

import (
    "fmt"
    "time"
)

func makePanic() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("[3] Recovered from panic in deferred func:", r)
        }
    }()
    panic("2 Something went wrong")
}

func main() {
    for {
        defer func() {
            if r := recover(); r != nil {
                fmt.Println("[1] Recovered from panic in deferred func:", r)
            }

            defer func() {
                if r := recover(); r != nil {
                    fmt.Println("[2] Recovered from panic in deferred func:", r)
                }
            }()

            fmt.Println("1")

            makePanic()

            fmt.Println("3")

            defer func() {
                fmt.Println("5")
            }()
        }()
        panic("1 Something went wrong")
        time.Sleep(time.Second)
    }
}

결과

[1] Recovered from panic in deferred func: 1 Something went wrong
1
[3] Recovered from panic in deferred func: 2 Something went wrong
3
5

4번의 코드 makePanic에 recover를 추가한코드 입니다.
이처럼 defer 내에 2번째 recover가 실행되지 않고 makePanic 내의 3번째 recover가 실행되며, recover 되었기 때문에 main 문의 2번째 defer 내의 recover는 실행되지 않고 3, 5 를 출력하며 process가 종료됩니다.

결론

func() block으로 panic이 발생하는 코드를 감싸는 것으로 for문이 종료되며 main block이 종료되는 것을 막을 수 있습니다.
하지만, 처음부터 main문에 panic이 발생할 수 있는 로직을 넣지않고,
따로 함수를 만들어 해당 함수 내에서 panic을 recover 하고 main로직에서 해당 함수를 호출하는 것이 더 나을 것 같습니다.

profile
모르면 공부하고 알게되면 공유하는 개발자

0개의 댓글