dotcloud/docker

View on GitHub
restartmanager/restartmanager.go

Summary

Maintainability
A
2 hrs
Test Coverage
package restartmanager // import "github.com/docker/docker/restartmanager"

import (
    "errors"
    "fmt"
    "sync"
    "time"

    "github.com/docker/docker/api/types/container"
)

const (
    backoffMultiplier = 2
    defaultTimeout    = 100 * time.Millisecond
    maxRestartTimeout = 1 * time.Minute
)

// ErrRestartCanceled is returned when the restart manager has been
// canceled and will no longer restart the container.
var ErrRestartCanceled = errors.New("restart canceled")

// RestartManager defines object that controls container restarting rules.
type RestartManager struct {
    sync.Mutex
    sync.Once
    policy       container.RestartPolicy
    restartCount int
    timeout      time.Duration
    active       bool
    cancel       chan struct{}
    canceled     bool
}

// New returns a new RestartManager based on a policy.
func New(policy container.RestartPolicy, restartCount int) *RestartManager {
    return &RestartManager{policy: policy, restartCount: restartCount, cancel: make(chan struct{})}
}

// SetPolicy sets the restart-policy for the RestartManager.
func (rm *RestartManager) SetPolicy(policy container.RestartPolicy) {
    rm.Lock()
    rm.policy = policy
    rm.Unlock()
}

// ShouldRestart returns whether the container should be restarted.
func (rm *RestartManager) ShouldRestart(exitCode uint32, hasBeenManuallyStopped bool, executionDuration time.Duration) (bool, chan error, error) {
    if rm.policy.IsNone() {
        return false, nil, nil
    }
    rm.Lock()
    unlockOnExit := true
    defer func() {
        if unlockOnExit {
            rm.Unlock()
        }
    }()

    if rm.canceled {
        return false, nil, ErrRestartCanceled
    }

    if rm.active {
        return false, nil, fmt.Errorf("invalid call on an active restart manager")
    }
    // if the container ran for more than 10s, regardless of status and policy reset
    // the timeout back to the default.
    if executionDuration.Seconds() >= 10 {
        rm.timeout = 0
    }
    switch {
    case rm.timeout == 0:
        rm.timeout = defaultTimeout
    case rm.timeout < maxRestartTimeout:
        rm.timeout *= backoffMultiplier
    }
    if rm.timeout > maxRestartTimeout {
        rm.timeout = maxRestartTimeout
    }

    var restart bool
    switch {
    case rm.policy.IsAlways():
        restart = true
    case rm.policy.IsUnlessStopped() && !hasBeenManuallyStopped:
        restart = true
    case rm.policy.IsOnFailure():
        // the default value of 0 for MaximumRetryCount means that we will not enforce a maximum count
        if maxRetryCount := rm.policy.MaximumRetryCount; maxRetryCount == 0 || rm.restartCount < maxRetryCount {
            restart = exitCode != 0
        }
    }

    if !restart {
        rm.active = false
        return false, nil, nil
    }

    rm.restartCount++

    unlockOnExit = false
    rm.active = true
    rm.Unlock()

    ch := make(chan error)
    go func() {
        timeout := time.NewTimer(rm.timeout)
        defer timeout.Stop()

        select {
        case <-rm.cancel:
            ch <- ErrRestartCanceled
            close(ch)
        case <-timeout.C:
            rm.Lock()
            close(ch)
            rm.active = false
            rm.Unlock()
        }
    }()

    return true, ch, nil
}

// Cancel tells the RestartManager to no longer restart the container.
func (rm *RestartManager) Cancel() {
    rm.Do(func() {
        rm.Lock()
        rm.canceled = true
        close(rm.cancel)
        rm.Unlock()
    })
}