본문 바로가기

소소한 팁

Go 언어에서 프로세스 실행 타임아웃(timeout) 지정하기

Go 언어의 장점은 goroutine이라는 가벼운 thread를 기반으로 동적인 처리를 효율적으로 제공한다는 것이다. 그리고 채널 등을 통해 goroutine을 어렵지 않게 제어할 수 있지만, 별도로 프로세스를 실행하는 경우에는 통제가 어렵다.

실제 내부적으로 Maven이나 Gradle 빌드를 별도의 프로세스로 실행하는 경우에 해당 프로세스들이 중지되지 않는 경우들이 종종 발생한다. 이런 경우 내부적인 goroutine 관리만으로는 프로세스를 중지할 수 없다.

이런 경우에 친절하게도 Go에서는 타임아웃이 적용된 프로세스를 시작할 수 있고, 타임아웃이 된 경우에 실행했던 프로세스를 "kill -9"와 같은 명령으로 강제 종료시키는 것을 지원한다. 이를 공통 함수로 활용하면 어렵지 않게 타임아웃을 적용할 수 있다.

package util

import (
    "context"
    "errors"
    "fmt"
    "log"
    "os/exec"
    "runtime"
    "time"
    
    "golang.org/x/text/encoding/korean"
    "golang.org/x/text/transform"
)

// Windows에서 한글 처리 (euc-kr -> utf-8)
func WindowsHangul(text string) (result string, err error) {
    result, _, err = transform.String(korean.EUCKR.NewDecoder(), text)
    
    return
}

func ExecCommandWithTimeout(errorOnTimeout bool, timeoutMin int, workingDirectory string, name string, arg ...string) (output string, timedOut bool, runError error) {
    ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeoutMin) * time.Minute)
    defer cancel()
    
    cmd := exec.CommandContext(ctx, name, arg...)
    cmd.Dir = workingDirectory
    
    stdOutAndErr, err := cmd.CombineOutput()
    if ctx.Err() == context.DeadlineExceeded {
        timedOut = true
    }
    
    if runtime.GOOS == "windows" {
        output, _ = WindowsHangul(string(stdOutAndErr))
    } else {
        output = string(stdOutAndErr)
    }
    
    if err != nil {
        output = fmt.Sprintf("%s\nError : %v", output, err)
        runError = err
    }
    
    // Timeout 발생 시, error 발생
    if timedOut {
        var resolvedError error
        if runError == nil {
            if errorOnTimeout {
                runError = errors.New("timed out")
            }
        } else {
            if !errorOnTimeout {
                resolvedError = runError
                runError = nil
            }
        }
        output += "\n=====> The process requested to execute has timed out!!!"
        output += fmt.Sprintln("\ntimed out process :", name, arg)
        if resolvedError != nil {
            output += fmt.Sprintln("Resolved error :", resolvedError)
        }
        
        log.Println("[WARN] timed out proess working dir. :", workingDirectory)
        log.Println("[WARN] timed out process output :", output)
    }
    
    return
}

아울러, Windows에서의 한글 처리(euc-kr 결과를 utf-8로 변환)하는 함수도 포함하고 있다.

Written with   by Vincent Han