Goでつくるdaemon処理レシピ集

本記事は、アドベントカレンダー [Go4](https://qiita.com/advent-calendar/2019/go4) の1日目の記事です。

## これは何?

みなさんは、どんなdaemon処理を書いていますか？

ここでは、Go の daemon 処理サンプルを紹介します.
daemon とは、バックグラウンドで動作するプロセス実行し続けるアレで、typoに注意なやつです(いつもdeamonと書いてしまう).

Goではその言語特性から、様々な処理をシンプルに記述しやすいと感じており、daemonの実装例を通してGoらしさを学ぶことにも役立つと思います.
他にも役立つレシピがあれば、教えて下さい :smiley: 



## まずは基本から

### 単純に無限実行させる

[The Go Playground](https://play.golang.org/p/RfaXcXsdMd2) で実行してみる

```Go
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](https://play.golang.org/p/GeiPbQLyjLm)で実行してみる

```Go
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](https://play.golang.org/p/Lfz0fE1kyX2) で実行してみる

```Go

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](https://play.golang.org/p/q-SxTfZonFD) で実行してみる

```Go
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 をお過ごしください!