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
Этот коммит содержится в:
Joram Wilander
2021-03-04 11:16:08 -05:00
коммит произвёл GitHub
родитель fa2ecad0a9
Коммит aba00a3cfd
150 изменённых файлов: 2500 добавлений и 1960 удалений

13
vendor/github.com/splitio/go-toolkit/v4/LICENSE сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,13 @@
Copyright © 2020 Split Software, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

150
vendor/github.com/splitio/go-toolkit/v4/asynctask/asynctasks.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,150 @@
package asynctask
import (
"fmt"
"time"
"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 {
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 (
taskMessageWakeup = iota
)
// 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.lifecycle.BeginInitialization() {
if t.logger != nil {
t.logger.Warning(fmt.Sprintf("Task %s is not idle. Aborting new execution.", t.name))
}
return
}
go func() {
defer func() {
if r := recover(); r != nil {
if t.logger != nil {
t.logger.Error(fmt.Sprintf(
"AsyncTask %s is panicking! shutting down. Consider restarting this instance and raising an issue",
t.name,
))
t.logger.Error(r)
}
}
}()
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 t.logger != nil {
t.logger.Error(err.Error())
}
t.lifecycle.AbnormalShutdown()
return
}
}
// Create timeout timer
idleDuration := time.Second * time.Duration(t.period)
taskTimer := time.NewTimer(idleDuration)
defer taskTimer.Stop()
if t.onStop != nil {
defer t.onStop(t.logger)
}
// Task execution
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 {
t.logger.Error(err.Error())
}
// Resetting timer
taskTimer.Reset(idleDuration)
}
}()
}
func (t *AsyncTask) sendSignal(signal int) error {
select {
case t.incoming <- signal:
return nil
default:
return fmt.Errorf("Couldn't send message to task %s", t.name)
}
}
// 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.lifecycle.BeginShutdown() {
return fmt.Errorf("task '%s' not running", t.name)
}
if blocking {
t.lifecycle.AwaitShutdownComplete()
}
return nil
}
// WakeUp interrupts the task's sleep period and resumes execution
func (t *AsyncTask) WakeUp() error {
return t.sendSignal(taskMessageWakeup)
}
// IsRunning returns true if the task is currently running
func (t *AsyncTask) IsRunning() bool {
return t.lifecycle.IsRunning()
}
// NewAsyncTask creates a new task and returns a pointer to it
func NewAsyncTask(
name string,
task func(l logging.LoggerInterface) error,
period int,
onInit func(l logging.LoggerInterface) error,
onStop func(l logging.LoggerInterface),
logger logging.LoggerInterface,
) *AsyncTask {
t := AsyncTask{
name: name,
task: task,
period: period,
onInit: onInit,
onStop: onStop,
logger: logger,
incoming: make(chan int, 10),
}
t.lifecycle.Setup()
return &t
}

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}
}

53
vendor/github.com/splitio/go-toolkit/v4/common/interface.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,53 @@
package common
// AsIntOrNil returns ref
func AsIntOrNil(data interface{}) *int {
if data == nil {
return nil
}
number, ok := data.(int)
if !ok {
return nil
}
return IntRef(number)
}
// AsInt64OrNil returns ref
func AsInt64OrNil(data interface{}) *int64 {
if data == nil {
return nil
}
number, ok := data.(int64)
if !ok {
return nil
}
return Int64Ref(number)
}
// AsFloat64OrNil return ref
func AsFloat64OrNil(data interface{}) *float64 {
if data == nil {
return nil
}
number, ok := data.(float64)
if !ok {
return nil
}
return Float64Ref(number)
}
// AsStringOrNil returns ref
func AsStringOrNil(data interface{}) *string {
if data == nil {
return nil
}
str, ok := data.(string)
if !ok {
return nil
}
return StringRef(str)
}

67
vendor/github.com/splitio/go-toolkit/v4/common/iterutil.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,67 @@
package common
import (
"errors"
"math"
"time"
)
// WithAttempts executes a function N times or until no error is returned
func WithAttempts(attempts int, main func() error) error {
err := errors.New("")
remaining := attempts
for err != nil && remaining > 0 {
remaining--
err = main()
}
return err
}
// WithBackoff wraps the function to add Exponential backoff
func WithBackoff(duration time.Duration, main func() error) func() error {
var count time.Duration = 1
return func() error {
err := main()
if err != nil {
time.Sleep(count * duration)
count++
} else {
count = 0
}
return main()
}
}
// WithBackoffCancelling wraps the function to add Exponential backoff
func WithBackoffCancelling(unit time.Duration, max time.Duration, main func() bool) func() {
cancel := make(chan struct{})
go func() {
attempts := 0
isDone := main()
// Create timeout timer for backoff
backoffTimer := time.NewTimer(MinDuration(time.Duration(math.Pow(2, float64(attempts)))*unit, max))
defer backoffTimer.Stop()
for !isDone {
attempts++
// Setting timer considerint attempts
backoffTimer.Reset(MinDuration(time.Duration(math.Pow(2, float64(attempts)))*unit, max))
select {
case <-cancel:
return
case <-backoffTimer.C: // Timedout
isDone = main()
}
}
}()
return func() {
select {
case cancel <- struct{}{}:
return
default:
}
}
}

74
vendor/github.com/splitio/go-toolkit/v4/common/refutil.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,74 @@
package common
// StringRef returns ref
func StringRef(str string) *string {
return &str
}
// StringFromRef returns original value if not empty. Default otherwise.
func StringFromRef(str *string) string {
if str == nil {
return ""
}
return *str
}
// IntRef returns ref
func IntRef(number int) *int {
return &number
}
// IntFromRef returns 0 if nil, dereferenced value otherwhise.
func IntFromRef(ref *int) int {
if ref == nil {
return 0
}
return *ref
}
// Int64Ref returns ref
func Int64Ref(number int64) *int64 {
return &number
}
// Int64FromRef returns value
func Int64FromRef(number *int64) int64 {
if number == nil {
return 0
}
return *number
}
// Int64Value kept to prevent breaking change. TODO: Deprecate in v4
func Int64Value(number *int64) int64 {
return Int64FromRef(number)
}
// Float64Ref returns ref
func Float64Ref(number float64) *float64 {
return &number
}
// IntRefOrNil returns ref
func IntRefOrNil(number int) *int {
if number == 0 {
return nil
}
return IntRef(number)
}
// Int64RefOrNil returns ref
func Int64RefOrNil(number int64) *int64 {
if number == 0 {
return nil
}
return Int64Ref(number)
}
// StringRefOrNil returns ref
func StringRefOrNil(str string) *string {
if str == "" {
return nil
}
return StringRef(str)
}

18
vendor/github.com/splitio/go-toolkit/v4/common/sliceutil.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,18 @@
package common
// Partition create partitions considering the passed amount
func Partition(items []string, maxItems int) [][]string {
var splitted [][]string
for i := 0; i < len(items); i += maxItems {
end := i + maxItems
if end > len(items) {
end = len(items)
}
splitted = append(splitted, items[i:end])
}
return splitted
}

9
vendor/github.com/splitio/go-toolkit/v4/common/strutil.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,9 @@
package common
// StringValueOrDefault returns original value if not empty. Default otherwise.
func StringValueOrDefault(str string, def string) string {
if str != "" {
return str
}
return def
}

