-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Automatically remove target after persistent failures
- Loading branch information
Showing
2 changed files
with
122 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,40 +1,107 @@ | ||
package datatypes | ||
|
||
import "sync" | ||
import ( | ||
"log" | ||
"sync" | ||
"time" | ||
|
||
"github.com/sony/gobreaker" | ||
) | ||
|
||
// MirrorTargets has the set of target URLs to mirror to | ||
// It protects these for multi threaded access | ||
type MirrorTargets struct { | ||
sync.RWMutex | ||
targets map[string]bool | ||
targets map[string]*targetState | ||
persistentFailureTimeout time.Duration | ||
retryAfter time.Duration | ||
} | ||
|
||
type MirrorSettings struct { | ||
PersistentFailureTimeout time.Duration | ||
RetryAfter time.Duration | ||
} | ||
|
||
func NewMirrorTargets() *MirrorTargets { | ||
type targetState struct { | ||
sync.Mutex | ||
firstFailure time.Time | ||
persistentFailureTimeout time.Duration | ||
circuitBreaker *gobreaker.CircuitBreaker | ||
onTargetFailed func(target string) | ||
} | ||
|
||
func NewMirrorTargets(settings MirrorSettings) *MirrorTargets { | ||
return &MirrorTargets{ | ||
targets: make(map[string]bool), | ||
targets: make(map[string]*targetState), | ||
persistentFailureTimeout: settings.PersistentFailureTimeout, | ||
retryAfter: settings.RetryAfter, | ||
} | ||
} | ||
|
||
func (me *MirrorTargets) Add(targets []string) { | ||
me.Lock() | ||
func (mt *MirrorTargets) Add(targets []string) { | ||
log.Printf("Adding %s to targets list.", targets) | ||
mt.Lock() | ||
defer mt.Unlock() | ||
for _, url := range targets { | ||
me.targets[url] = true | ||
mt.targets[url] = newTargetState(url, mt.persistentFailureTimeout, mt.retryAfter, func(target string) { | ||
mt.Delete([]string{target}) | ||
}) | ||
} | ||
me.Unlock() | ||
} | ||
|
||
func (me *MirrorTargets) Delete(targets []string) { | ||
me.Lock() | ||
func (mt *MirrorTargets) Delete(targets []string) { | ||
log.Printf("Removing %s from targets list.", targets) | ||
mt.Lock() | ||
defer mt.Unlock() | ||
for _, url := range targets { | ||
delete(me.targets, url) | ||
delete(mt.targets, url) | ||
} | ||
} | ||
|
||
func (mt *MirrorTargets) ForEach(f func(string, *gobreaker.CircuitBreaker)) { | ||
mt.RLock() | ||
defer mt.RUnlock() | ||
for url, target := range mt.targets { | ||
f(url, target.circuitBreaker) | ||
} | ||
} | ||
|
||
func (ts *targetState) onBreakerChange(name string, from gobreaker.State, to gobreaker.State) { | ||
if from == gobreaker.StateClosed && to == gobreaker.StateOpen { | ||
ts.Lock() | ||
defer ts.Unlock() | ||
ts.firstFailure = time.Now() | ||
log.Printf("Temporary not mirroring to target %s.", name) | ||
} else if to == gobreaker.StateOpen { | ||
ts.Lock() | ||
defer ts.Unlock() | ||
if !ts.firstFailure.IsZero() && time.Now().Sub(ts.firstFailure) > ts.persistentFailureTimeout { | ||
log.Printf("%s is persistently failing.", name) | ||
ts.onTargetFailed(name) | ||
} | ||
} else if to == gobreaker.StateHalfOpen { | ||
log.Printf("Retrying target %s.", name) | ||
} else if to == gobreaker.StateClosed { | ||
ts.Lock() | ||
defer ts.Unlock() | ||
ts.firstFailure = time.Time{} | ||
log.Printf("Resuming mirroring to target %s.", name) | ||
} | ||
me.Unlock() | ||
} | ||
|
||
func (me *MirrorTargets) ForEach(f func(string)) { | ||
me.RLock() | ||
for url := range me.targets { | ||
f(url) | ||
func newTargetState(name string, persistentFailureTimeout time.Duration, retryAfter time.Duration, onTargetFailed func(target string)) *targetState { | ||
targetState := &targetState{ | ||
circuitBreaker: nil, | ||
onTargetFailed: onTargetFailed, | ||
persistentFailureTimeout: persistentFailureTimeout, | ||
} | ||
settings := gobreaker.Settings{ | ||
Name: name, | ||
MaxRequests: 1, | ||
Interval: 0, // Never clear counts | ||
Timeout: retryAfter, // When open retry after 60 seconds | ||
OnStateChange: targetState.onBreakerChange, | ||
} | ||
me.RUnlock() | ||
targetState.circuitBreaker = gobreaker.NewCircuitBreaker(settings) | ||
return targetState | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters