restartmanager/restartmanager.go
9c4570a9
 package restartmanager
 
 import (
fc2e2234
 	"errors"
9c4570a9
 	"fmt"
 	"sync"
 	"time"
 
 	"github.com/docker/engine-api/types/container"
 )
 
 const (
 	backoffMultiplier = 2
 	defaultTimeout    = 100 * time.Millisecond
 )
 
fc2e2234
 // ErrRestartCanceled is returned when the restart manager has been
 // canceled and will no longer restart the container.
 var ErrRestartCanceled = errors.New("restart canceled")
 
9c4570a9
 // RestartManager defines object that controls container restarting rules.
 type RestartManager interface {
 	Cancel() error
b6db56b5
 	ShouldRestart(exitCode uint32, hasBeenManuallyStopped bool, executionDuration time.Duration) (bool, chan error, error)
9c4570a9
 }
 
 type restartManager struct {
 	sync.Mutex
 	sync.Once
 	policy       container.RestartPolicy
51e42e6e
 	restartCount int
9c4570a9
 	timeout      time.Duration
 	active       bool
 	cancel       chan struct{}
 	canceled     bool
 }
 
 // New returns a new restartmanager based on a policy.
51e42e6e
 func New(policy container.RestartPolicy, restartCount int) RestartManager {
 	return &restartManager{policy: policy, restartCount: restartCount, cancel: make(chan struct{})}
9c4570a9
 }
 
 func (rm *restartManager) SetPolicy(policy container.RestartPolicy) {
 	rm.Lock()
 	rm.policy = policy
 	rm.Unlock()
 }
 
b6db56b5
 func (rm *restartManager) ShouldRestart(exitCode uint32, hasBeenManuallyStopped bool, executionDuration time.Duration) (bool, chan error, error) {
494297ba
 	if rm.policy.IsNone() {
 		return false, nil, nil
 	}
9c4570a9
 	rm.Lock()
 	unlockOnExit := true
 	defer func() {
 		if unlockOnExit {
 			rm.Unlock()
 		}
 	}()
 
 	if rm.canceled {
fc2e2234
 		return false, nil, ErrRestartCanceled
9c4570a9
 	}
 
 	if rm.active {
 		return false, nil, fmt.Errorf("invalid call on active restartmanager")
 	}
6a5a1507
 	// if the container ran for more than 10s, regardless of status and policy reset the
b6db56b5
 	// the timeout back to the default.
 	if executionDuration.Seconds() >= 10 {
 		rm.timeout = 0
 	}
9c4570a9
 	if rm.timeout == 0 {
 		rm.timeout = defaultTimeout
 	} else {
 		rm.timeout *= backoffMultiplier
 	}
 
 	var restart bool
 	switch {
51e42e6e
 	case rm.policy.IsAlways():
 		restart = true
 	case rm.policy.IsUnlessStopped() && !hasBeenManuallyStopped:
9c4570a9
 		restart = true
 	case rm.policy.IsOnFailure():
 		// the default value of 0 for MaximumRetryCount means that we will not enforce a maximum count
51e42e6e
 		if max := rm.policy.MaximumRetryCount; max == 0 || rm.restartCount < max {
9c4570a9
 			restart = exitCode != 0
 		}
 	}
 
 	if !restart {
 		rm.active = false
 		return false, nil, nil
 	}
 
51e42e6e
 	rm.restartCount++
 
9c4570a9
 	unlockOnExit = false
 	rm.active = true
 	rm.Unlock()
 
 	ch := make(chan error)
 	go func() {
 		select {
 		case <-rm.cancel:
fc2e2234
 			ch <- ErrRestartCanceled
9c4570a9
 			close(ch)
 		case <-time.After(rm.timeout):
 			rm.Lock()
 			close(ch)
 			rm.active = false
 			rm.Unlock()
 		}
 	}()
 
 	return true, ch, nil
 }
 
 func (rm *restartManager) Cancel() error {
 	rm.Do(func() {
 		rm.Lock()
 		rm.canceled = true
 		close(rm.cancel)
 		rm.Unlock()
 	})
 	return nil
 }