最近我遇到了一個問題,我寫的程式會呼叫 git clone ,而 git clone 在網路環境不佳時,甚至會執行超過24小時,將程式的 go routine 整個卡住。所以解法就是需要將 golang 的外部指令呼叫,改成有 timeout 的。
要加上這個 timeout 並不難,困難點是,要如何測試這個功能?因為其實要模擬出 git clone 長時間執行的環境,並不是很簡單的事。
解法是這樣子:
加上這個功能時,必須將這個「為指令加上 timeout 功能」實作成一個獨立的函數,使它的功能獨立,並不「依賴」於 git clone 。換言之,「 git clone 」對應的指令,會成為這個函數的輸入變數(input argument),由外部「注入」。於是這個功能就可以獨立地來寫單元測試來測。測試它的時候,就可以用「 sleep 500 」對應的指令,來做為它的輸入變數(input argument)
原始程式碼的修改
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Original -> New | |
cmd := exec.Command("git", "clone", gitRemoteAddr, file.Basename(pluginDir)) | |
cmd.Dir = parentDir | |
- err2 := cmd.Run() | |
+ err2 := RunCmdWithTimeout(cmd, 600) |
新增的程式碼和單元測試
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
func RunCmdWithTimeout(cmd *exec.Cmd, timeout int64) (err error) { | |
if err = cmd.Start(); err != nil { | |
log.Println("Start shell command error:", err) | |
} | |
done := make(chan error, 1) | |
go func() { done <- cmd.Wait() }() | |
d := time.Duration(time.Duration(timeout) * time.Second) | |
select { | |
case err = <-done: | |
case <-time.After(d): | |
// timed out | |
if err = cmd.Process.Kill(); err != nil { | |
log.Printf("failed to kill: %s, error: %s", cmd.Path, err) | |
} | |
err = fmt.Errorf("Command %s time out", cmd.Path) | |
} | |
return | |
} | |
// Unit test of RunCmdWithTimeout | |
func TestRunCmdWithTimeout(t *testing.T) { | |
cmd := exec.Command("sleep", "500") | |
t1 := time.Now() | |
RunCmdWithTimeout(cmd, 3) | |
t2 := time.Now() | |
t.Log("Time spent should less than 4 second: ", t2.Sub(t1)) | |
} |