25
vendor/github.com/splitio/go-toolkit/v4/common/timeutil.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,25 @@
package common
import "time"
// MinDuration returns the min duration among them
func MinDuration(d1, d2 time.Duration, ds ...time.Duration) time.Duration {
min := d1
for _, d := range append(ds, d2) {
if d < min {
min = d
}
}
return min
}
// MaxDuration returns the max duration among them
func MaxDuration(d1, d2 time.Duration, ds ...time.Duration) time.Duration {
max := d1
for _, d := range append(ds, d2) {
if d > max {
max = d
}
}
return max
}

56
vendor/github.com/splitio/go-toolkit/v4/datastructures/set/functions.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,56 @@
package set
// Union calculates the union of two or more sets
func Union(set1, set2 Set, sets ...Set) Set {
u := set1.Copy()
set2.Each(func(item interface{}) bool {
u.Add(item)
return true
})
for _, set := range sets {
set.Each(func(item interface{}) bool {
u.Add(item)
return true
})
}
return u
}
// Intersection calculates the intersection of two or more sets
func Intersection(set1, set2 Set, sets ...Set) Set {
all := Union(set1, set2, sets...)
result := Union(set1, set2, sets...)
all.Each(func(item interface{}) bool {
if !set1.Has(item) || !set2.Has(item) {
result.Remove(item)
}
for _, set := range sets {
if !set.Has(item) {
result.Remove(item)
}
}
return true
})
return result
}
// Difference calculates the difference of two or more sets
func Difference(set1, set2 Set, sets ...Set) Set {
s := set1.Copy()
s.Separate(set2)
for _, set := range sets {
s.Separate(set) // seperate is thread safe
}
return s
}
// SymmetricDifference calculates the symmetric difference of two or more sets
func SymmetricDifference(s Set, t Set) Set {
u := Difference(s, t)
v := Difference(t, s)
return Union(u, v)
}

355
vendor/github.com/splitio/go-toolkit/v4/datastructures/set/implementations.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,355 @@
package set
import (
"sync"
)
var keyExists = struct{}{} // Value that indicates existance in the set for the key element
// ThreadUnsafeSet structure. Container of unique items with O(1) access time. NOT THREAD SAFE
type ThreadUnsafeSet struct {
set
}
// NewSet Constructs a new set from an optinal slice of items
func NewSet(items ...interface{}) *ThreadUnsafeSet {
s := &ThreadUnsafeSet{}
s.m = make(map[interface{}]struct{})
s.Add(items...)
return s
}
// Add adds new items to the set
func (s *ThreadUnsafeSet) Add(items ...interface{}) {
if len(items) == 0 {
return
}
for _, item := range items {
s.m[item] = keyExists
}
}
// Remove removes items from the set
func (s *ThreadUnsafeSet) Remove(items ...interface{}) {
if len(items) == 0 {
return
}
for _, item := range items {
delete(s.m, item)
}
}
// Pop removes an item from the set and returns it.
func (s *set) Pop() interface{} {
for item := range s.m {
delete(s.m, item)
return item
}
return nil
}
// Has returns true if the items passed are present in the set
func (s *ThreadUnsafeSet) Has(items ...interface{}) bool {
// assume checked for empty item, which not exist
if len(items) == 0 {
return false
}
has := true
for _, item := range items {
if _, has = s.m[item]; !has {
break
}
}
return has
}
// Size returns the size of the set
func (s *ThreadUnsafeSet) Size() int {
return len(s.m)
}
// Clear removes all elements from the set
func (s *ThreadUnsafeSet) Clear() {
s.m = make(map[interface{}]struct{})
}
// IsEqual returns true if the received set is equal to this one
func (s *ThreadUnsafeSet) IsEqual(t Set) bool {
// Force locking only if given set is threadsafe.
if conv, ok := t.(*ThreadSafeSet); ok {
conv.l.RLock()
defer conv.l.RUnlock()
}
// return false if they are no the same size
if sameSize := len(s.m) == t.Size(); !sameSize {
return false
}
equal := true
t.Each(func(item interface{}) bool {
_, equal = s.m[item]
return equal // if false, Each() will end
})
return equal
}
// IsSubset returns true if the passed set is a subset of this one
func (s *ThreadUnsafeSet) IsSubset(t Set) (subset bool) {
subset = true
t.Each(func(item interface{}) bool {
_, subset = s.m[item]
return subset
})
return subset
}
// Each executes a passed function on each of the items passed.
func (s *ThreadUnsafeSet) Each(f func(item interface{}) bool) {
for item := range s.m {
if !f(item) {
break
}
}
}
// List returns a slice of the items in th set
func (s *ThreadUnsafeSet) List() []interface{} {
list := make([]interface{}, 0, len(s.m))
for item := range s.m {
list = append(list, item)
}
return list
}
// Copy returns a new set with a copy of the elements
func (s *ThreadUnsafeSet) Copy() Set {
return NewSet(s.List()...)
}
// Merge adds all the elefements in the passed set to this one.
func (s *ThreadUnsafeSet) Merge(t Set) {
t.Each(func(item interface{}) bool {
s.m[item] = keyExists
return true
})
}
// Separate removes all the items that are present in the passed set from this set
func (s *ThreadUnsafeSet) Separate(t Set) {
s.Remove(t.List()...)
}
// IsEmpty returns true if the set has no elements
func (s *ThreadUnsafeSet) IsEmpty() bool {
return s.Size() == 0
}
// IsSuperset returns true if the passed set is a supertset of this one
func (s *ThreadUnsafeSet) IsSuperset(t Set) bool {
return t.IsSubset(s)
}
// ** Thread safe implementation
// ThreadSafeSet is a thread safe implementation of the set data structure
type ThreadSafeSet struct {
set
l sync.RWMutex
}
// NewThreadSafeSet instantiates a new ThreadSafeSet
func NewThreadSafeSet(items ...interface{}) *ThreadSafeSet {
s := &ThreadSafeSet{}
s.m = make(map[interface{}]struct{})
// Ensure interface compliance
var _ Set = s
s.Add(items...)
return s
}
// Add adds a new element to the set
func (s *ThreadSafeSet) Add(items ...interface{}) {
if len(items) == 0 {
return
}
s.l.Lock()
defer s.l.Unlock()
for _, item := range items {
s.m[item] = keyExists
}
}
// Remove deletes an elemenet from the set.
func (s *ThreadSafeSet) Remove(items ...interface{}) {
if len(items) == 0 {
return
}
s.l.Lock()
defer s.l.Unlock()
for _, item := range items {
delete(s.m, item)
}
}
// Pop removes an element from the set and returns it
func (s *ThreadSafeSet) Pop() interface{} {
s.l.RLock()
for item := range s.m {
s.l.RUnlock()
s.l.Lock()
delete(s.m, item)
s.l.Unlock()
return item
}
s.l.RUnlock()
return nil
}
// Has returns true if the element passed is in the set
func (s *ThreadSafeSet) Has(items ...interface{}) bool {
// assume checked for empty item, which not exist
if len(items) == 0 {
return false
}
s.l.RLock()
defer s.l.RUnlock()
has := true
for _, item := range items {
if _, has = s.m[item]; !has {
break
}
}
return has
}
// Size returns the number of elements in the set
func (s *ThreadSafeSet) Size() int {
s.l.RLock()
defer s.l.RUnlock()
l := len(s.m)
return l
}
// Clear removes all the elements in the set
func (s *ThreadSafeSet) Clear() {
s.l.Lock()
defer s.l.Unlock()
s.m = make(map[interface{}]struct{})
}
// IsEqual returns true if the set contains the same elements as the passed one
func (s *ThreadSafeSet) IsEqual(t Set) bool {
s.l.RLock()
defer s.l.RUnlock()
// Force locking only if given set is threadsafe.
if conv, ok := t.(*ThreadSafeSet); ok {
conv.l.RLock()
defer conv.l.RUnlock()
}
// return false if they are no the same size
if sameSize := len(s.m) == t.Size(); !sameSize {
return false
}
equal := true
t.Each(func(item interface{}) bool {
_, equal = s.m[item]
return equal // if false, Each() will end
})
return equal
}
// IsSubset returns true if the passed set is a subset of this one
func (s *ThreadSafeSet) IsSubset(t Set) (subset bool) {
s.l.RLock()
defer s.l.RUnlock()
subset = true
t.Each(func(item interface{}) bool {
_, subset = s.m[item]
return subset
})
return
}
// Each executes the passed function on each item from the set
func (s *ThreadSafeSet) Each(f func(item interface{}) bool) {
s.l.RLock()
defer s.l.RUnlock()
for item := range s.m {
if !f(item) {
break
}
}
}
// List returns a list with all the elements of the set
func (s *ThreadSafeSet) List() []interface{} {
s.l.RLock()
defer s.l.RUnlock()
list := make([]interface{}, 0, len(s.m))
for item := range s.m {
list = append(list, item)
}
return list
}
// Merge adds all the elements of the passed set into this one
func (s *ThreadSafeSet) Merge(t Set) {
s.l.Lock()
defer s.l.Unlock()
t.Each(func(item interface{}) bool {
s.m[item] = keyExists
return true
})
}
// Copy returns a copy of the this thread
func (s *ThreadSafeSet) Copy() Set {
return NewThreadSafeSet(s.List()...)
}
// Separate removes all the items that are present in the passed set from this set
func (s *ThreadSafeSet) Separate(t Set) {
s.Remove(t.List()...)
}
// IsEmpty returns true if the set has no elements
func (s *ThreadSafeSet) IsEmpty() bool {
return s.Size() == 0
}
// IsSuperset returns true if the passed set is a supertset of this one
func (s *ThreadSafeSet) IsSuperset(t Set) bool {
return t.IsSubset(s)
}

