mirror of
https://github.com/fnproject/fn.git
synced 2022-10-28 21:29:17 +03:00
Add graceful shutdown support for async runners (#125)
This commit is contained in:
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
21
main.go
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user