Home > other >  How to implement a for loop mechanism with a timer that runs immediately in the first loop?
How to implement a for loop mechanism with a timer that runs immediately in the first loop?

Time:06-03

I would like to write a golang program that runs as a service in a container in kubernetes. The program should run all the time and not terminate itself - except in the event of an error. If a SIGTERM comes from kubelet / kubernetes because the pod should be deleted, the program should exit.

In my first approach, I implemented a for loop that should be run through every minute. In this loop, the program queries a resource from another service and processes it. The service should then "sleep" for a minute before carrying out the steps again. During the "sleep" phase, the program should immediately repond if there is a SIGTERM.

func main() {
  
  c := make(chan os.Signal, 1)
  signal.Notify(c, os.Interrupt, syscall.SIGTERM)

  //restart loop every minute 
  ticker := time.NewTicker(60 * time.Second)
  defer ticker.Stop()

  // while true loop
  for {
    select {
    case <-c:
      fmt.Println("Break the loop")
      return
    case <-ticker.C:
      fmt.Println("Hello in a loop")
    }
  }
}

I have two problems with my current approach. Firstly, I have to wait a minute the first time I run the for loop. Can I somehow configure it so that the timer only runs after the first run?

The second problem is that the program should not do anything between runs - except react to SIGTERM.

I'm not sure if my approach to the problem is the right one. I'm just starting out with the GO language. Perhaps there is a much better approach to my problem.

CodePudding user response:

You can do your work outside of the select statement, and just continue inside of the ticket channel case. This will execute your work once immediately, then every time the ticker ticks.

Your function looks good, but it is good practice to make use of context when you can.

Lot's of packages (database, IO, etc) have the option of specifying a context, which is often used to define timeouts. In your case, passing the same (or child) context to these packages would mean that they also respect sigterm.

package main

import (
    "context"
    "fmt"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    ctx := cancelCtxOnSigterm(context.Background())
    startWork(ctx)
}

// cancelCtxOnSigterm returns a Context that will be cancelled when the program receives a sigterm.
func cancelCtxOnSigterm(ctx context.Context) context.Context {
    exitCh := make(chan os.Signal, 1)
    signal.Notify(exitCh, os.Interrupt, syscall.SIGTERM)

    ctx, cancel := context.WithCancel(ctx)
    go func() {
        <-exitCh
        cancel()
    }()
    return ctx
}

// startWork performs a task every 60 seconds until the context is done.
func startWork(ctx context.Context) {
    ticker := time.NewTicker(60 * time.Second)
    defer ticker.Stop()
    for {
        // Do work here so we don't need duplicate calls. It will run immediately, and again every minute as the loop continues.
        if err := work(ctx); err != nil {
            fmt.Printf("failed to do work: %s", err)
        }
        select {
        case <-ticker.C:
            continue
        case <-ctx.Done():
            return
        }
    }
}

func work(ctx context.Context) error {
    fmt.Println("doing work")
    return nil
}
  •  Tags:  
  • go
  • Related