24
vendor/github.com/splitio/go-toolkit/v4/datastructures/set/set.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,24 @@
package set
// Set interface shared between Thread-Safe and Thread-Unsafe implementations
type Set interface {
Add(items ...interface{})
Remove(items ...interface{})
Pop() interface{}
Has(items ...interface{}) bool
Size() int
Clear()
IsEmpty() bool
IsEqual(s Set) bool
IsSubset(s Set) bool
IsSuperset(s Set) bool
Each(func(interface{}) bool)
List() []interface{}
Copy() Set
Merge(s Set)
Separate(t Set)
}
type set struct {
m map[interface{}]struct{}
}

47
vendor/github.com/splitio/go-toolkit/v4/injection/container.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,47 @@
package injection
import (
"reflect"
"sync"
)
// NewContext return an instance of Context
func NewContext() *Context {
return &Context{container: make(map[string]interface{})}
}
// Context of dependencies to be injected as context
type Context struct {
container map[string]interface{}
mx sync.Mutex
}
// AddDependency adds a dependency to the container
func (c *Context) AddDependency(key string, d interface{}) {
c.mx.Lock()
c.container[key] = d
c.mx.Unlock()
}
// Dependency returns an object given a key
func (c *Context) Dependency(key string) interface{} {
c.mx.Lock()
defer c.mx.Unlock()
return c.container[key]
}
// Inject add the Context instance as dependency
func (c *Context) Inject(o interface{}) {
rv := reflect.ValueOf(c)
reflectedContext := reflect.ValueOf(o).Elem()
typeOfT := rv.Type()
for i := 0; i < reflectedContext.NumField(); i++ {
f := reflectedContext.Field(i)
if f.Type().String() == typeOfT.String() {
reflectedContext.Field(i).Set(rv)
}
}
}

33
vendor/github.com/splitio/go-toolkit/v4/logging/functions.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,33 @@
package logging
import (
"fmt"
"net/http"
"regexp"
"strings"
)
// ObfuscateAPIKey obfucate part of api key
func ObfuscateAPIKey(apikey string) string {
obfuscationIndex := 80
total := len(apikey)
charsToObfuscate := obfuscationIndex * total / 100
toShow := (total - charsToObfuscate) / 2
return strings.Join([]string{apikey[:toShow], apikey[len(apikey)-toShow:]}, "...")
}
// ObfuscateHTTPHeader obfuscates sensitive data into headers
func ObfuscateHTTPHeader(headers http.Header) string {
var re = regexp.MustCompile(`Authorization:\[Bearer ([0-9|a-z|A-Z|\s]*)\]`)
var str = fmt.Sprint(headers)
match := re.FindStringSubmatch(str)
if len(match) == 2 {
str = strings.Replace(str, match[1], ObfuscateAPIKey(match[1]), 1)
return fmt.Sprint("[REQUEST_HEADERS]", str, "[END_REQUEST_HEADERS]")
}
return str
}

12
vendor/github.com/splitio/go-toolkit/v4/logging/interface.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,12 @@
package logging
// LoggerInterface ...
// If a custom logger object is to be used, it should comply with the following
// interface. (Standard go-lang library log.Logger.Println method signature)
type LoggerInterface interface {
Error(msg ...interface{})
Warning(msg ...interface{})
Info(msg ...interface{})
Debug(msg ...interface{})
Verbose(msg ...interface{})
}

93
vendor/github.com/splitio/go-toolkit/v4/logging/levels.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,93 @@
package logging
import (
"math"
)
// Standard values
const (
// Discard 0 value, so when can use it as "the lack of a logging level"
_ = iota
// LevelError log level
LevelError
// LevelWarning log level
LevelWarning
// LevelInfo log level
LevelInfo
// LevelDebug log level
LevelDebug
// LevelVerbose log level
LevelVerbose
)
// Special values
const (
// LevelNone implies that NOTHING will be logged, not even errors
LevelNone = math.MinInt32
// LevelAll implies that All logging levels will be recorded
LevelAll = math.MaxInt32
)
// LevelFilteredLoggerWrapper forwards log message to delegate if level is set higher than incoming message
type LevelFilteredLoggerWrapper struct {
level int
delegate LoggerInterface
}
// Error forwards error logging messages
func (l *LevelFilteredLoggerWrapper) Error(is ...interface{}) {
if l.level >= LevelError {
l.delegate.Error(is...)
}
}
// Warning forwards warning logging messages
func (l *LevelFilteredLoggerWrapper) Warning(is ...interface{}) {
if l.level >= LevelWarning {
l.delegate.Warning(is...)
}
}
// Info forwards info logging messages
func (l *LevelFilteredLoggerWrapper) Info(is ...interface{}) {
if l.level >= LevelInfo {
l.delegate.Info(is...)
}
}
// Debug forwards debug logging messages
func (l *LevelFilteredLoggerWrapper) Debug(is ...interface{}) {
if l.level >= LevelDebug {
l.delegate.Debug(is...)
}
}
// Verbose forwards verbose logging messages
func (l *LevelFilteredLoggerWrapper) Verbose(is ...interface{}) {
if l.level >= LevelVerbose {
l.delegate.Verbose(is...)
}
}
var levels map[string]int = map[string]int{
"ERROR": LevelError,
"WARNING": LevelWarning,
"INFO": LevelInfo,
"DEBUG": LevelDebug,
"VERBOSE": LevelVerbose,
}
// Level gets current level
func Level(level string) int {
l, ok := levels[level]
if !ok {
panic("Invalid log level " + level)
}
return l
}

