I want to create a scheduler so that it executes a task every second for example, but also would like to have and http interface to stop/start the scheduler and get more stats/info, after reading more about timers & tickers, channels and gorutines I came out with this:
https://gist.github.com/nbari/483c5b382c795bf290b5
package main
import (
"fmt"
"log"
"net/http"
"time"
)
var timer *time.Ticker
func scheduler(seconds time.Duration) *time.Ticker {
ticker := time.NewTicker(seconds * time.Second)
go func() {
for t := range ticker.C {
// do stuff
fmt.Println(t)
}
}()
return ticker
}
func Start(timer *time.Ticker) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
timer = scheduler(1)
w.Write([]byte("Starting scheduler"))
})
}
func Stop(timer *time.Ticker) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
timer.Stop()
w.Write([]byte("Stoping scheduler"))
})
}
func main() {
timer = scheduler(1)
http.Handle("/start", Start(timer))
http.Handle("/stop", Stop(timer))
log.Fatal(http.ListenAndServe(":8080", nil))
}
The above code is working but I have a global "timer" variable, I would like to know if there is a better way to implement this and also a way for handle more than 1 scheduler, currently thinking on probably implementing kind of a container for all the scheduler but would like to have some feedbacks that could help me find clever solutions.
Yes, there is a better way:
You should use channels here, and, as you suggested, some data structure to hold more schedulers.
I came up with this, it's a most basic working example of what I'd do:
package main
import (
"errors"
"fmt"
"sync"
"time"
)
// a scheduler runs f on every receive on t and exits when receiving from quit is non-blocking
type scheduler struct {
t <-chan time.Time
quit chan struct{}
f func()
}
// a schedulerPool holds multiple schedulers
type schedulerPool struct {
schedulers map[int]scheduler //I used a map here so you can use more clever keys
counter int
mut sync.Mutex
}
func newPool() *schedulerPool {
return &schedulerPool{
schedulers: make(map[int]scheduler),
}
}
// start adds and starts a new scheduler that will execute f every interval.
// It returns the generated ID for the scheduler, or an error.
func (p *schedulerPool) start(interval time.Duration, f func()) (int, error) {
p.mut.Lock()
defer p.mut.Unlock()
index := p.counter
p.counter++
if _, ok := p.schedulers[index]; ok {
return 0, errors.New("key already in use")
}
sched := scheduler{
t: time.NewTicker(interval).C,
quit: make(chan struct{}),
f: f,
}
p.schedulers[index] = sched
go func() {
for {
select {
case <-sched.t:
sched.f()
case <-sched.quit:
return
}
}
}()
return index, nil
}
// stop stops the scheduler with the given ID.
func (p *schedulerPool) stop(index int) error {
p.mut.Lock()
defer p.mut.Unlock()
sched, ok := p.schedulers[index]
if !ok {
return errors.New("does not exist")
}
close(sched.quit)
return nil
}
func main() {
// get a new pool
pool := newPool()
// start a scheduler
idx1, err := pool.start(time.Second, func() { fmt.Println("hello 1") })
if err != nil {
panic(err)
}
// start another scheduler
idx2, err := pool.start(time.Second, func() { fmt.Println("hello 2") })
if err != nil {
panic(err)
}
// wait some time
time.Sleep(3 * time.Second)
// stop the second scheduler
err = pool.stop(idx2)
if err != nil {
panic(err)
}
// wait some more
time.Sleep(3 * time.Second)
// stop the first scheduler
err = pool.stop(idx1)
if err != nil {
panic(err)
}
}
Check it out on the playground.
Note that there is no guarantee that the first scheduler will run six times in total and the second one three times.
Also note a few other things I've done:
beep
to have a scheduler dump something to the console (like "beep"
))type schedFunc func(int, int)
Obviously, you need to wrap that stuff inside of http handlers, probably take the ID or parameters from the querystring, or some other fancy stuff. This is just for demonstration.