How do I make it cache(?) the output of the command for one minute,
and then run the command again only after the cache time is expired?
In below solution two goroutines are declared.
- First goroutine loop until the context is done to execute the command at regular interval and send a copy to the second go routine.
- The second routine, tries to get from the first goroutine, or distribute its value to other goroutines.
- The http handler, third goroutine, only queries the value from the getter and does something with it.
The reason to use three routines instead of two in this example is to prevent blocking the http routines if the command is being executed. With that additional routines, http requests only wait for the synchronization to occur.
type state
is a dummy struct to transport the values within channels.
We prevent race conditions because of two facts, the state is passed by value, cmd.Output()
allocates a new buffer each time it runs.
To retrieve original command in the http handler, OP should build a custom error type and attach those information to the recorded error, within the http handler, OP should type assert the error to its custom type and get the specific details from there.
package main
import (
"context"
"fmt"
"log"
"net/http"
"os/exec"
"time"
)
type state struct {
out []byte
err error
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
set := make(chan state, 1)
go func() {
ticker := time.NewTicker(2 * time.Second)
cmd := []string{"date"}
s := state{}
s.out, s.err = exec.Command(cmd[0], cmd[1:]...).Output()
set <- s
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
s.out, s.err = exec.Command(cmd[0], cmd[1:]...).Output()
set <- s
}
}
}()
get := make(chan state)
go func() {
s := state{}
for {
select {
case <-ctx.Done():
return
case s = <-set: // get the fresh value, if any
case get <- s: // distribute your copy
}
}
}()
http.HandleFunc("/feed", func(w http.ResponseWriter, r *http.Request) {
state := <-get
if state.err != nil {
fmt.Printf("Failed to execute command: %v", state.err)
}
fmt.Fprintf(w, string(state.out))
})
log.Fatal(http.ListenAndServe(":8080", nil))
}