129
vendor/github.com/splitio/go-toolkit/v4/logging/logging.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,129 @@
// Package logging ...
// Handles logging within the SDK
package logging
import (
"fmt"
"io"
"log"
"os"
)
const (
skipStackFrameBase = 3 // How many stack frames to skip when logging filename
)
// LoggerOptions ...
// Struct that must be passed to the NewLogger constructor to setup a logger
// CommonWriter and ErrorWriter can be <nil>. In that case they'll default to os.Stdout
type LoggerOptions struct {
LogLevel int
ErrorWriter io.Writer
WarningWriter io.Writer
InfoWriter io.Writer
DebugWriter io.Writer
VerboseWriter io.Writer
StandardLoggerFlags int
Prefix string
ExtraFramesToSkip int
}
// Logger struct. Encapsulates four different loggers, each for a different "level",
// and provides Error, Debug, Warning and Info functions, that will forward a message
// to the appropriate logger.
type Logger struct {
debugLogger log.Logger
infoLogger log.Logger
warningLogger log.Logger
errorLogger log.Logger
verboseLogger log.Logger
framesToSkip int
}
// Verbose logs a message with Debug level
func (l *Logger) Verbose(msg ...interface{}) {
l.verboseLogger.Output(l.framesToSkip, fmt.Sprintln(msg...))
}
// Debug logs a message with Debug level
func (l *Logger) Debug(msg ...interface{}) {
l.debugLogger.Output(l.framesToSkip, fmt.Sprintln(msg...))
}
// Info logs a message with Info level
func (l *Logger) Info(msg ...interface{}) {
l.infoLogger.Output(l.framesToSkip, fmt.Sprintln(msg...))
}
// Warning logs a message with Warning level
func (l *Logger) Warning(msg ...interface{}) {
l.warningLogger.Output(l.framesToSkip, fmt.Sprintln(msg...))
}
// Error logs a message with Error level
func (l *Logger) Error(msg ...interface{}) {
l.errorLogger.Output(l.framesToSkip, fmt.Sprintln(msg...))
}
func normalizeOptions(options *LoggerOptions) *LoggerOptions {
var toRet *LoggerOptions
if options == nil {
toRet = &LoggerOptions{}
} else {
toRet = options
}
if toRet.DebugWriter == nil {
toRet.DebugWriter = os.Stdout
}
if toRet.ErrorWriter == nil {
toRet.ErrorWriter = os.Stdout
}
if toRet.InfoWriter == nil {
toRet.InfoWriter = os.Stdout
}
if toRet.VerboseWriter == nil {
toRet.VerboseWriter = os.Stdout
}
if toRet.WarningWriter == nil {
toRet.WarningWriter = os.Stdout
}
if toRet.StandardLoggerFlags == 0 {
toRet.StandardLoggerFlags = log.Ldate | log.Ltime
}
switch toRet.LogLevel {
case LevelAll, LevelDebug, LevelError, LevelInfo, LevelNone, LevelVerbose, LevelWarning:
default:
toRet.LogLevel = LevelError
}
return toRet
}
// NewLogger instantiates a new Logger instance. Requires a pointer to a LoggerOptions struct to be passed.
func NewLogger(options *LoggerOptions) LoggerInterface {
options = normalizeOptions(options)
prefix := ""
if options.Prefix != "" {
prefix = fmt.Sprintf("%s - ", options.Prefix)
}
logger := &Logger{
debugLogger: *log.New(options.DebugWriter, fmt.Sprintf("%sDEBUG - ", prefix), options.StandardLoggerFlags),
infoLogger: *log.New(options.InfoWriter, fmt.Sprintf("%sINFO - ", prefix), options.StandardLoggerFlags),
warningLogger: *log.New(options.WarningWriter, fmt.Sprintf("%sWARNING - ", prefix), options.StandardLoggerFlags),
errorLogger: *log.New(options.ErrorWriter, fmt.Sprintf("%sERROR - ", prefix), options.StandardLoggerFlags),
verboseLogger: *log.New(options.VerboseWriter, fmt.Sprintf("%sVERBOSE - ", prefix), options.StandardLoggerFlags),
framesToSkip: 3 + options.ExtraFramesToSkip,
}
return &LevelFilteredLoggerWrapper{
delegate: logger,
level: options.LogLevel,
}
}

106
vendor/github.com/splitio/go-toolkit/v4/logging/rotate.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,106 @@
package logging
import (
"fmt"
"os"
"strconv"
"sync"
)
// FileRotateOptions struct to configure FileRotate
type FileRotateOptions struct {
MaxBytes int64
BackupCount int
Path string
}
// FileRotate rotates a log file at MaxBytes
type FileRotate struct {
fl *os.File
fm *sync.Mutex
options *FileRotateOptions
}
// NewFileRotate returns a pointer to a FileRotate instance
func NewFileRotate(opt *FileRotateOptions) (*FileRotate, error) {
fileWriter, err := os.OpenFile(opt.Path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
return nil, err
}
fl := &FileRotate{fl: fileWriter, fm: &sync.Mutex{}, options: opt}
return fl, nil
}
func (f *FileRotate) shouldRotate(bytesToAdd int64) bool {
fi, err := f.fl.Stat()
if err != nil {
fmt.Println("Error getting stats of file")
return false
}
if fi.Size()+bytesToAdd >= f.options.MaxBytes {
return true
}
return false
}
func (f *FileRotate) rotate() error {
f.fl.Close()
for i := f.options.BackupCount - 1; i >= 0; i-- {
var currentLog string
if i == 0 {
currentLog = f.options.Path
} else {
currentLog = f.options.Path + "." + strconv.Itoa(i)
}
if _, err := os.Stat(currentLog); err == nil {
rotateLog := f.options.Path + "." + strconv.Itoa(i+1)
err := os.Rename(currentLog, rotateLog)
if err != nil {
fmt.Printf("Error rotating log file: %s \n", err.Error())
return err
}
}
}
var err error
f.fl, err = os.OpenFile(f.options.Path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
fmt.Printf("Error reopening log file: %s \n", err.Error())
return err
}
return nil
}
func (f *FileRotate) write(p []byte) (n int, err error) {
f.fm.Lock()
if f.shouldRotate(int64(len(p))) {
f.rotate()
}
n, err = f.fl.Write(p)
f.fm.Unlock()
if err != nil {
fmt.Println("Error writing in rotated log file", f.options.Path)
return n, err
}
return n, nil
}
// Write writes async the log message
func (f *FileRotate) Write(p []byte) (n int, err error) {
dst := make([]byte, len(p))
copy(dst, p)
go f.write(dst)
return len(p), nil
}

44
vendor/github.com/splitio/go-toolkit/v4/nethelpers/ip.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,44 @@
package nethelpers
import (
"errors"
"net"
)
// ExternalIP returns the IP address of the host
func ExternalIP() (string, error) {
ifaces, err := net.Interfaces()
if err != nil {
return "", err
}
for _, iface := range ifaces {
if iface.Flags&net.FlagUp == 0 {
continue // interface down
}
if iface.Flags&net.FlagLoopback != 0 {
continue // loopback interface
}
addrs, err := iface.Addrs()
if err != nil {
return "", err
}
for _, addr := range addrs {
var ip net.IP
switch v := addr.(type) {
case *net.IPNet:
ip = v.IP
case *net.IPAddr:
ip = v.IP
}
if ip == nil || ip.IsLoopback() {
continue
}
ip = ip.To4()
if ip == nil {
continue // not an ipv4 address
}
return ip.String(), nil
}
}
return "", errors.New("Could not determine IP Address")
}

