Go 에서는 예상치 못한 에러가 발생했을 때 panic이 발생합니다.
이 때 process가 종료되게되는데 panic시 프로세스가 종료되지 않도록 해주는 함수가 recover()입니다.
프로세스 강제 종료를 막기 위해 시도하였으나 process가 계속 죽어서 panic과 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
프로세스 종료
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
프로세스 종료
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
반복...
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 또한 종료가 됩니다.
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이 종료되는 것을 막을 수 있습니다. 즉, for문은 panic이 발생한 시점에서 scope를 벗어나기 때문에 의미가 없는 코드라 볼 수 있습니다.
하지만, 처음부터 main문에 panic이 발생할 수 있는 로직을 넣지않고,
따로 함수를 만들어 해당 함수 내에서 panic을 recover 한 뒤에 main로직에서 해당 함수를 호출하는 것이 더 나을 것 같습니다.
또한, for문을 통해 해당 로직을 무한히 발생시키고 싶다면,
for{
func(){
//위의 코드
}()
}
이렇게 하면 됩니다!