MM-28859 Add feature flag managment system using split.io and remove viper. (#15954)
* Add feature flag managment system using split.io and remove viper. * Fixing tests. * Attempt to fix postgres tests. * Fix watch filepath for advanced logging. * Review fixes. * Some error wrapping. * Remove unessisary store interface. * Desanitize SplitKey * Simplify. * Review feedback. * Rename split mlog adatper to split logger. * fsInner * Style. * Restore oldcfg test. * Downgrading non-actionable feature flag errors to warnings. Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
8bb772638c
Коммит
1aadd36644
13
vendor/github.com/splitio/go-toolkit/v3/LICENSE
сгенерированный
поставляемый
Обычный файл
13
vendor/github.com/splitio/go-toolkit/v3/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.
|
||||
179
vendor/github.com/splitio/go-toolkit/v3/asynctask/asynctasks.go
сгенерированный
поставляемый
Обычный файл
179
vendor/github.com/splitio/go-toolkit/v3/asynctask/asynctasks.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,179 @@
|
||||
package asynctask
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/splitio/go-toolkit/v3/logging"
|
||||
)
|
||||
|
||||
// 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{}
|
||||
}
|
||||
|
||||
const (
|
||||
taskMessageStop = iota
|
||||
taskMessageWakeup
|
||||
)
|
||||
|
||||
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.logger != nil {
|
||||
t.logger.Warning("Task %s is already running. 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)",
|
||||
t.name,
|
||||
t.period,
|
||||
))
|
||||
t.logger.Error(r)
|
||||
}
|
||||
time.Sleep(time.Duration(t.period) * time.Second)
|
||||
}
|
||||
}()
|
||||
|
||||
// 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())
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Create timeout timer
|
||||
idleDuration := time.Second * time.Duration(t.period)
|
||||
taskTimer := time.NewTimer(idleDuration)
|
||||
defer taskTimer.Stop()
|
||||
|
||||
// Task execution
|
||||
for t._running() {
|
||||
// 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)
|
||||
|
||||
// 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)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
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._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 blocking {
|
||||
// If blocking was set to true, wait until an empty strcut is pushed into the channel
|
||||
<-t.finishChan
|
||||
}
|
||||
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._running()
|
||||
}
|
||||
|
||||
// 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),
|
||||
finishChan: make(chan struct{}, 1),
|
||||
}
|
||||
t.running.Store(false)
|
||||
t.finished.Store(false)
|
||||
return &t
|
||||
}
|
||||
67
vendor/github.com/splitio/go-toolkit/v3/common/iterutil.go
сгенерированный
поставляемый
Обычный файл
67
vendor/github.com/splitio/go-toolkit/v3/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:
|
||||
}
|
||||
}
|
||||
}
|
||||
105
vendor/github.com/splitio/go-toolkit/v3/common/refutil.go
сгенерированный
поставляемый
Обычный файл
105
vendor/github.com/splitio/go-toolkit/v3/common/refutil.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,105 @@
|
||||
package common
|
||||
|
||||
// StringRef returns ref
|
||||
func StringRef(str string) *string {
|
||||
return &str
|
||||
}
|
||||
|
||||
// IntRef returns ref
|
||||
func IntRef(number int) *int {
|
||||
return &number
|
||||
}
|
||||
|
||||
// Int64Ref returns ref
|
||||
func Int64Ref(number int64) *int64 {
|
||||
return &number
|
||||
}
|
||||
|
||||
// Float64Ref returns ref
|
||||
func Float64Ref(number float64) *float64 {
|
||||
return &number
|
||||
}
|
||||
|
||||
// Int64Value returns value
|
||||
func Int64Value(number *int64) int64 {
|
||||
if number == nil {
|
||||
return 0
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
18
vendor/github.com/splitio/go-toolkit/v3/common/sliceutil.go
сгенерированный
поставляемый
Обычный файл
18
vendor/github.com/splitio/go-toolkit/v3/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/v3/common/strutil.go
сгенерированный
поставляемый
Обычный файл
9
vendor/github.com/splitio/go-toolkit/v3/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/v3/common/timeutil.go
сгенерированный
поставляемый
Обычный файл
25
vendor/github.com/splitio/go-toolkit/v3/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/v3/datastructures/set/functions.go
сгенерированный
поставляемый
Обычный файл
56
vendor/github.com/splitio/go-toolkit/v3/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/v3/datastructures/set/implementations.go
сгенерированный
поставляемый
Обычный файл
355
vendor/github.com/splitio/go-toolkit/v3/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/v3/datastructures/set/set.go
сгенерированный
поставляемый
Обычный файл
24
vendor/github.com/splitio/go-toolkit/v3/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/v3/injection/container.go
сгенерированный
поставляемый
Обычный файл
47
vendor/github.com/splitio/go-toolkit/v3/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/v3/logging/functions.go
сгенерированный
поставляемый
Обычный файл
33
vendor/github.com/splitio/go-toolkit/v3/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/v3/logging/interface.go
сгенерированный
поставляемый
Обычный файл
12
vendor/github.com/splitio/go-toolkit/v3/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/v3/logging/levels.go
сгенерированный
поставляемый
Обычный файл
93
vendor/github.com/splitio/go-toolkit/v3/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/v3/logging/logging.go
сгенерированный
поставляемый
Обычный файл
129
vendor/github.com/splitio/go-toolkit/v3/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/v3/logging/rotate.go
сгенерированный
поставляемый
Обычный файл
106
vendor/github.com/splitio/go-toolkit/v3/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/v3/nethelpers/ip.go
сгенерированный
поставляемый
Обычный файл
44
vendor/github.com/splitio/go-toolkit/v3/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/v3/provisional/hashing/murmur128.go
сгенерированный
поставляемый
Обычный файл
231
vendor/github.com/splitio/go-toolkit/v3/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/v3/provisional/int64cache/cache.go
сгенерированный
поставляемый
Обычный файл
91
vendor/github.com/splitio/go-toolkit/v3/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/v3/queuecache/cache.go
сгенерированный
поставляемый
Обычный файл
127
vendor/github.com/splitio/go-toolkit/v3/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/v3/redis/helpers/helpers.go
сгенерированный
поставляемый
Обычный файл
23
vendor/github.com/splitio/go-toolkit/v3/redis/helpers/helpers.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,23 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/splitio/go-toolkit/v3/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/v3/redis/prefixedclient.go
сгенерированный
поставляемый
Обычный файл
160
vendor/github.com/splitio/go-toolkit/v3/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/v3/redis/types.go
сгенерированный
поставляемый
Обычный файл
8
vendor/github.com/splitio/go-toolkit/v3/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/v3/redis/wrapper.go
сгенерированный
поставляемый
Обычный файл
293
vendor/github.com/splitio/go-toolkit/v3/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
|
||||
}
|
||||
214
vendor/github.com/splitio/go-toolkit/v3/sse/sse.go
сгенерированный
поставляемый
Обычный файл
214
vendor/github.com/splitio/go-toolkit/v3/sse/sse.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,214 @@
|
||||
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("Awaited unexpected event")
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
cancel()
|
||||
shouldRun.Store(false)
|
||||
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 {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
166
vendor/github.com/splitio/go-toolkit/v3/workerpool/workerpool.go
сгенерированный
поставляемый
Обычный файл
166
vendor/github.com/splitio/go-toolkit/v3/workerpool/workerpool.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,166 @@
|
||||
package workerpool
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/splitio/go-toolkit/v3/logging"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
workerSignalStop = iota
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func (a *WorkerAdmin) workerWrapper(w Worker) {
|
||||
a.signalsMutex.Lock()
|
||||
a.signals[w.Name()] = make(chan int, 10)
|
||||
a.signalsMutex.Unlock()
|
||||
defer a.wg.Done()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
a.logger.Error(fmt.Sprintf(
|
||||
"Worker %s is panicking with the following error \"%s\" and will be shutted down.",
|
||||
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()
|
||||
}
|
||||
}()
|
||||
defer w.Cleanup()
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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) error {
|
||||
a.signalsMutex.RLock()
|
||||
c, ok := a.signals[name]
|
||||
a.signalsMutex.RUnlock()
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
if len(failed) > 0 {
|
||||
return fmt.Errorf("Workers %v failed to shutdown", failed)
|
||||
}
|
||||
|
||||
if blocking {
|
||||
a.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.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
|
||||
}
|
||||
|
||||
// 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),
|
||||
logger: logger,
|
||||
queue: make(chan interface{}, queueSize),
|
||||
}
|
||||
}
|
||||
Ссылка в новой задаче
Block a user