本記事は、アドベントカレンダー Go4 の1日目の記事です。
これは何?
みなさんは、どんなdaemon処理を書いていますか?
ここでは、Go の daemon 処理サンプルを紹介します.
daemon とは、バックグラウンドで動作するプロセス実行し続けるアレで、typoに注意なやつです(いつもdeamonと書いてしまう).
Goではその言語特性から、様々な処理をシンプルに記述しやすいと感じており、daemonの実装例を通してGoらしさを学ぶことにも役立つと思います.
他にも役立つレシピがあれば、教えて下さい
まずは基本から
単純に無限実行させる
The Go Playground で実行してみる
func main() {
timeout := 5 * time.Second
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
f := func() error { // some task
return nil
}
simple(ctx, f)
}
func simple(ctx context.Context, task func() error) {
for {
err := task()
if err != nil {
log.Printf("[ERROR] err: %v", err)
}
time.Sleep(500 * time.Millisecond)
}
}
context を使ったタイムアウト
The Go Playgroundで実行してみる
func withTimeout(ctx context.Context, task func() error) {
child, childCancel := context.WithCancel(ctx)
defer childCancel()
for {
err := task()
if err != nil {
log.Printf("[ERROR] err: %v", err)
}
select {
case <-child.Done():
log.Printf("[DEBUG] timeout")
return
default:
time.Sleep(500 * time.Millisecond)
}
}
}
応用編
特定時刻にだけ処理をする
The Go Playground で実行してみる
func runDaemon(ctx context.Context, f func(context.Context) error) {
daemonHour := "7-10"
waitDuration := 500 * time.Millisecond
for {
now := time.Now()
if len(daemonHour) != 0 { //起動時刻の指定があったら
isExec := isExecHour(now, daemonHour)
log.Printf("[DEBUG] daemon起動時間かどうかの判定 now:%v, daemonHour:%s, isExec:%v", now, daemonHour, isExec)
if !isExec {
time.Sleep(1 * time.Minute)
continue
}
}
err := f(ctx)
if err != nil {
log.Printf("[ERRROR] err:%v", err)
}
time.Sleep(waitDuration)
}
}
func isExecHour(now time.Time, dHour string) bool {
delimitor := "-"
dh := strings.Split(dHour, delimitor)
if len(dh) <= 1 {
return false
}
start, err := strconv.Atoi(dh[0])
if err != nil {
return false
}
end, err := strconv.Atoi(dh[1])
if err != nil {
return false
}
h := now.Hour()
if start <= h && h <= end {
return true
}
return false
}
一定周期ごとに(前の処理が終わっていなくても)処理を実行する
The Go Playground で実行してみる
func timeTicker(ctx context.Context, task func(context.Context) error) {
counter := 0
waitTime := 1 * time.Second
ticker := time.NewTicker(waitTime)
defer ticker.Stop()
child, childCancel := context.WithCancel(ctx)
defer childCancel()
for { // deamon化するため無限実行
select {
case t := <-ticker.C:
counter++
requestID := counter
log.Println("[DEBUG] START taskNo=", requestID, "t=", t)
errCh := make(chan error, 1)
go func() { // 登録したタスクをブロックせずに実行
errCh <- task(ctx)
}()
go func() {
// error channelにリクエストの結果が返ってくるのを待つ
select {
case err := <-errCh:
if err != nil {
// Deamonの強制終了
log.Println("[ERROR] ", err)
}
log.Println("[DEBUG] END requestNo=", requestID)
}
}()
case <-child.Done():
return
}
}
}
みなさんも、よき daemon Life をお過ごしください!