github.com/ssdev-go/moby@v17.12.1-ce-rc2+incompatible/restartmanager/restartmanager.go (about)

     1  package restartmanager
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"sync"
     7  	"time"
     8  
     9  	"github.com/docker/docker/api/types/container"
    10  )
    11  
    12  const (
    13  	backoffMultiplier = 2
    14  	defaultTimeout    = 100 * time.Millisecond
    15  	maxRestartTimeout = 1 * time.Minute
    16  )
    17  
    18  // ErrRestartCanceled is returned when the restart manager has been
    19  // canceled and will no longer restart the container.
    20  var ErrRestartCanceled = errors.New("restart canceled")
    21  
    22  // RestartManager defines object that controls container restarting rules.
    23  type RestartManager interface {
    24  	Cancel() error
    25  	ShouldRestart(exitCode uint32, hasBeenManuallyStopped bool, executionDuration time.Duration) (bool, chan error, error)
    26  }
    27  
    28  type restartManager struct {
    29  	sync.Mutex
    30  	sync.Once
    31  	policy       container.RestartPolicy
    32  	restartCount int
    33  	timeout      time.Duration
    34  	active       bool
    35  	cancel       chan struct{}
    36  	canceled     bool
    37  }
    38  
    39  // New returns a new restartManager based on a policy.
    40  func New(policy container.RestartPolicy, restartCount int) RestartManager {
    41  	return &restartManager{policy: policy, restartCount: restartCount, cancel: make(chan struct{})}
    42  }
    43  
    44  func (rm *restartManager) SetPolicy(policy container.RestartPolicy) {
    45  	rm.Lock()
    46  	rm.policy = policy
    47  	rm.Unlock()
    48  }
    49  
    50  func (rm *restartManager) ShouldRestart(exitCode uint32, hasBeenManuallyStopped bool, executionDuration time.Duration) (bool, chan error, error) {
    51  	if rm.policy.IsNone() {
    52  		return false, nil, nil
    53  	}
    54  	rm.Lock()
    55  	unlockOnExit := true
    56  	defer func() {
    57  		if unlockOnExit {
    58  			rm.Unlock()
    59  		}
    60  	}()
    61  
    62  	if rm.canceled {
    63  		return false, nil, ErrRestartCanceled
    64  	}
    65  
    66  	if rm.active {
    67  		return false, nil, fmt.Errorf("invalid call on an active restart manager")
    68  	}
    69  	// if the container ran for more than 10s, regardless of status and policy reset the
    70  	// the timeout back to the default.
    71  	if executionDuration.Seconds() >= 10 {
    72  		rm.timeout = 0
    73  	}
    74  	switch {
    75  	case rm.timeout == 0:
    76  		rm.timeout = defaultTimeout
    77  	case rm.timeout < maxRestartTimeout:
    78  		rm.timeout *= backoffMultiplier
    79  	}
    80  	if rm.timeout > maxRestartTimeout {
    81  		rm.timeout = maxRestartTimeout
    82  	}
    83  
    84  	var restart bool
    85  	switch {
    86  	case rm.policy.IsAlways():
    87  		restart = true
    88  	case rm.policy.IsUnlessStopped() && !hasBeenManuallyStopped:
    89  		restart = true
    90  	case rm.policy.IsOnFailure():
    91  		// the default value of 0 for MaximumRetryCount means that we will not enforce a maximum count
    92  		if max := rm.policy.MaximumRetryCount; max == 0 || rm.restartCount < max {
    93  			restart = exitCode != 0
    94  		}
    95  	}
    96  
    97  	if !restart {
    98  		rm.active = false
    99  		return false, nil, nil
   100  	}
   101  
   102  	rm.restartCount++
   103  
   104  	unlockOnExit = false
   105  	rm.active = true
   106  	rm.Unlock()
   107  
   108  	ch := make(chan error)
   109  	go func() {
   110  		select {
   111  		case <-rm.cancel:
   112  			ch <- ErrRestartCanceled
   113  			close(ch)
   114  		case <-time.After(rm.timeout):
   115  			rm.Lock()
   116  			close(ch)
   117  			rm.active = false
   118  			rm.Unlock()
   119  		}
   120  	}()
   121  
   122  	return true, ch, nil
   123  }
   124  
   125  func (rm *restartManager) Cancel() error {
   126  	rm.Do(func() {
   127  		rm.Lock()
   128  		rm.canceled = true
   129  		close(rm.cancel)
   130  		rm.Unlock()
   131  	})
   132  	return nil
   133  }