231
vendor/github.com/splitio/go-toolkit/v4/provisional/hashing/murmur128.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,231 @@
package hashing
// Implementation borrowed from https://github.com/spaolacci/murmur3,
// distributed under BSD-3 license.
import (
//"encoding/binary"
"hash"
"math/bits"
"unsafe"
)
const (
c1_128 = 0x87c37b91114253d5
c2_128 = 0x4cf5ad432745937f
)
// Hash128 represents a 128-bit hasher
// Hack: the standard api doesn't define any Hash128 interface.
type Hash128 interface {
hash.Hash
Sum128() (uint64, uint64)
}
// digest128 represents a partial evaluation of a 128 bites hash.
type digest128 struct {
clen int // Digested input cumulative length.
tail []byte // 0 to Size()-1 bytes view of `buf'.
buf [16]byte // Expected (but not required) to be Size() large.
seed uint32 // Seed for initializing the hash.
h1 uint64 // Unfinalized running hash part 1.
h2 uint64 // Unfinalized running hash part 2.
}
// New128 returns a 128-bit hasher
func New128() Hash128 { return New128WithSeed(0) }
// New128WithSeed returns a 128-bit hasher set with explicit seed value
func New128WithSeed(seed uint32) Hash128 {
d := new(digest128)
d.seed = seed
d.Reset()
return d
}
func (d *digest128) BlockSize() int { return 1 }
func (d *digest128) Write(p []byte) (n int, err error) {
n = len(p)
d.clen += n
if len(d.tail) > 0 {
// Stick back pending bytes.
nfree := d.Size() - len(d.tail) // nfree ∈ [1, d.Size()-1].
if nfree < len(p) {
// One full block can be formed.
block := append(d.tail, p[:nfree]...)
p = p[nfree:]
_ = d.bmix(block) // No tail.
} else {
// Tail's buf is large enough to prevent reallocs.
p = append(d.tail, p...)
}
}
d.tail = d.bmix(p)
// Keep own copy of the 0 to Size()-1 pending bytes.
nn := copy(d.buf[:], d.tail)
d.tail = d.buf[:nn]
return n, nil
}
func (d *digest128) Reset() {
d.clen = 0
d.tail = nil
d.reset()
}
func (d *digest128) Size() int { return 16 }
func (d *digest128) reset() { d.h1, d.h2 = uint64(d.seed), uint64(d.seed) }
func (d *digest128) Sum(b []byte) []byte {
h1, h2 := d.Sum128()
return append(b,
byte(h1>>56), byte(h1>>48), byte(h1>>40), byte(h1>>32),
byte(h1>>24), byte(h1>>16), byte(h1>>8), byte(h1),
byte(h2>>56), byte(h2>>48), byte(h2>>40), byte(h2>>32),
byte(h2>>24), byte(h2>>16), byte(h2>>8), byte(h2),
)
}
func (d *digest128) bmix(p []byte) (tail []byte) {
h1, h2 := d.h1, d.h2
nblocks := len(p) / 16
for i := 0; i < nblocks; i++ {
t := (*[2]uint64)(unsafe.Pointer(&p[i*16]))
k1, k2 := t[0], t[1]
k1 *= c1_128
k1 = bits.RotateLeft64(k1, 31)
k1 *= c2_128
h1 ^= k1
h1 = bits.RotateLeft64(h1, 27)
h1 += h2
h1 = h1*5 + 0x52dce729
k2 *= c2_128
k2 = bits.RotateLeft64(k2, 33)
k2 *= c1_128
h2 ^= k2
h2 = bits.RotateLeft64(h2, 31)
h2 += h1
h2 = h2*5 + 0x38495ab5
}
d.h1, d.h2 = h1, h2
return p[nblocks*d.Size():]
}
func (d *digest128) Sum128() (h1, h2 uint64) {
h1, h2 = d.h1, d.h2
var k1, k2 uint64
switch len(d.tail) & 15 {
case 15:
k2 ^= uint64(d.tail[14]) << 48
fallthrough
case 14:
k2 ^= uint64(d.tail[13]) << 40
fallthrough
case 13:
k2 ^= uint64(d.tail[12]) << 32
fallthrough
case 12:
k2 ^= uint64(d.tail[11]) << 24
fallthrough
case 11:
k2 ^= uint64(d.tail[10]) << 16
fallthrough
case 10:
k2 ^= uint64(d.tail[9]) << 8
fallthrough
case 9:
k2 ^= uint64(d.tail[8]) << 0
k2 *= c2_128
k2 = bits.RotateLeft64(k2, 33)
k2 *= c1_128
h2 ^= k2
fallthrough
case 8:
k1 ^= uint64(d.tail[7]) << 56
fallthrough
case 7:
k1 ^= uint64(d.tail[6]) << 48
fallthrough
case 6:
k1 ^= uint64(d.tail[5]) << 40
fallthrough
case 5:
k1 ^= uint64(d.tail[4]) << 32
fallthrough
case 4:
k1 ^= uint64(d.tail[3]) << 24
fallthrough
case 3:
k1 ^= uint64(d.tail[2]) << 16
fallthrough
case 2:
k1 ^= uint64(d.tail[1]) << 8
fallthrough
case 1:
k1 ^= uint64(d.tail[0]) << 0
k1 *= c1_128
k1 = bits.RotateLeft64(k1, 31)
k1 *= c2_128
h1 ^= k1
}
h1 ^= uint64(d.clen)
h2 ^= uint64(d.clen)
h1 += h2
h2 += h1
h1 = fmix64(h1)
h2 = fmix64(h2)
h1 += h2
h2 += h1
return h1, h2
}
func fmix64(k uint64) uint64 {
k ^= k >> 33
k *= 0xff51afd7ed558ccd
k ^= k >> 33
k *= 0xc4ceb9fe1a85ec53
k ^= k >> 33
return k
}
// Sum128 returns the MurmurHash3 sum of data. It is equivalent to the
// following sequence (without the extra burden and the extra allocation):
// hasher := New128()
// hasher.Write(data)
// return hasher.Sum128()
func Sum128(data []byte) (h1 uint64, h2 uint64) { return Sum128WithSeed(data, 0) }
// Sum128WithSeed returns the MurmurHash3 sum of data. It is equivalent to the
// following sequence (without the extra burden and the extra allocation):
// hasher := New128WithSeed(seed)
// hasher.Write(data)
// return hasher.Sum128()
func Sum128WithSeed(data []byte, seed uint32) (h1 uint64, h2 uint64) {
d := digest128{h1: uint64(seed), h2: uint64(seed)}
d.seed = seed
d.tail = d.bmix(data)
d.clen = len(data)
return d.Sum128()
}

