LoginSignup
13
11

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-11-30

本記事は、アドベントカレンダー Go4 の1日目の記事です。

これは何?

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

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

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

まずは基本から

単純に無限実行させる

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

13
11
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
11