Add graceful shutdown support for async runners (#125)

This commit is contained in:
C Cirello
2016-10-06 00:32:56 +02:00
committed by GitHub
parent 1e62c2a403
commit aa12f3c724
3 changed files with 78 additions and 22 deletions

View File

@@ -8,6 +8,7 @@ import (
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
"sync"
"time" "time"
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
@@ -95,28 +96,50 @@ func runTask(task *models.Task) error {
} }
// RunAsyncRunner pulls tasks off a queue and processes them // RunAsyncRunner pulls tasks off a queue and processes them
func RunAsyncRunner(tasksrv, port string) { func RunAsyncRunner(ctx context.Context, wgAsync *sync.WaitGroup, tasksrv, port string, n int) {
u := tasksrvURL(tasksrv, port) u := tasksrvURL(tasksrv, port)
var wg sync.WaitGroup
for i := 0; i < n; i++ {
wg.Add(1)
go startAsyncRunners(ctx, &wg, i, u, runTask)
}
wg.Wait()
<-ctx.Done()
wgAsync.Done()
}
func startAsyncRunners(ctx context.Context, wg *sync.WaitGroup, i int, url string, runTask func(task *models.Task) error) {
defer wg.Done()
for { for {
task, err := getTask(u) select {
case <-ctx.Done():
return
default:
task, err := getTask(url)
if err != nil { if err != nil {
log.WithError(err).Info("Cannot get task") log.WithError(err).Error("Could not fetch task")
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
continue continue
} }
log.Info("Picked up task:", task.ID) log.Info("Picked up task:", task.ID)
log.Info("Running task:", task.ID)
// Process Task // Process Task
if err := runTask(task); err != nil { if err := runTask(task); err != nil {
log.WithError(err) log.WithError(err).WithFields(log.Fields{"async runner": i, "task_id": task.ID}).Error("Cannot run task")
continue continue
} }
log.Info("Processed task:", task.ID) log.Info("Processed task:", task.ID)
// Delete task from queue // Delete task from queue
if err := deleteTask(u, task); err != nil { if err := deleteTask(url, task); err != nil {
log.WithError(err) log.WithError(err).WithFields(log.Fields{"async runner": i, "task_id": task.ID}).Error("Cannot delete task")
} else { continue
}
log.Info("Deleted task:", task.ID) log.Info("Deleted task:", task.ID)
} }
} }

View File

@@ -7,7 +7,9 @@ import (
"math/rand" "math/rand"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"sync"
"testing" "testing"
"time"
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@@ -167,3 +169,21 @@ func TestTasksrvURL(t *testing.T) {
} }
} }
} }
func TestAsyncRunnersGracefulShutdown(t *testing.T) {
mockTask := getMockTask()
ts := getTestServer([]*models.Task{&mockTask})
defer ts.Close()
ctx, _ := context.WithTimeout(context.Background(), 2*time.Second)
var wg sync.WaitGroup
wg.Add(1)
go startAsyncRunners(ctx, &wg, 0, ts.URL+"/tasks", func(task *models.Task) error {
return nil
})
wg.Wait()
if err := ctx.Err(); err != context.DeadlineExceeded {
t.Errorf("async runners stopped unexpectedly. context error: %v", err)
}
}

21
main.go
View File

@@ -3,7 +3,9 @@ package main
import ( import (
"fmt" "fmt"
"os" "os"
"os/signal"
"strings" "strings"
"sync"
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
"github.com/iron-io/functions/api/datastore" "github.com/iron-io/functions/api/datastore"
@@ -38,7 +40,14 @@ func init() {
} }
func main() { func main() {
ctx := context.Background() ctx, halt := context.WithCancel(context.Background())
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
<-c
log.Info("Halting...")
halt()
}()
ds, err := datastore.New(viper.GetString("DB")) ds, err := datastore.New(viper.GetString("DB"))
if err != nil { if err != nil {
@@ -57,10 +66,14 @@ func main() {
tasksURL, port, nasync := viper.GetString("tasks_url"), viper.GetString("port"), viper.GetInt("nasync") tasksURL, port, nasync := viper.GetString("tasks_url"), viper.GetString("port"), viper.GetInt("nasync")
log.Info("async workers:", nasync) log.Info("async workers:", nasync)
for i := 0; i < nasync; i++ { var wgAsync sync.WaitGroup
go runner.RunAsyncRunner(tasksURL, port) if nasync > 0 {
wgAsync.Add(1)
go runner.RunAsyncRunner(ctx, &wgAsync, tasksURL, port, nasync)
} }
srv := server.New(ds, mqType, rnr) srv := server.New(ds, mqType, rnr)
srv.Run(ctx) go srv.Run(ctx)
<-ctx.Done()
wgAsync.Wait()
} }