Update split SDK to 6.0.2 to fix sync bug (#17060)
* Update split SDK to 6.0.2 to fix sync bug * Vendor and tidy
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
fa2ecad0a9
Коммит
aba00a3cfd
216
vendor/github.com/splitio/go-toolkit/v3/sse/sse.go
сгенерированный
поставляемый
216
vendor/github.com/splitio/go-toolkit/v3/sse/sse.go
сгенерированный
поставляемый
@@ -1,216 +0,0 @@
|
||||
package sse
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/splitio/go-toolkit/v3/logging"
|
||||
)
|
||||
|
||||
const (
|
||||
// OK It could connect streaming
|
||||
OK = iota
|
||||
// ErrorOnClientCreation Could not create client
|
||||
ErrorOnClientCreation
|
||||
// ErrorRequestPerformed Could not perform request
|
||||
ErrorRequestPerformed
|
||||
// ErrorConnectToStreaming Could not connect to streaming
|
||||
ErrorConnectToStreaming
|
||||
// ErrorReadingStream Error in streaming
|
||||
ErrorReadingStream
|
||||
// ErrorKeepAlive timedout
|
||||
ErrorKeepAlive
|
||||
// ErrorInternal Internal error for streaming
|
||||
ErrorInternal
|
||||
// ErrorUnexpected unexpected error occures
|
||||
ErrorUnexpected
|
||||
)
|
||||
|
||||
var sseDelimiter [2]byte = [...]byte{':', ' '}
|
||||
var sseData [4]byte = [...]byte{'d', 'a', 't', 'a'}
|
||||
var sseKeepAlive [10]byte = [...]byte{':', 'k', 'e', 'e', 'p', 'a', 'l', 'i', 'v', 'e'}
|
||||
|
||||
// SSEClient struct
|
||||
type SSEClient struct {
|
||||
url string
|
||||
client http.Client
|
||||
status chan int
|
||||
shutdown chan struct{}
|
||||
timeout int
|
||||
logger logging.LoggerInterface
|
||||
}
|
||||
|
||||
// NewSSEClient creates new SSEClient
|
||||
func NewSSEClient(url string, status chan int, timeout int, logger logging.LoggerInterface) (*SSEClient, error) {
|
||||
if cap(status) < 1 {
|
||||
return nil, errors.New("Status channel should have length")
|
||||
}
|
||||
if timeout < 1 {
|
||||
return nil, errors.New("Timeout should be higher than 0")
|
||||
}
|
||||
return &SSEClient{
|
||||
url: url,
|
||||
client: http.Client{},
|
||||
status: status,
|
||||
shutdown: make(chan struct{}, 1),
|
||||
timeout: timeout,
|
||||
logger: logger,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Shutdown stops SSE
|
||||
func (l *SSEClient) Shutdown() {
|
||||
select {
|
||||
case l.shutdown <- struct{}{}:
|
||||
default:
|
||||
l.logger.Error("Shutdown already in progress")
|
||||
}
|
||||
}
|
||||
|
||||
func parseData(raw []byte) (map[string]interface{}, error) {
|
||||
data := make(map[string]interface{})
|
||||
err := json.Unmarshal(raw, &data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing json: %w", err)
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (l *SSEClient) readEvent(reader *bufio.Reader) (map[string]interface{}, error) {
|
||||
line, err := reader.ReadBytes('\n')
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(line) < 2 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
splitted := bytes.Split(line, sseDelimiter[:])
|
||||
|
||||
if bytes.Compare(splitted[0], sseData[:]) != 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
raw := bytes.TrimSpace(splitted[1])
|
||||
l.logger.Debug("LINE:", string(line))
|
||||
data, err := parseData(raw)
|
||||
if err != nil {
|
||||
l.logger.Error("Error parsing event: ", err)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func parseHTTPError(resp *http.Response) int {
|
||||
if resp.StatusCode >= http.StatusInternalServerError {
|
||||
return ErrorInternal
|
||||
}
|
||||
return ErrorConnectToStreaming
|
||||
}
|
||||
|
||||
// Do starts streaming
|
||||
func (l *SSEClient) Do(params map[string]string, callback func(e map[string]interface{})) {
|
||||
select {
|
||||
case <-l.shutdown:
|
||||
// Skipping previous shutdown
|
||||
default:
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
shouldRun := atomic.Value{}
|
||||
shouldRun.Store(false)
|
||||
activeGoroutines := sync.WaitGroup{}
|
||||
defer func() {
|
||||
l.logger.Info("SSE streaming exiting")
|
||||
shouldRun.Store(false)
|
||||
cancel()
|
||||
activeGoroutines.Wait()
|
||||
}()
|
||||
|
||||
req, err := http.NewRequest("GET", l.url, nil)
|
||||
if err != nil {
|
||||
l.logger.Error(err)
|
||||
l.status <- ErrorOnClientCreation
|
||||
return
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
query := req.URL.Query()
|
||||
|
||||
for key, value := range params {
|
||||
query.Add(key, value)
|
||||
}
|
||||
req.URL.RawQuery = query.Encode()
|
||||
req.Header.Set("Accept", "text/event-stream")
|
||||
|
||||
resp, err := l.client.Do(req)
|
||||
if err != nil {
|
||||
l.logger.Error(err)
|
||||
l.status <- ErrorRequestPerformed
|
||||
return
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
l.status <- parseHTTPError(resp)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
l.status <- OK
|
||||
reader := bufio.NewReader(resp.Body)
|
||||
|
||||
eventChannel := make(chan map[string]interface{}, 1000)
|
||||
shouldRun.Store(true)
|
||||
go func() {
|
||||
for shouldRun.Load().(bool) {
|
||||
event, err := l.readEvent(reader)
|
||||
if err != nil {
|
||||
if shouldRun.Load().(bool) {
|
||||
l.logger.Error(err)
|
||||
}
|
||||
close(eventChannel)
|
||||
return
|
||||
}
|
||||
eventChannel <- event
|
||||
}
|
||||
}()
|
||||
|
||||
// Create timeout timer in case SSE dont receive notifications or keepalive messages
|
||||
idleDuration := time.Duration(l.timeout) * time.Second
|
||||
keepAliveTimer := time.NewTimer(idleDuration)
|
||||
defer keepAliveTimer.Stop()
|
||||
|
||||
for {
|
||||
// Resetting timer
|
||||
keepAliveTimer.Reset(idleDuration)
|
||||
|
||||
select {
|
||||
case <-l.shutdown:
|
||||
l.logger.Info("Shutting down listener")
|
||||
return
|
||||
case event, ok := <-eventChannel:
|
||||
if !ok {
|
||||
l.status <- ErrorReadingStream
|
||||
return
|
||||
}
|
||||
if event != nil {
|
||||
activeGoroutines.Add(1)
|
||||
go func() {
|
||||
defer activeGoroutines.Done()
|
||||
callback(event)
|
||||
}()
|
||||
}
|
||||
case <-keepAliveTimer.C: // Timedout
|
||||
l.status <- ErrorKeepAlive
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
0
vendor/github.com/splitio/go-toolkit/v3/LICENSE → vendor/github.com/splitio/go-toolkit/v4/LICENSE
сгенерированный
поставляемый
0
vendor/github.com/splitio/go-toolkit/v3/LICENSE → vendor/github.com/splitio/go-toolkit/v4/LICENSE
сгенерированный
поставляемый
119
vendor/github.com/splitio/go-toolkit/v3/asynctask/asynctasks.go → vendor/github.com/splitio/go-toolkit/v4/asynctask/asynctasks.go
сгенерированный
поставляемый
119
vendor/github.com/splitio/go-toolkit/v3/asynctask/asynctasks.go → vendor/github.com/splitio/go-toolkit/v4/asynctask/asynctasks.go
сгенерированный
поставляемый
@@ -2,81 +2,66 @@ package asynctask
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/splitio/go-toolkit/v3/logging"
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
"github.com/splitio/go-toolkit/v4/struct/traits/lifecycle"
|
||||
)
|
||||
|
||||
// AsyncTask is a struct that wraps tasks that should run periodically and can be remotely stopped & started,
|
||||
// as well as making it's status (running/stopped) available.
|
||||
type AsyncTask struct {
|
||||
task func(l logging.LoggerInterface) error
|
||||
name string
|
||||
running atomic.Value
|
||||
incoming chan int
|
||||
period int
|
||||
onInit func(l logging.LoggerInterface) error
|
||||
onStop func(l logging.LoggerInterface)
|
||||
logger logging.LoggerInterface
|
||||
finished atomic.Value
|
||||
finishChan chan struct{}
|
||||
lifecycle lifecycle.Manager
|
||||
task func(l logging.LoggerInterface) error
|
||||
name string
|
||||
incoming chan int
|
||||
period int
|
||||
onInit func(l logging.LoggerInterface) error
|
||||
onStop func(l logging.LoggerInterface)
|
||||
logger logging.LoggerInterface
|
||||
}
|
||||
|
||||
const (
|
||||
taskMessageStop = iota
|
||||
taskMessageWakeup
|
||||
taskMessageWakeup = iota
|
||||
)
|
||||
|
||||
func (t *AsyncTask) _running() bool {
|
||||
res, ok := t.running.Load().(bool)
|
||||
if !ok {
|
||||
t.logger.Error("Error parsing async task status flag")
|
||||
return false
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// Start initiates the task. It wraps the execution in a closure guarded by a call to recover() in order
|
||||
// to prevent the main application from crashin if something goes wrong while the sdk interacts with the backend.
|
||||
func (t *AsyncTask) Start() {
|
||||
|
||||
if t._running() {
|
||||
if !t.lifecycle.BeginInitialization() {
|
||||
if t.logger != nil {
|
||||
t.logger.Warning("Task %s is already running. Aborting new execution.", t.name)
|
||||
t.logger.Warning(fmt.Sprintf("Task %s is not idle. Aborting new execution.", t.name))
|
||||
}
|
||||
return
|
||||
}
|
||||
t.running.Store(true)
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
t.finished.Store(true)
|
||||
t.finishChan <- struct{}{}
|
||||
}()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.running.Store(false)
|
||||
if t.logger != nil {
|
||||
t.logger.Error(fmt.Sprintf(
|
||||
"AsyncTask %s is panicking! Delaying execution for %d seconds (1 period)",
|
||||
"AsyncTask %s is panicking! shutting down. Consider restarting this instance and raising an issue",
|
||||
t.name,
|
||||
t.period,
|
||||
))
|
||||
t.logger.Error(r)
|
||||
}
|
||||
time.Sleep(time.Duration(t.period) * time.Second)
|
||||
}
|
||||
}()
|
||||
|
||||
defer t.lifecycle.ShutdownComplete()
|
||||
if !t.lifecycle.InitializationComplete() {
|
||||
return
|
||||
}
|
||||
|
||||
// If there's an initialization function, execute it
|
||||
if t.onInit != nil {
|
||||
err := t.onInit(t.logger)
|
||||
if err != nil {
|
||||
// If something goes wrong during initialization, abort.
|
||||
if err != nil { // If something goes wrong during initialization, abort.
|
||||
if t.logger != nil {
|
||||
t.logger.Error(err.Error())
|
||||
}
|
||||
t.lifecycle.AbnormalShutdown()
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -86,8 +71,19 @@ func (t *AsyncTask) Start() {
|
||||
taskTimer := time.NewTimer(idleDuration)
|
||||
defer taskTimer.Stop()
|
||||
|
||||
if t.onStop != nil {
|
||||
defer t.onStop(t.logger)
|
||||
}
|
||||
|
||||
// Task execution
|
||||
for t._running() {
|
||||
for {
|
||||
select {
|
||||
case <-t.lifecycle.ShutdownRequested():
|
||||
return
|
||||
case <-t.incoming: // wake up signal
|
||||
case <-taskTimer.C: // Timedout
|
||||
}
|
||||
|
||||
// Run the wrapped task and handle the returned error if any.
|
||||
err := t.task(t.logger)
|
||||
if err != nil && t.logger != nil {
|
||||
@@ -96,22 +92,6 @@ func (t *AsyncTask) Start() {
|
||||
|
||||
// Resetting timer
|
||||
taskTimer.Reset(idleDuration)
|
||||
|
||||
// Wait for either a timeout or an interruption (can be a stop signal or a wake up)
|
||||
select {
|
||||
case msg := <-t.incoming:
|
||||
switch msg {
|
||||
case taskMessageStop:
|
||||
t.running.Store(false)
|
||||
case taskMessageWakeup:
|
||||
}
|
||||
case <-taskTimer.C: // Timedout
|
||||
}
|
||||
}
|
||||
|
||||
// Post-execution cleanup
|
||||
if t.onStop != nil {
|
||||
t.onStop(t.logger)
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -127,19 +107,12 @@ func (t *AsyncTask) sendSignal(signal int) error {
|
||||
|
||||
// Stop executes onStop hook if any, blocks until its done (if blocking = true) and prevents future executions of the task.
|
||||
func (t *AsyncTask) Stop(blocking bool) error {
|
||||
|
||||
if !t._running() || t.finished.Load().(bool) {
|
||||
// Task already stopped
|
||||
return nil
|
||||
}
|
||||
if err := t.sendSignal(taskMessageStop); err != nil {
|
||||
// If the signal couldnt be sent, return error!
|
||||
return err
|
||||
if !t.lifecycle.BeginShutdown() {
|
||||
return fmt.Errorf("task '%s' not running", t.name)
|
||||
}
|
||||
|
||||
if blocking {
|
||||
// If blocking was set to true, wait until an empty strcut is pushed into the channel
|
||||
<-t.finishChan
|
||||
t.lifecycle.AwaitShutdownComplete()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -151,7 +124,7 @@ func (t *AsyncTask) WakeUp() error {
|
||||
|
||||
// IsRunning returns true if the task is currently running
|
||||
func (t *AsyncTask) IsRunning() bool {
|
||||
return t._running()
|
||||
return t.lifecycle.IsRunning()
|
||||
}
|
||||
|
||||
// NewAsyncTask creates a new task and returns a pointer to it
|
||||
@@ -164,16 +137,14 @@ func NewAsyncTask(
|
||||
logger logging.LoggerInterface,
|
||||
) *AsyncTask {
|
||||
t := AsyncTask{
|
||||
name: name,
|
||||
task: task,
|
||||
period: period,
|
||||
onInit: onInit,
|
||||
onStop: onStop,
|
||||
logger: logger,
|
||||
incoming: make(chan int, 10),
|
||||
finishChan: make(chan struct{}, 1),
|
||||
name: name,
|
||||
task: task,
|
||||
period: period,
|
||||
onInit: onInit,
|
||||
onStop: onStop,
|
||||
logger: logger,
|
||||
incoming: make(chan int, 10),
|
||||
}
|
||||
t.running.Store(false)
|
||||
t.finished.Store(false)
|
||||
t.lifecycle.Setup()
|
||||
return &t
|
||||
}
|
||||
35
vendor/github.com/splitio/go-toolkit/v4/backoff/backoff.go
сгенерированный
поставляемый
Обычный файл
35
vendor/github.com/splitio/go-toolkit/v4/backoff/backoff.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,35 @@
|
||||
package backoff
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Interface is the backoff interface
|
||||
type Interface interface {
|
||||
Next() time.Duration
|
||||
Reset()
|
||||
}
|
||||
|
||||
// Impl implements the Backoff interface
|
||||
type Impl struct {
|
||||
base int64
|
||||
current int64
|
||||
}
|
||||
|
||||
// Next returns how long to wait and updates the current count
|
||||
func (b *Impl) Next() time.Duration {
|
||||
current := atomic.AddInt64(&b.current, 1)
|
||||
return time.Duration(math.Pow(float64(b.base), float64(current))) * time.Second
|
||||
}
|
||||
|
||||
// Reset sets the current count to 0
|
||||
func (b *Impl) Reset() {
|
||||
atomic.StoreInt64(&b.current, 0)
|
||||
}
|
||||
|
||||
// New creates a new Backoffer
|
||||
func New() *Impl {
|
||||
return &Impl{base: 2}
|
||||
}
|
||||
0
vendor/github.com/splitio/go-toolkit/v3/common/interface.go → vendor/github.com/splitio/go-toolkit/v4/common/interface.go
сгенерированный
поставляемый
0
vendor/github.com/splitio/go-toolkit/v3/common/interface.go → vendor/github.com/splitio/go-toolkit/v4/common/interface.go
сгенерированный
поставляемый
0
vendor/github.com/splitio/go-toolkit/v3/common/iterutil.go → vendor/github.com/splitio/go-toolkit/v4/common/iterutil.go
сгенерированный
поставляемый
0
vendor/github.com/splitio/go-toolkit/v3/common/iterutil.go → vendor/github.com/splitio/go-toolkit/v4/common/iterutil.go
сгенерированный
поставляемый
0
vendor/github.com/splitio/go-toolkit/v3/common/refutil.go → vendor/github.com/splitio/go-toolkit/v4/common/refutil.go
сгенерированный
поставляемый
0
vendor/github.com/splitio/go-toolkit/v3/common/refutil.go → vendor/github.com/splitio/go-toolkit/v4/common/refutil.go
сгенерированный
поставляемый
0
vendor/github.com/splitio/go-toolkit/v3/common/sliceutil.go → vendor/github.com/splitio/go-toolkit/v4/common/sliceutil.go
сгенерированный
поставляемый
0
vendor/github.com/splitio/go-toolkit/v3/common/sliceutil.go → vendor/github.com/splitio/go-toolkit/v4/common/sliceutil.go
сгенерированный
поставляемый
0
vendor/github.com/splitio/go-toolkit/v3/common/strutil.go → vendor/github.com/splitio/go-toolkit/v4/common/strutil.go
сгенерированный
поставляемый
0
vendor/github.com/splitio/go-toolkit/v3/common/strutil.go → vendor/github.com/splitio/go-toolkit/v4/common/strutil.go
сгенерированный
поставляемый
0
vendor/github.com/splitio/go-toolkit/v3/common/timeutil.go → vendor/github.com/splitio/go-toolkit/v4/common/timeutil.go
сгенерированный
поставляемый
0
vendor/github.com/splitio/go-toolkit/v3/common/timeutil.go → vendor/github.com/splitio/go-toolkit/v4/common/timeutil.go
сгенерированный
поставляемый
0
vendor/github.com/splitio/go-toolkit/v3/logging/functions.go → vendor/github.com/splitio/go-toolkit/v4/logging/functions.go
сгенерированный
поставляемый
0
vendor/github.com/splitio/go-toolkit/v3/logging/functions.go → vendor/github.com/splitio/go-toolkit/v4/logging/functions.go
сгенерированный
поставляемый
0
vendor/github.com/splitio/go-toolkit/v3/logging/interface.go → vendor/github.com/splitio/go-toolkit/v4/logging/interface.go
сгенерированный
поставляемый
0
vendor/github.com/splitio/go-toolkit/v3/logging/interface.go → vendor/github.com/splitio/go-toolkit/v4/logging/interface.go
сгенерированный
поставляемый
0
vendor/github.com/splitio/go-toolkit/v3/logging/levels.go → vendor/github.com/splitio/go-toolkit/v4/logging/levels.go
сгенерированный
поставляемый
0
vendor/github.com/splitio/go-toolkit/v3/logging/levels.go → vendor/github.com/splitio/go-toolkit/v4/logging/levels.go
сгенерированный
поставляемый
0
vendor/github.com/splitio/go-toolkit/v3/logging/logging.go → vendor/github.com/splitio/go-toolkit/v4/logging/logging.go
сгенерированный
поставляемый
0
vendor/github.com/splitio/go-toolkit/v3/logging/logging.go → vendor/github.com/splitio/go-toolkit/v4/logging/logging.go
сгенерированный
поставляемый
0
vendor/github.com/splitio/go-toolkit/v3/logging/rotate.go → vendor/github.com/splitio/go-toolkit/v4/logging/rotate.go
сгенерированный
поставляемый
0
vendor/github.com/splitio/go-toolkit/v3/logging/rotate.go → vendor/github.com/splitio/go-toolkit/v4/logging/rotate.go
сгенерированный
поставляемый
0
vendor/github.com/splitio/go-toolkit/v3/nethelpers/ip.go → vendor/github.com/splitio/go-toolkit/v4/nethelpers/ip.go
сгенерированный
поставляемый
0
vendor/github.com/splitio/go-toolkit/v3/nethelpers/ip.go → vendor/github.com/splitio/go-toolkit/v4/nethelpers/ip.go
сгенерированный
поставляемый
0
vendor/github.com/splitio/go-toolkit/v3/queuecache/cache.go → vendor/github.com/splitio/go-toolkit/v4/queuecache/cache.go
сгенерированный
поставляемый
0
vendor/github.com/splitio/go-toolkit/v3/queuecache/cache.go → vendor/github.com/splitio/go-toolkit/v4/queuecache/cache.go
сгенерированный
поставляемый
@@ -3,7 +3,7 @@ package helpers
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/splitio/go-toolkit/v3/redis"
|
||||
"github.com/splitio/go-toolkit/v4/redis"
|
||||
)
|
||||
|
||||
const (
|
||||
0
vendor/github.com/splitio/go-toolkit/v3/redis/types.go → vendor/github.com/splitio/go-toolkit/v4/redis/types.go
сгенерированный
поставляемый
0
vendor/github.com/splitio/go-toolkit/v3/redis/types.go → vendor/github.com/splitio/go-toolkit/v4/redis/types.go
сгенерированный
поставляемый
0
vendor/github.com/splitio/go-toolkit/v3/redis/wrapper.go → vendor/github.com/splitio/go-toolkit/v4/redis/wrapper.go
сгенерированный
поставляемый
0
vendor/github.com/splitio/go-toolkit/v3/redis/wrapper.go → vendor/github.com/splitio/go-toolkit/v4/redis/wrapper.go
сгенерированный
поставляемый
31
vendor/github.com/splitio/go-toolkit/v4/sse/errors.go
сгенерированный
поставляемый
Обычный файл
31
vendor/github.com/splitio/go-toolkit/v4/sse/errors.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,31 @@
|
||||
package sse
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// ErrNotIdle is the error tor eturn when Do() gets called on an already running client.
|
||||
var ErrNotIdle = errors.New("sse client already running")
|
||||
|
||||
// ErrReadingStream is the error to return when channel event channel is closed because of an error reading the stream
|
||||
var ErrReadingStream = errors.New("sse channel closed")
|
||||
|
||||
// ErrTimeout is the error to return when keepalive timeout is exceeded
|
||||
var ErrTimeout = errors.New("timeout exceeeded")
|
||||
|
||||
// ErrConnectionFailed contains a nested error
|
||||
type ErrConnectionFailed struct {
|
||||
wrapped error
|
||||
}
|
||||
|
||||
// Error returns the error as a string
|
||||
func (e *ErrConnectionFailed) Error() string {
|
||||
return "error connecting: " + e.wrapped.Error()
|
||||
}
|
||||
|
||||
// Unwrap returns the wrapped error
|
||||
func (e *ErrConnectionFailed) Unwrap() error {
|
||||
return e.wrapped
|
||||
}
|
||||
|
||||
var _ error = &ErrConnectionFailed{}
|
||||
120
vendor/github.com/splitio/go-toolkit/v4/sse/event.go
сгенерированный
поставляемый
Обычный файл
120
vendor/github.com/splitio/go-toolkit/v4/sse/event.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,120 @@
|
||||
package sse
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
sseDelimiter = ":"
|
||||
sseData = "data"
|
||||
sseEvent = "event"
|
||||
sseID = "id"
|
||||
sseRetry = "retry"
|
||||
)
|
||||
|
||||
// RawEvent interface contains the methods that expose the incoming SSE properties
|
||||
type RawEvent interface {
|
||||
ID() string
|
||||
Event() string
|
||||
Data() string
|
||||
Retry() int64
|
||||
IsError() bool
|
||||
IsEmpty() bool
|
||||
}
|
||||
|
||||
// RawEventImpl represents an incoming SSE event
|
||||
type RawEventImpl struct {
|
||||
id string
|
||||
event string
|
||||
data string
|
||||
retry int64
|
||||
}
|
||||
|
||||
// ID returns the event id
|
||||
func (r *RawEventImpl) ID() string { return r.id }
|
||||
|
||||
// Event returns the event type
|
||||
func (r *RawEventImpl) Event() string { return r.event }
|
||||
|
||||
// Data returns the event associated data
|
||||
func (r *RawEventImpl) Data() string { return r.data }
|
||||
|
||||
// Retry returns the expected retry time
|
||||
func (r *RawEventImpl) Retry() int64 { return r.retry }
|
||||
|
||||
// IsError returns true if the message is an error
|
||||
func (r *RawEventImpl) IsError() bool { return r.event == "error" }
|
||||
|
||||
// IsEmpty returns true if the event contains no id, event type and data
|
||||
func (r *RawEventImpl) IsEmpty() bool { return r.event == "" && r.id == "" && r.data == "" }
|
||||
|
||||
// EventBuilder interface
|
||||
type EventBuilder interface {
|
||||
AddLine(string)
|
||||
Build() *RawEventImpl
|
||||
}
|
||||
|
||||
// EventBuilderImpl implenets the EventBuilder interface. Used to parse incoming event lines
|
||||
type EventBuilderImpl struct {
|
||||
includesComment bool
|
||||
mutex sync.Mutex
|
||||
lines []string
|
||||
}
|
||||
|
||||
// AddLine adds a new line belonging to the currently being processed event
|
||||
func (b *EventBuilderImpl) AddLine(line string) {
|
||||
if strings.HasPrefix(line, sseDelimiter) {
|
||||
// Ignore comments
|
||||
return
|
||||
}
|
||||
|
||||
b.mutex.Lock()
|
||||
defer b.mutex.Unlock()
|
||||
b.lines = append(b.lines, line)
|
||||
}
|
||||
|
||||
// Build processes all the added lines and builds the event
|
||||
func (b *EventBuilderImpl) Build() *RawEventImpl {
|
||||
b.mutex.Lock()
|
||||
defer b.mutex.Unlock()
|
||||
|
||||
if len(b.lines) == 0 { // Empty event
|
||||
return &RawEventImpl{}
|
||||
}
|
||||
|
||||
e := &RawEventImpl{}
|
||||
for _, line := range b.lines {
|
||||
splitted := strings.SplitN(line, sseDelimiter, 2)
|
||||
if len(splitted) != 2 {
|
||||
// TODO: log invalid line.
|
||||
continue
|
||||
}
|
||||
|
||||
switch splitted[0] {
|
||||
case sseID:
|
||||
e.id = strings.TrimSpace(splitted[1])
|
||||
case sseData:
|
||||
e.data = strings.TrimSpace(splitted[1])
|
||||
case sseEvent:
|
||||
e.event = strings.TrimSpace(splitted[1])
|
||||
case sseRetry:
|
||||
e.retry, _ = strconv.ParseInt(strings.TrimSpace(splitted[1]), 10, 64)
|
||||
}
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// Reset clears the lines accepted
|
||||
func (b *EventBuilderImpl) Reset() {
|
||||
b.mutex.Lock()
|
||||
defer b.mutex.Unlock()
|
||||
b.lines = []string{}
|
||||
}
|
||||
|
||||
// NewEventBuilder constructs a new event builder
|
||||
func NewEventBuilder() *EventBuilderImpl {
|
||||
return &EventBuilderImpl{lines: []string{}}
|
||||
}
|
||||
174
vendor/github.com/splitio/go-toolkit/v4/sse/sse.go
сгенерированный
поставляемый
Обычный файл
174
vendor/github.com/splitio/go-toolkit/v4/sse/sse.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,174 @@
|
||||
package sse
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
"github.com/splitio/go-toolkit/v4/struct/traits/lifecycle"
|
||||
)
|
||||
|
||||
const (
|
||||
statusIdle = iota
|
||||
statusRunning
|
||||
statusShuttingDown
|
||||
|
||||
endOfLineChar = '\n'
|
||||
endOfLineStr = "\n"
|
||||
)
|
||||
|
||||
// Client struct
|
||||
type Client struct {
|
||||
lifecycle lifecycle.Manager
|
||||
url string
|
||||
client http.Client
|
||||
timeout time.Duration
|
||||
logger logging.LoggerInterface
|
||||
}
|
||||
|
||||
// NewClient creates new SSEClient
|
||||
func NewClient(url string, timeout int, logger logging.LoggerInterface) (*Client, error) {
|
||||
if timeout < 1 {
|
||||
return nil, errors.New("Timeout should be higher than 0")
|
||||
}
|
||||
|
||||
client := &Client{
|
||||
url: url,
|
||||
client: http.Client{},
|
||||
timeout: time.Duration(timeout) * time.Second,
|
||||
logger: logger,
|
||||
}
|
||||
client.lifecycle.Setup()
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (l *Client) readEvents(in *bufio.Reader, out chan<- RawEvent) {
|
||||
eventBuilder := NewEventBuilder()
|
||||
for {
|
||||
line, err := in.ReadString(endOfLineChar)
|
||||
l.logger.Debug("Incoming SSE line: ", line)
|
||||
if err != nil {
|
||||
if l.lifecycle.IsRunning() { // If it's supposed to be running, log an error
|
||||
l.logger.Error(err)
|
||||
}
|
||||
close(out)
|
||||
return
|
||||
}
|
||||
if line != endOfLineStr {
|
||||
eventBuilder.AddLine(line)
|
||||
continue
|
||||
|
||||
}
|
||||
l.logger.Debug("Building SSE event")
|
||||
if event := eventBuilder.Build(); event != nil {
|
||||
out <- event
|
||||
}
|
||||
eventBuilder.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
// Do starts streaming
|
||||
func (l *Client) Do(params map[string]string, callback func(e RawEvent)) error {
|
||||
|
||||
if !l.lifecycle.BeginInitialization() {
|
||||
return ErrNotIdle
|
||||
}
|
||||
|
||||
activeGoroutines := sync.WaitGroup{}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer func() {
|
||||
l.logger.Info("SSE streaming exiting")
|
||||
cancel()
|
||||
activeGoroutines.Wait()
|
||||
l.lifecycle.ShutdownComplete()
|
||||
}()
|
||||
|
||||
req, err := l.buildCancellableRequest(ctx, params)
|
||||
if err != nil {
|
||||
return &ErrConnectionFailed{wrapped: fmt.Errorf("error building request: %w", err)}
|
||||
}
|
||||
|
||||
resp, err := l.client.Do(req)
|
||||
if err != nil {
|
||||
return &ErrConnectionFailed{wrapped: fmt.Errorf("error issuing request: %w", err)}
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
return &ErrConnectionFailed{wrapped: fmt.Errorf("sse request status code: %d", resp.StatusCode)}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if !l.lifecycle.InitializationComplete() {
|
||||
return nil
|
||||
}
|
||||
|
||||
reader := bufio.NewReader(resp.Body)
|
||||
eventChannel := make(chan RawEvent, 1000)
|
||||
go l.readEvents(reader, eventChannel)
|
||||
|
||||
// Create timeout timer in case SSE dont receive notifications or keepalive messages
|
||||
keepAliveTimer := time.NewTimer(l.timeout)
|
||||
defer keepAliveTimer.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-l.lifecycle.ShutdownRequested():
|
||||
l.logger.Info("Shutting down listener")
|
||||
return nil
|
||||
case event, ok := <-eventChannel:
|
||||
keepAliveTimer.Reset(l.timeout)
|
||||
if !ok {
|
||||
if l.lifecycle.IsRunning() {
|
||||
return ErrReadingStream
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if event.IsEmpty() {
|
||||
continue // don't forward empty/comment events
|
||||
}
|
||||
activeGoroutines.Add(1)
|
||||
go func() {
|
||||
defer activeGoroutines.Done()
|
||||
callback(event)
|
||||
}()
|
||||
case <-keepAliveTimer.C: // Timeout
|
||||
l.logger.Warning("SSE idle timeout.")
|
||||
l.lifecycle.AbnormalShutdown()
|
||||
return ErrTimeout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shutdown stops SSE
|
||||
func (l *Client) Shutdown(blocking bool) {
|
||||
if !l.lifecycle.BeginShutdown() {
|
||||
l.logger.Info("SSE client stopped or shutdown in progress. Ignoring.")
|
||||
return
|
||||
}
|
||||
|
||||
if blocking {
|
||||
l.lifecycle.AwaitShutdownComplete()
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Client) buildCancellableRequest(ctx context.Context, params map[string]string) (*http.Request, error) {
|
||||
req, err := http.NewRequest("GET", l.url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error instantiating request: %w", err)
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
query := req.URL.Query()
|
||||
|
||||
for key, value := range params {
|
||||
query.Add(key, value)
|
||||
}
|
||||
req.URL.RawQuery = query.Encode()
|
||||
req.Header.Set("Accept", "text/event-stream")
|
||||
return req, nil
|
||||
}
|
||||
108
vendor/github.com/splitio/go-toolkit/v4/struct/traits/lifecycle/lifecycle.go
сгенерированный
поставляемый
Обычный файл
108
vendor/github.com/splitio/go-toolkit/v4/struct/traits/lifecycle/lifecycle.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,108 @@
|
||||
package lifecycle
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// Status constants
|
||||
const (
|
||||
StatusIdle = iota
|
||||
StatusStarting
|
||||
StatusInitializationCancelled
|
||||
StatusRunning
|
||||
StatusStopping
|
||||
)
|
||||
|
||||
// Status type alias
|
||||
type Status = int32
|
||||
|
||||
// Manager is a trait to be embedded in structs that manage the lifecycle of goroutines.
|
||||
// The trait enables the struct to easily switch between states and await proper shutdown
|
||||
type Manager struct {
|
||||
status int32
|
||||
c *sync.Cond
|
||||
shutdown chan struct{}
|
||||
}
|
||||
|
||||
// Setup must be called in the struct constructor
|
||||
func (l *Manager) Setup() {
|
||||
l.c = sync.NewCond(&sync.Mutex{})
|
||||
l.shutdown = make(chan struct{}, 1)
|
||||
}
|
||||
|
||||
// BeginInitialization should be called in the .Start() method (or whichever begins the async work)
|
||||
func (l *Manager) BeginInitialization() bool {
|
||||
return atomic.CompareAndSwapInt32(&l.status, StatusIdle, StatusStarting)
|
||||
}
|
||||
|
||||
// InitializationComplete should be called just prior to the `go ...` directive starting the async work
|
||||
func (l *Manager) InitializationComplete() bool {
|
||||
if !atomic.CompareAndSwapInt32(&l.status, StatusStarting, StatusRunning) {
|
||||
atomic.StoreInt32(&l.status, StatusStopping)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// BeginShutdown should be called on the .Stop() method or whichever makes a request for the async work to stop
|
||||
func (l *Manager) BeginShutdown() bool {
|
||||
// If we're currently initializing but not yet running, just change the status.
|
||||
if atomic.CompareAndSwapInt32(&l.status, StatusStarting, StatusInitializationCancelled) {
|
||||
return true
|
||||
}
|
||||
|
||||
if !atomic.CompareAndSwapInt32(&l.status, StatusRunning, StatusStopping) {
|
||||
return false
|
||||
}
|
||||
|
||||
l.shutdown <- struct{}{}
|
||||
return true
|
||||
}
|
||||
|
||||
// ShutdownComplete should be called just before the goroutine exits. (ie: it should be the FIRST deferred func)
|
||||
func (l *Manager) ShutdownComplete() {
|
||||
// clean up status channel in case a Stop occurred while the task was exiting on its own
|
||||
select {
|
||||
case <-l.shutdown:
|
||||
default:
|
||||
}
|
||||
|
||||
l.c.L.Lock()
|
||||
atomic.StoreInt32(&l.status, StatusIdle)
|
||||
l.c.Broadcast()
|
||||
l.c.L.Unlock()
|
||||
}
|
||||
|
||||
// AwaitShutdownComplete can be called in case you need to join against the goroutine's end
|
||||
func (l *Manager) AwaitShutdownComplete() {
|
||||
for {
|
||||
l.c.L.Lock()
|
||||
if atomic.LoadInt32(&l.status) == StatusIdle {
|
||||
l.c.L.Unlock()
|
||||
return
|
||||
}
|
||||
l.c.Wait()
|
||||
l.c.L.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// ShutdownRequested should be queried in a select statement, which should react by terminating the goroutine
|
||||
func (l *Manager) ShutdownRequested() <-chan struct{} {
|
||||
return l.shutdown
|
||||
}
|
||||
|
||||
// AbnormalShutdown should be called when the goroutine exits without Stop being called.
|
||||
func (l *Manager) AbnormalShutdown() {
|
||||
atomic.CompareAndSwapInt32(&l.status, StatusRunning, StatusStopping)
|
||||
}
|
||||
|
||||
// Status Returns the current status as an int32 constant
|
||||
func (l *Manager) Status() int32 {
|
||||
return atomic.LoadInt32(&l.status)
|
||||
}
|
||||
|
||||
// IsRunning returns true if the BG work is still going on
|
||||
func (l *Manager) IsRunning() bool {
|
||||
return atomic.LoadInt32(&l.status) == StatusRunning
|
||||
}
|
||||
41
vendor/github.com/splitio/go-toolkit/v4/sync/atomicbool.go
сгенерированный
поставляемый
Обычный файл
41
vendor/github.com/splitio/go-toolkit/v4/sync/atomicbool.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,41 @@
|
||||
package sync
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
const (
|
||||
falseValue = 0
|
||||
trueValue = 1
|
||||
)
|
||||
|
||||
type AtomicBool struct {
|
||||
value uint32
|
||||
}
|
||||
|
||||
func (b *AtomicBool) Set() {
|
||||
atomic.StoreUint32(&b.value, trueValue)
|
||||
}
|
||||
|
||||
func (b *AtomicBool) Unset() {
|
||||
atomic.StoreUint32(&b.value, falseValue)
|
||||
}
|
||||
|
||||
func (b *AtomicBool) IsSet() bool {
|
||||
return atomic.LoadUint32(&b.value) == trueValue
|
||||
}
|
||||
|
||||
func (b *AtomicBool) TestAndSet() bool {
|
||||
return atomic.CompareAndSwapUint32(&b.value, falseValue, trueValue)
|
||||
}
|
||||
|
||||
func (b *AtomicBool) TestAndClear() bool {
|
||||
return atomic.CompareAndSwapUint32(&b.value, trueValue, falseValue)
|
||||
}
|
||||
|
||||
func NewAtomicBool(initialValue bool) *AtomicBool {
|
||||
if initialValue {
|
||||
return &AtomicBool{value: trueValue}
|
||||
}
|
||||
return &AtomicBool{}
|
||||
}
|
||||
149
vendor/github.com/splitio/go-toolkit/v3/workerpool/workerpool.go → vendor/github.com/splitio/go-toolkit/v4/workerpool/workerpool.go
сгенерированный
поставляемый
149
vendor/github.com/splitio/go-toolkit/v3/workerpool/workerpool.go → vendor/github.com/splitio/go-toolkit/v4/workerpool/workerpool.go
сгенерированный
поставляемый
@@ -2,9 +2,11 @@ package workerpool
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/splitio/go-toolkit/v3/logging"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
"github.com/splitio/go-toolkit/v4/struct/traits/lifecycle"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -13,11 +15,11 @@ const (
|
||||
|
||||
// WorkerAdmin struct handles multiple worker execution, popping jobs from a single queue
|
||||
type WorkerAdmin struct {
|
||||
queue chan interface{}
|
||||
signalsMutex sync.RWMutex
|
||||
signals map[string]chan int
|
||||
logger logging.LoggerInterface
|
||||
wg sync.WaitGroup
|
||||
queue chan interface{}
|
||||
mutex sync.RWMutex
|
||||
//signals map[string]chan int
|
||||
workers map[string]*workerWrapper
|
||||
logger logging.LoggerInterface
|
||||
}
|
||||
|
||||
// Worker interface should be implemented by concrete workers that will perform the actual job
|
||||
@@ -34,53 +36,77 @@ type Worker interface {
|
||||
FailureTime() int64
|
||||
}
|
||||
|
||||
func (a *WorkerAdmin) workerWrapper(w Worker) {
|
||||
a.signalsMutex.Lock()
|
||||
a.signals[w.Name()] = make(chan int, 10)
|
||||
a.signalsMutex.Unlock()
|
||||
defer a.wg.Done()
|
||||
type workerWrapper struct {
|
||||
w Worker
|
||||
lifecycle lifecycle.Manager
|
||||
queue <-chan interface{}
|
||||
logger logging.LoggerInterface
|
||||
}
|
||||
|
||||
func (w *workerWrapper) Start() {
|
||||
if !w.lifecycle.BeginInitialization() {
|
||||
w.logger.Error(fmt.Sprintf("initialization of worker '%s' aborted. Worker not idle.", w.w.Name()))
|
||||
return
|
||||
}
|
||||
go w.do()
|
||||
}
|
||||
|
||||
func (w *workerWrapper) Stop(blocking bool) {
|
||||
if !w.lifecycle.BeginShutdown() {
|
||||
w.logger.Error(fmt.Sprintf("shutodwn of worker '%s' aborted. Worker not running.", w.w.Name()))
|
||||
return
|
||||
}
|
||||
|
||||
if blocking {
|
||||
w.lifecycle.AwaitShutdownComplete()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *workerWrapper) do() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
a.logger.Error(fmt.Sprintf(
|
||||
w.logger.Error(fmt.Sprintf(
|
||||
"Worker %s is panicking with the following error \"%s\" and will be shutted down.",
|
||||
w.Name(),
|
||||
w.w.Name(),
|
||||
r,
|
||||
))
|
||||
}
|
||||
if a.signals != nil { // This should ALWAYS be the case, but just in case... we don't want to panic here.
|
||||
a.signalsMutex.Lock()
|
||||
delete(a.signals, w.Name())
|
||||
a.signalsMutex.Unlock()
|
||||
w.lifecycle.AbnormalShutdown()
|
||||
}
|
||||
}()
|
||||
defer w.Cleanup()
|
||||
defer w.lifecycle.ShutdownComplete()
|
||||
defer w.w.Cleanup()
|
||||
if !w.lifecycle.InitializationComplete() {
|
||||
return
|
||||
}
|
||||
for {
|
||||
a.signalsMutex.RLock()
|
||||
signal := a.signals[w.Name()]
|
||||
a.signalsMutex.RUnlock()
|
||||
select {
|
||||
case msg := <-signal:
|
||||
switch msg {
|
||||
case workerSignalStop:
|
||||
return
|
||||
}
|
||||
case msg := <-a.queue:
|
||||
if err := w.DoWork(msg); err != nil {
|
||||
w.OnError(err)
|
||||
time.Sleep(time.Duration(w.FailureTime()) * time.Millisecond)
|
||||
case <-w.lifecycle.ShutdownRequested():
|
||||
return
|
||||
case msg := <-w.queue:
|
||||
if err := w.w.DoWork(msg); err != nil {
|
||||
w.w.OnError(err)
|
||||
time.Sleep(time.Duration(w.w.FailureTime()) * time.Millisecond)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newWorkerWraper(w Worker, logger logging.LoggerInterface, queue <-chan interface{}) *workerWrapper {
|
||||
worker := &workerWrapper{w: w, queue: queue, logger: logger}
|
||||
worker.lifecycle.Setup()
|
||||
worker.Start()
|
||||
return worker
|
||||
}
|
||||
|
||||
// AddWorker registers a new worker in the admin
|
||||
func (a *WorkerAdmin) AddWorker(w Worker) {
|
||||
if w == nil {
|
||||
a.logger.Error("AddWorker called with nil")
|
||||
return
|
||||
}
|
||||
a.wg.Add(1)
|
||||
go a.workerWrapper(w)
|
||||
a.mutex.Lock()
|
||||
defer a.mutex.Unlock()
|
||||
a.workers[w.Name()] = newWorkerWraper(w, a.logger, a.queue)
|
||||
}
|
||||
|
||||
// QueueMessage adds a new message that will be popped by a worker and processed
|
||||
@@ -98,48 +124,37 @@ func (a *WorkerAdmin) QueueMessage(m interface{}) bool {
|
||||
}
|
||||
|
||||
// StopWorker ends the worker's event loop, preventing it from picking further jobs
|
||||
func (a *WorkerAdmin) StopWorker(name string) error {
|
||||
a.signalsMutex.RLock()
|
||||
c, ok := a.signals[name]
|
||||
a.signalsMutex.RUnlock()
|
||||
func (a *WorkerAdmin) StopWorker(name string, blocking bool) error {
|
||||
a.mutex.Lock()
|
||||
defer a.mutex.Unlock()
|
||||
w, ok := a.workers[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("Worker %s doesn't exist, hence it cannot be stopped", name)
|
||||
}
|
||||
select {
|
||||
case c <- workerSignalStop:
|
||||
default:
|
||||
return fmt.Errorf("Couldn't send stop signal to worker %s", name)
|
||||
}
|
||||
|
||||
w.Stop(blocking)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopAll ends all worker's event loops
|
||||
func (a *WorkerAdmin) StopAll(blocking bool) error {
|
||||
failed := make([]string, 0)
|
||||
workerNames := make([]string, 0)
|
||||
|
||||
// Get worker names safely
|
||||
a.signalsMutex.RLock()
|
||||
for workerName := range a.signals {
|
||||
workerNames = append(workerNames, workerName)
|
||||
}
|
||||
a.signalsMutex.RUnlock()
|
||||
|
||||
for _, workerName := range workerNames {
|
||||
err := a.StopWorker(workerName)
|
||||
if err != nil {
|
||||
a.logger.Error(err)
|
||||
failed = append(failed, workerName)
|
||||
wg := sync.WaitGroup{}
|
||||
a.mutex.Lock()
|
||||
defer a.mutex.Unlock()
|
||||
for _, w := range a.workers {
|
||||
if w != nil {
|
||||
wg.Add(1)
|
||||
go func(current *workerWrapper) {
|
||||
current.Stop(true)
|
||||
wg.Done()
|
||||
}(w)
|
||||
}
|
||||
}
|
||||
if len(failed) > 0 {
|
||||
return fmt.Errorf("Workers %v failed to shutdown", failed)
|
||||
}
|
||||
|
||||
if blocking {
|
||||
a.wg.Wait()
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -150,16 +165,16 @@ func (a *WorkerAdmin) QueueSize() int {
|
||||
|
||||
// IsWorkerRunning returns true if the worker exists and is currently running
|
||||
func (a *WorkerAdmin) IsWorkerRunning(name string) bool {
|
||||
a.signalsMutex.RLock()
|
||||
_, ok := a.signals[name]
|
||||
a.signalsMutex.RUnlock()
|
||||
return ok // We consider a worker to be running if it exists in the list of valid signal channels
|
||||
a.mutex.RLock()
|
||||
defer a.mutex.RUnlock()
|
||||
x, ok := a.workers[name]
|
||||
return ok && x.lifecycle.IsRunning()
|
||||
}
|
||||
|
||||
// NewWorkerAdmin instantiates a new WorkerAdmin and returns a pointer to it.
|
||||
func NewWorkerAdmin(queueSize int, logger logging.LoggerInterface) *WorkerAdmin {
|
||||
return &WorkerAdmin{
|
||||
signals: make(map[string]chan int, 0),
|
||||
workers: make(map[string]*workerWrapper, 0),
|
||||
logger: logger,
|
||||
queue: make(chan interface{}, queueSize),
|
||||
}
|
||||
Ссылка в новой задаче
Block a user