91
vendor/github.com/splitio/go-toolkit/v4/provisional/int64cache/cache.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,91 @@
package int64cache
import (
"container/list"
"fmt"
"sync"
)
// Int64Cache is an in-memory TTL & LRU cache
type Int64Cache interface {
Get(key int64) (int64, error)
Set(key int64, value int64) error
}
// Impl implements the LocalCache interface
type Impl struct {
maxLen int
items map[int64]*list.Element
lru *list.List
mutex sync.Mutex
}
type entry struct {
key int64
value int64
}
// Get retrieves an item if exist, nil + an error otherwise
func (c *Impl) Get(key int64) (int64, error) {
c.mutex.Lock()
defer c.mutex.Unlock()
node, ok := c.items[key]
if !ok {
return 0, &Miss{}
}
entry, ok := node.Value.(entry)
if !ok {
return 0, fmt.Errorf("Invalid data in cache for key %d", key)
}
c.lru.MoveToFront(node)
return entry.value, nil
}
// Set adds a new item. Since the cache being full results in removing the LRU element, this method never fails.
func (c *Impl) Set(key int64, value int64) error {
c.mutex.Lock()
defer c.mutex.Unlock()
if node, ok := c.items[key]; ok {
c.lru.MoveToFront(node)
node.Value = entry{key: key, value: value}
} else {
// Drop the LRU item on the list before adding a new one.
if c.lru.Len() == c.maxLen {
entry, ok := c.lru.Back().Value.(entry)
if !ok {
return fmt.Errorf("Invalid data in list for key %d", key)
}
key := entry.key
delete(c.items, key)
c.lru.Remove(c.lru.Back())
}
ptr := c.lru.PushFront(entry{key: key, value: value})
c.items[key] = ptr
}
return nil
}
// NewInt64Cache returns a new LocalCache instance of the specified size and TTL
func NewInt64Cache(maxSize int) (*Impl, error) {
if maxSize <= 0 {
return nil, fmt.Errorf("Cache size should be > 0. Is: %d", maxSize)
}
return &Impl{
maxLen: maxSize,
lru: new(list.List),
items: make(map[int64]*list.Element, maxSize),
}, nil
}
// Miss is a special error indicating the key was not found in the cache
type Miss struct {
Key int64
}
func (m *Miss) Error() string {
return fmt.Sprintf("key %d not found in cache", m.Key)
}

127
vendor/github.com/splitio/go-toolkit/v4/queuecache/cache.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,127 @@
package queuecache
import (
"errors"
"fmt"
"math"
"sync"
)
// RefillError struct to be returned when the refill function panics
type RefillError struct {
OriginalPanic interface{}
}
func (e *RefillError) Error() string {
return "Supplied refilling function panicked. See `.OriginalPanic` property to get panicked content"
}
// MessagesDroppedError is the Error to be returned when messages fail to be added to the queue.
type MessagesDroppedError struct {
MessagesDropped int
}
func (e *MessagesDroppedError) Error() string {
return fmt.Sprintf(
"%d messages were dropped. Please report this error as it's most likely a bug in the library",
e.MessagesDropped,
)
}
// InMemoryQueueCacheOverlay offers an in-memory queue that gets re-populated whenever it runs out of items
type InMemoryQueueCacheOverlay struct {
maxSize int
writeCursor int
readCursor int
queue []interface{}
lock sync.Mutex
refillCustom func(count int) ([]interface{}, error)
}
// New creates a new InMemoryQueueCacheOverlay
func New(maxSize int, refillFunc func(count int) ([]interface{}, error)) *InMemoryQueueCacheOverlay {
return &InMemoryQueueCacheOverlay{
queue: make([]interface{}, maxSize),
maxSize: maxSize,
writeCursor: 0,
readCursor: 0,
refillCustom: refillFunc,
}
}
// Count returns the number of cached items
func (i *InMemoryQueueCacheOverlay) Count() int {
if i.writeCursor == i.readCursor {
return 0
} else if i.writeCursor > i.readCursor {
return i.writeCursor - i.readCursor
}
return i.maxSize - (i.readCursor - i.writeCursor)
}
func (i *InMemoryQueueCacheOverlay) write(elem interface{}) error {
if ((i.writeCursor + 1) % i.maxSize) == i.readCursor {
return errors.New("QUEUE_FULL")
}
i.queue[i.writeCursor] = elem
i.writeCursor = (i.writeCursor + 1) % i.maxSize
return nil
}
func (i *InMemoryQueueCacheOverlay) read() (interface{}, error) {
if i.readCursor == i.writeCursor {
return nil, errors.New("QUEUE_EMPTY")
}
toReturn := i.queue[i.readCursor]
i.readCursor = (i.readCursor + 1) % i.maxSize
return toReturn, nil
}
func (i *InMemoryQueueCacheOverlay) refillWrapper(count int) (result []interface{}, err error) {
defer func() {
if r := recover(); r != nil {
result = nil
err = &RefillError{OriginalPanic: r}
}
}()
return i.refillCustom(count)
}
// Fetch items (will re-populate if necessary)
func (i *InMemoryQueueCacheOverlay) Fetch(requestedCount int) ([]interface{}, error) {
defer i.lock.Unlock()
i.lock.Lock()
dropped := 0
if i.Count() < requestedCount {
toAdd, err := i.refillWrapper(i.maxSize - i.Count() - 1)
if err != nil {
return nil, err
}
for _, item := range toAdd {
err = i.write(item)
if err != nil {
dropped++
}
}
}
toReturn := make([]interface{}, int(math.Min(float64(requestedCount), float64(i.Count()))))
for index := 0; index < len(toReturn); index++ {
elem, err := i.read()
if err != nil {
return toReturn[0:index], nil
}
toReturn[index] = elem
}
if dropped > 0 {
return toReturn, &MessagesDroppedError{MessagesDropped: dropped}
}
return toReturn, nil
}

23
vendor/github.com/splitio/go-toolkit/v4/redis/helpers/helpers.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,23 @@
package helpers
import (
"fmt"
"github.com/splitio/go-toolkit/v4/redis"
)
const (
pong = "PONG"
)
// EnsureConnected pings redis
func EnsureConnected(client redis.Client) {
res := client.Ping()
if res.Err() != nil {
panic(fmt.Sprintf("Couldn't connect to redis: %s", res.Err()))
}
if res.String() != pong {
panic(fmt.Sprintf("Invalid redis ping response when connecting: %s", res.String()))
}
}

