func (c *CircuitBreaker) setTripped(e endpoint.Endpoint) bool {
	c.m.Lock()
	defer c.m.Unlock()
	if c.state == stateTripped {
		log.Infof("%v skip set tripped", c)
		return false
	}
	c.setState(stateTripped, c.tm.UtcNow().Add(c.o.FallbackDuration))
	c.resetStats(e)
	return true
}
setTripped sets state only when current state is not tripped already
package circuitbreaker
import (
	"fmt"
	"net/http"
	"sync"
	"time"
	"github.com/mailgun/log"
	"github.com/mailgun/timetools"
	"github.com/mailgun/vulcan/endpoint"
	"github.com/mailgun/vulcan/metrics"
	"github.com/mailgun/vulcan/middleware"
	"github.com/mailgun/vulcan/request"
	"github.com/mailgun/vulcan/threshold"
)
type cbState int
func (s cbState) String() string {
	switch s {
	case stateStandby:
		return "standby"
	case stateTripped:
		return "tripped"
	case stateRecovering:
		return "recovering"
	}
	return "undefined"
}
const (
	stateStandby = iota
	stateTripped
	stateRecovering
)
const (
	defaultFallbackDuration = 10 * time.Second
	defaultRecoveryDuration = 10 * time.Second
	defaultCheckPeriod      = 100 * time.Millisecond
)
type Options struct {
	CheckPeriod time.Duration
	FallbackDuration time.Duration
	RecoveryDuration time.Duration
	TimeProvider timetools.TimeProvider
	OnTripped SideEffect
	OnStandby SideEffect
}
type CircuitBreaker struct {
	o Options
	m       *sync.RWMutex
	tm      timetools.TimeProvider
	metrics *metrics.RoundTripMetrics
	condition threshold.Predicate
	duration  time.Duration
	state cbState
	until time.Time
	rc *ratioController
	checkPeriod time.Duration
	lastCheck   time.Time
	fallback middleware.Middleware
}
func New(condition threshold.Predicate, fallback middleware.Middleware, options Options) (*CircuitBreaker, error) {
	if condition == nil || fallback == nil {
		return nil, fmt.Errorf("provide non nil condition and fallback")
	}
	o, err := setDefaults(options)
	if err != nil {
		return nil, err
	}
	mt, err := metrics.NewRoundTripMetrics(metrics.RoundTripOptions{TimeProvider: o.TimeProvider})
	if err != nil {
		return nil, err
	}
	cb := &CircuitBreaker{
		tm:          o.TimeProvider,
		o:           o,
		condition:   condition,
		fallback:    fallback,
		metrics:     mt,
		m:           &sync.RWMutex{},
		checkPeriod: o.CheckPeriod,
	}
	return cb, nil
}
func (c *CircuitBreaker) String() string {
	switch c.state {
	case stateTripped, stateRecovering:
		return fmt.Sprintf("CircuitBreaker(state=%v, until=%v)", c.state, c.until)
	default:
		return fmt.Sprintf("CircuitBreaker(state=%v)", c.state)
	}
}
func (c *CircuitBreaker) ProcessRequest(r request.Request) (*http.Response, error) {
	if c.isStandby() {
		c.markToRecordMetrics(r)
		return nil, nil
	}
	c.m.Lock()
	defer c.m.Unlock()
	log.Infof("%v is in error handling state", c)
	switch c.state {
	case stateStandby:
		return nil, nil
	case stateTripped:
		if c.tm.UtcNow().Before(c.until) {
			return c.fallback.ProcessRequest(r)
		}
		c.setRecovering()
		fallthrough
	case stateRecovering:
		if c.tm.UtcNow().After(c.until) {
			c.markToRecordMetrics(r)
			c.setState(stateStandby, c.tm.UtcNow())
			return nil, nil
		}
		if c.rc.allowRequest() {
			c.markToRecordMetrics(r)
			return nil, nil
		}
		return c.fallback.ProcessRequest(r)
	}
	return nil, nil
}
func (c *CircuitBreaker) ProcessResponse(r request.Request, a request.Attempt) {
	if c.shouldRecordMetrics(r) {
		c.metrics.RecordMetrics(a)
	}
	if c.checkCondition(r) {
		c.setTripped(a.GetEndpoint())
	}
}
func (c *CircuitBreaker) isStandby() bool {
	c.m.RLock()
	defer c.m.RUnlock()
	return c.state == stateStandby
}
func (c *CircuitBreaker) exec(s SideEffect) {
	if s == nil {
		return
	}
	go func() {
		if err := s.Exec(); err != nil {
			log.Errorf("%v side effect failure: %v", c, err)
		}
	}()
}
func (c *CircuitBreaker) setState(new cbState, until time.Time) {
	log.Infof("%v setting state to %v, until %v", c, new, until)
	c.state = new
	c.until = until
	switch new {
	case stateTripped:
		c.exec(c.o.OnTripped)
	case stateStandby:
		c.exec(c.o.OnStandby)
	}
}