初めに
goroutineといえばgolangで並行処理を実装するための言語機能ですが、それを使いあえて並行処理を行わず、Timeoutイベントを取るためだけに使ったコードをシェアします
背景
とあるDBサービスのデータをバックアップするために、そのDBサービスが提供するコマンドラインツールを使う必要がありました
ですが、いつまで経ってもレスポンスが返ってこないことがありました
その対応として、一定期間レスポンスが返ってこない場合に、中断しリトライするようにしました
なお、初めは全テーブル並行処理で実行したのですが、DBサービス側での制限があるようで安定したレスポンスが得られなかったので、1テーブルづつの実行に変更しました
// download and return to channel
func worker(table string, dt string, ch chan Result) {
err := exec.Command("コマンドラインツール",
"-a", "aaa",
"-b", "bbb").Run()
if err != nil {
zap.S().Errorf("%s: %v", table, err)
ch<- Result{table: table, err: err}
return
}
ch<- Result{table: table, err: nil}
}
func execDownload(tables []string, dt string) []string {
// channel use only timeout event
ch := make(chan Result, 1)
var finTables []string
// call go routine and download
for _, table := range tables {
go func(table string) {
zap.S().Infof("Start: %s", table)
worker(table, dt, ch)
}(table)
select {
case result := <-ch:
if result.err == nil {
zap.S().Infof("Finish: %s", result.table)
finTables = append(finTables, result.table)
} else {
zap.S().Errorf("Error: %s, %v", result.table, result.err)
}
case <-time.After(time.Minute * 3):
zap.S().Info("TIMEOUT")
}
}
return finTables
}
(業務に関連しそうな所は書き換えています)
main関数から、ダウンロード対象となるテーブルのスライスを渡してループし、go funcからworker関数でダウンロードコマンドを実行します
本来であれば全テーブル並行で動くところですが、channelの数を1つにしているため、ダウンロードが終了し、channelに戻り値を書き込まれるまでブロックされるので、1テーブルづつの実行となります
ダウンロードが成功したテーブルをmain関数に返し、未処理のテーブルのみをリトライするようにしています
補足
こういった処理はスクリプト系で書けば簡単に実装出来そうですが、ダウンロード以外にも必要な処理があり、それはnodeを必要としていました
nodeで実装しようとしましたが、nodeとTimeoutイベントがちょっと相性悪そうだったのと、コンテナでの実行が前提となっているため、nodeの上に新たに他のスクリプト環境を上乗せするのは、筋が悪いように思われました
このような理由で、1バイナリで完結するgolangで実装することにしました
終わりに
この記事が何方かのお役に立つと嬉しく思います