160
vendor/github.com/splitio/go-toolkit/v4/redis/prefixedclient.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,160 @@
package redis
import (
"fmt"
"strings"
"time"
)
// PrefixedRedisClient struct
type PrefixedRedisClient struct {
Prefix string
Client Client
}
// withPrefix adds a prefix to the key if the prefix supplied has a length greater than 0
func (p *PrefixedRedisClient) withPrefix(key string) string {
if len(p.Prefix) > 0 {
return fmt.Sprintf("%s.%s", p.Prefix, key)
}
return key
}
// withoutPrefix removes the prefix from a key if the prefix has a length greater than 0
func (p *PrefixedRedisClient) withoutPrefix(key string) string {
if len(p.Prefix) > 0 {
return strings.Replace(key, fmt.Sprintf("%s.", p.Prefix), "", 1)
}
return key
}
// Get wraps around redis get method by adding prefix and returning string and error directly
func (p *PrefixedRedisClient) Get(key string) (string, error) {
return p.Client.Get(p.withPrefix(key)).ResultString()
}
// Set wraps around redis get method by adding prefix and returning error directly
func (p *PrefixedRedisClient) Set(key string, value interface{}, expiration time.Duration) error {
return p.Client.Set(p.withPrefix(key), value, expiration).Err()
}
// Keys wraps around redis keys method by adding prefix and returning []string and error directly
func (p *PrefixedRedisClient) Keys(pattern string) ([]string, error) {
keys, err := p.Client.Keys(p.withPrefix(pattern)).Multi()
if err != nil {
return nil, err
}
woPrefix := make([]string, len(keys))
for index, key := range keys {
woPrefix[index] = p.withoutPrefix(key)
}
return woPrefix, nil
}
// Del wraps around redis del method by adding prefix and returning int64 and error directly
func (p *PrefixedRedisClient) Del(keys ...string) (int64, error) {
prefixedKeys := make([]string, len(keys))
for i, k := range keys {
prefixedKeys[i] = p.withPrefix(k)
}
return p.Client.Del(prefixedKeys...).Result()
}
// SMembers returns a slice with all the members of a set
func (p *PrefixedRedisClient) SMembers(key string) ([]string, error) {
return p.Client.SMembers(p.withPrefix(key)).Multi()
}
// SIsMember returns true if members is in the set
func (p *PrefixedRedisClient) SIsMember(key string, member interface{}) bool {
return p.Client.SIsMember(p.withPrefix(key), member).Bool()
}
// SAdd adds new members to a set
func (p *PrefixedRedisClient) SAdd(key string, members ...interface{}) (int64, error) {
return p.Client.SAdd(p.withPrefix(key), members...).Result()
}
// SRem removes members from a set
func (p *PrefixedRedisClient) SRem(key string, members ...interface{}) (int64, error) {
return p.Client.SRem(p.withPrefix(key), members...).Result()
}
// Exists returns true if a key exists in redis
func (p *PrefixedRedisClient) Exists(keys ...string) (int64, error) {
prefixedKeys := make([]string, len(keys))
for i, k := range keys {
prefixedKeys[i] = p.withPrefix(k)
}
val, err := p.Client.Exists(prefixedKeys...).Result()
return val, err
}
// Incr increments a key. Sets it in one if it doesn't exist
func (p *PrefixedRedisClient) Incr(key string) (int64, error) {
return p.Client.Incr(p.withPrefix(key)).Result()
}
// Decr increments a key. Sets it in one if it doesn't exist
func (p *PrefixedRedisClient) Decr(key string) (int64, error) {
return p.Client.Decr(p.withPrefix(key)).Result()
}
// RPush insert all the specified values at the tail of the list stored at key
func (p *PrefixedRedisClient) RPush(key string, values ...interface{}) (int64, error) {
return p.Client.RPush(p.withPrefix(key), values...).Result()
}
// LRange Returns the specified elements of the list stored at key
func (p *PrefixedRedisClient) LRange(key string, start, stop int64) ([]string, error) {
return p.Client.LRange(p.withPrefix(key), start, stop).Multi()
}
// LTrim Trim an existing list so that it will contain only the specified range of elements specified
func (p *PrefixedRedisClient) LTrim(key string, start, stop int64) error {
return p.Client.LTrim(p.withPrefix(key), start, stop).Err()
}
// LLen Returns the length of the list stored at key
func (p *PrefixedRedisClient) LLen(key string) (int64, error) {
return p.Client.LLen(p.withPrefix(key)).Result()
}
// Expire set expiration time for particular key
func (p *PrefixedRedisClient) Expire(key string, value time.Duration) bool {
return p.Client.Expire(p.withPrefix(key), value).Bool()
}
// TTL for particular key
func (p *PrefixedRedisClient) TTL(key string) time.Duration {
return p.Client.TTL(p.withPrefix(key)).Duration()
}
// MGet fetchs multiple results
func (p *PrefixedRedisClient) MGet(keys []string) ([]interface{}, error) {
keysWithPrefix := make([]string, 0)
for _, key := range keys {
keysWithPrefix = append(keysWithPrefix, p.withPrefix(key))
}
return p.Client.MGet(keysWithPrefix).MultiInterface()
}
// SCard implements SCard wrapper for redis
func (p *PrefixedRedisClient) SCard(key string) (int64, error) {
return p.Client.SCard(p.withPrefix(key)).Result()
}
// Eval implements Eval wrapper for redis
func (p *PrefixedRedisClient) Eval(script string, keys []string, args ...interface{}) error {
return p.Client.Eval(script, keys, args...).Err()
}
// NewPrefixedRedisClient returns a new Prefixed Redis Client
func NewPrefixedRedisClient(redisClient Client, prefix string) (*PrefixedRedisClient, error) {
return &PrefixedRedisClient{
Client: redisClient,
Prefix: prefix,
}, nil
}

8
vendor/github.com/splitio/go-toolkit/v4/redis/types.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,8 @@
package redis
import (
wredis "github.com/go-redis/redis/v8"
)
// UniversalOptions type used for redis package
type UniversalOptions = wredis.UniversalOptions

293
vendor/github.com/splitio/go-toolkit/v4/redis/wrapper.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,293 @@
package redis
import (
"context"
"time"
"github.com/go-redis/redis/v8"
)
// ===== Command output / return value types
// Result generic interface
type Result interface {
Int() int64
String() string
Bool() bool
Duration() time.Duration
Result() (int64, error)
ResultString() (string, error)
Multi() ([]string, error)
MultiInterface() ([]interface{}, error)
Err() error
}
// ResultImpl generic interface
type ResultImpl struct {
value int64
valueString string
valueBool bool
valueDuration time.Duration
err error
multi []string
multiInterface []interface{}
}
// Int implementation
func (r *ResultImpl) Int() int64 {
return r.value
}
// String implementation
func (r *ResultImpl) String() string {
return r.valueString
}
// Bool implementation
func (r *ResultImpl) Bool() bool {
return r.valueBool
}
// Duration implementation
func (r *ResultImpl) Duration() time.Duration {
return r.valueDuration
}
// Err implementation
func (r *ResultImpl) Err() error {
return r.err
}
// Result implementation
func (r *ResultImpl) Result() (int64, error) {
return r.value, r.err
}
// ResultString implementation
func (r *ResultImpl) ResultString() (string, error) {
return r.valueString, r.err
}
// Multi implementation
func (r *ResultImpl) Multi() ([]string, error) {
return r.multi, r.err
}
// MultiInterface implementation
func (r *ResultImpl) MultiInterface() ([]interface{}, error) {
return r.multiInterface, r.err
}
// ====== Client
// Client interface which specifies the currently used subset of redis operations
type Client interface {
Del(keys ...string) Result
Exists(keys ...string) Result
Get(key string) Result
Set(key string, value interface{}, expiration time.Duration) Result
Ping() Result
Keys(pattern string) Result
SMembers(key string) Result
SIsMember(key string, member interface{}) Result
SAdd(key string, members ...interface{}) Result
SRem(key string, members ...interface{}) Result
Incr(key string) Result
Decr(key string) Result
RPush(key string, values ...interface{}) Result
LRange(key string, start, stop int64) Result
LTrim(key string, start, stop int64) Result
LLen(key string) Result
Expire(key string, value time.Duration) Result
TTL(key string) Result
MGet(keys []string) Result
SCard(key string) Result
Eval(script string, keys []string, args ...interface{}) Result
}
// ClientImpl wrapps redis client
type ClientImpl struct {
wrapped redis.UniversalClient
}
func (c *ClientImpl) wrapResult(result interface{}) Result {
if result == nil {
return nil
}
switch v := result.(type) {
case *redis.StatusCmd:
return &ResultImpl{
valueString: v.Val(),
err: v.Err(),
}
case *redis.IntCmd:
return &ResultImpl{
value: v.Val(),
err: v.Err(),
}
case *redis.StringCmd:
return &ResultImpl{
valueString: v.Val(),
err: v.Err(),
}
case *redis.StringSliceCmd:
return &ResultImpl{
err: v.Err(),
multi: v.Val(),
}
case *redis.BoolCmd:
return &ResultImpl{
valueBool: v.Val(),
err: v.Err(),
}
case *redis.DurationCmd:
return &ResultImpl{
valueDuration: v.Val(),
err: v.Err(),
}
case *redis.SliceCmd:
return &ResultImpl{
err: v.Err(),
multiInterface: v.Val(),
}
case *redis.Cmd:
return &ResultImpl{
err: v.Err(),
}
default:
return nil
}
}
// Del implements Del wrapper for redis
func (c *ClientImpl) Del(keys ...string) Result {
res := c.wrapped.Del(context.TODO(), keys...)
return c.wrapResult(res)
}
// Exists implements Exists wrapper for redis
func (c *ClientImpl) Exists(keys ...string) Result {
res := c.wrapped.Exists(context.TODO(), keys...)
return c.wrapResult(res)
}
// Get implements Get wrapper for redis
func (c *ClientImpl) Get(key string) Result {
res := c.wrapped.Get(context.TODO(), key)
return c.wrapResult(res)
}
// Set implements Set wrapper for redis
func (c *ClientImpl) Set(key string, value interface{}, expiration time.Duration) Result {
res := c.wrapped.Set(context.TODO(), key, value, expiration)
return c.wrapResult(res)
}
// Ping implements Ping wrapper for redis
func (c *ClientImpl) Ping() Result {
res := c.wrapped.Ping(context.TODO())
return c.wrapResult(res)
}
// Keys implements Keys wrapper for redis
func (c *ClientImpl) Keys(pattern string) Result {
res := c.wrapped.Keys(context.TODO(), pattern)
return c.wrapResult(res)
}
// SMembers implements SMembers wrapper for redis
func (c *ClientImpl) SMembers(key string) Result {
res := c.wrapped.SMembers(context.TODO(), key)
return c.wrapResult(res)
}
// SIsMember implements SIsMember wrapper for redis
func (c *ClientImpl) SIsMember(key string, member interface{}) Result {
res := c.wrapped.SIsMember(context.TODO(), key, member)
return c.wrapResult(res)
}
// SAdd implements SAdd wrapper for redis
func (c *ClientImpl) SAdd(key string, members ...interface{}) Result {
res := c.wrapped.SAdd(context.TODO(), key, members...)
return c.wrapResult(res)
}
// SRem implements SRem wrapper for redis
func (c *ClientImpl) SRem(key string, members ...interface{}) Result {
res := c.wrapped.SRem(context.TODO(), key, members...)
return c.wrapResult(res)
}
// Incr implements Incr wrapper for redis
func (c *ClientImpl) Incr(key string) Result {
res := c.wrapped.Incr(context.TODO(), key)
return c.wrapResult(res)
}
// Decr implements Decr wrapper for redis
func (c *ClientImpl) Decr(key string) Result {
res := c.wrapped.Decr(context.TODO(), key)
return c.wrapResult(res)
}
// RPush implements RPush wrapper for redis
func (c *ClientImpl) RPush(key string, values ...interface{}) Result {
res := c.wrapped.RPush(context.TODO(), key, values...)
return c.wrapResult(res)
}
// LRange implements LRange wrapper for redis
func (c *ClientImpl) LRange(key string, start, stop int64) Result {
res := c.wrapped.LRange(context.TODO(), key, start, stop)
return c.wrapResult(res)
}
// LTrim implements LTrim wrapper for redis
func (c *ClientImpl) LTrim(key string, start, stop int64) Result {
res := c.wrapped.LTrim(context.TODO(), key, start, stop)
return c.wrapResult(res)
}
// LLen implements LLen wrapper for redis
func (c *ClientImpl) LLen(key string) Result {
res := c.wrapped.LLen(context.TODO(), key)
return c.wrapResult(res)
}
// Expire implements Expire wrapper for redis
func (c *ClientImpl) Expire(key string, value time.Duration) Result {
res := c.wrapped.Expire(context.TODO(), key, value)
return c.wrapResult(res)
}
// TTL implements TTL wrapper for redis
func (c *ClientImpl) TTL(key string) Result {
res := c.wrapped.TTL(context.TODO(), key)
return c.wrapResult(res)
}
// MGet implements MGet wrapper for redis
func (c *ClientImpl) MGet(keys []string) Result {
res := c.wrapped.MGet(context.TODO(), keys...)
return c.wrapResult(res)
}
// SCard implements SCard wrapper for redis
func (c *ClientImpl) SCard(key string) Result {
res := c.wrapped.SCard(context.TODO(), key)
return c.wrapResult(res)
}
// Eval implements Eval wrapper for redis
func (c *ClientImpl) Eval(script string, keys []string, args ...interface{}) Result {
res := c.wrapped.Eval(context.TODO(), script, keys, args...)
return c.wrapResult(res)
}
// NewClient returns new client implementation
func NewClient(options *UniversalOptions) (Client, error) {
return &ClientImpl{
wrapped: redis.NewUniversalClient(options),
}, nil
}

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 сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -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 сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -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 сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -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 сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -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{}
}

181
vendor/github.com/splitio/go-toolkit/v4/workerpool/workerpool.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,181 @@
package workerpool
import (
"fmt"
"sync"
"time"
"github.com/splitio/go-toolkit/v4/logging"
"github.com/splitio/go-toolkit/v4/struct/traits/lifecycle"
)
const (
workerSignalStop = iota
)
// WorkerAdmin struct handles multiple worker execution, popping jobs from a single queue
type WorkerAdmin struct {
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
type Worker interface {
// Name should return a unique identifier for a particular worker
Name() string
// DoWork should receive a message, and perform the actual work, only an error should be returned
DoWork(message interface{}) error
// OnError will be called if DoWork returns an error != nil
OnError(e error)
// Cleanup will be called when the worker is shutting down
Cleanup() error
// FailureTime should return the amount of time the worker should wait after resuming work if an error occurs
FailureTime() int64
}
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 {
w.logger.Error(fmt.Sprintf(
"Worker %s is panicking with the following error \"%s\" and will be shutted down.",
w.w.Name(),
r,
))
w.lifecycle.AbnormalShutdown()
}
}()
defer w.lifecycle.ShutdownComplete()
defer w.w.Cleanup()
if !w.lifecycle.InitializationComplete() {
return
}
for {
select {
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.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
func (a *WorkerAdmin) QueueMessage(m interface{}) bool {
if m == nil {
a.logger.Warning("Nil message not added to queue")
return false
}
select {
case a.queue <- m:
return true
default:
return false
}
}
// StopWorker ends the worker's event loop, preventing it from picking further jobs
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)
}
w.Stop(blocking)
return nil
}
// StopAll ends all worker's event loops
func (a *WorkerAdmin) StopAll(blocking bool) error {
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 blocking {
wg.Wait()
}
return nil
}
// QueueSize returns the current queue size
func (a *WorkerAdmin) QueueSize() int {
return len(a.queue)
}
// IsWorkerRunning returns true if the worker exists and is currently running
func (a *WorkerAdmin) IsWorkerRunning(name string) bool {
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{
workers: make(map[string]*workerWrapper, 0),
logger: logger,
queue: make(chan interface{}, queueSize),
}
}