Implement Backoff state and modify restart behavior.

This commit is contained in:
Ryan Bourgeois 2013-11-30 16:12:01 -08:00
parent ae0f96dd2e
commit 77aa83ecfc

View File

@ -13,10 +13,11 @@ import (
const ( const (
// Service defaults. // Service defaults.
DefaultStopSignal = syscall.SIGINT DefaultStartTimeout = 1 * time.Second
DefaultStopTimeout = 5 * time.Second DefaultStartRetries = 3
DefaultRestart = true DefaultStopSignal = syscall.SIGINT
DefaultRetries = 3 DefaultStopTimeout = 5 * time.Second
DefaultStopRestart = true
// Service commands. // Service commands.
Start = "start" Start = "start"
@ -30,7 +31,7 @@ const (
Stopping = "stopping" Stopping = "stopping"
Stopped = "stopped" Stopped = "stopped"
Exited = "exited" Exited = "exited"
//TODO: Implement Backoff state. Backoff = "backoff"
) )
// Command is sent to a Service to initiate a state change. // Command is sent to a Service to initiate a state change.
@ -66,17 +67,18 @@ type Event struct {
// Service represents a controllable process. Exported fields may be set to configure the service. // Service represents a controllable process. Exported fields may be set to configure the service.
type Service struct { type Service struct {
Directory string // The process's working directory. Defaults to the current directory. Directory string // The process's working directory. Defaults to the current directory.
Environment []string // The environment of the process. Defaults to nil which indicatesA the current environment. Environment []string // The environment of the process. Defaults to nil which indicatesA the current environment.
StopSignal syscall.Signal // The signal to send when stopping the process. Defaults to SIGINT. StartTimeout time.Duration // How long the process has to run before it's considered Running.
StopTimeout time.Duration // How long to wait for a process to stop before sending a SIGKILL. Defaults to 5s. StartRetries int // How many times to restart a process if it fails to start. Defaults to 3.
Restart bool // Whether or not to restart the process if it exits unexpectedly. Defaults to true. StopSignal syscall.Signal // The signal to send when stopping the process. Defaults to SIGINT.
Retries int // How many times to restart a process if it fails to start. Defaults to 3. StopTimeout time.Duration // How long to wait for a process to stop before sending a SIGKILL. Defaults to 5s.
Stdout io.Writer // Where to send the process's stdout. Defaults to /dev/null. StopRestart bool // Whether or not to restart the process if it exits unexpectedly. Defaults to true.
Stderr io.Writer // Where to send the process's stderr. Defaults to /dev/null. Stdout io.Writer // Where to send the process's stdout. Defaults to /dev/null.
args []string // The command line of the process to run. Stderr io.Writer // Where to send the process's stderr. Defaults to /dev/null.
command *exec.Cmd // The os/exec command running the process. args []string // The command line of the process to run.
state string // The state of the Service. command *exec.Cmd // The os/exec command running the process.
state string // The state of the Service.
} }
// New creates a new service with the default configution. // New creates a new service with the default configution.
@ -85,10 +87,11 @@ func NewService(args []string) (svc *Service, err error) {
svc = &Service{ svc = &Service{
cwd, cwd,
nil, nil,
DefaultStartTimeout,
DefaultStartRetries,
DefaultStopSignal, DefaultStopSignal,
DefaultStopTimeout, DefaultStopTimeout,
DefaultRestart, DefaultStopRestart,
DefaultRetries,
nil, nil,
nil, nil,
args, args,
@ -122,25 +125,6 @@ func (s Service) makeCommand() *exec.Cmd {
return cmd return cmd
} }
func (s *Service) startProcess(states chan string) (err error) {
defer func() {
if err != nil {
//TODO: Do something with this error.
states <- Exited
}
}()
s.command = s.makeCommand()
if err = s.command.Start(); err != nil {
return
}
states <- Running
s.command.Wait()
states <- Exited
return
}
func (s *Service) Run(commands <-chan Command, events chan<- Event) { func (s *Service) Run(commands <-chan Command, events chan<- Event) {
var lastCommand *Command var lastCommand *Command
states := make(chan string) states := make(chan string)
@ -166,13 +150,25 @@ func (s *Service) Run(commands <-chan Command, events chan<- Event) {
} }
start := func(cmd *Command) { start := func(cmd *Command) {
if s.state != Stopped && s.state != Exited { if s.state != Stopped && s.state != Exited && s.state != Backoff {
sendInvalidCmd(cmd, Starting) sendInvalidCmd(cmd, Starting)
return return
} }
sendEvent(Starting) sendEvent(Starting)
go s.startProcess(states) go func() {
s.command = s.makeCommand()
startTime := time.Now()
if err := s.command.Start(); err == nil { //TODO: Don't swallow this error.
states <- Running
s.command.Wait()
if time.Now().Sub(startTime) > s.StartTimeout {
states <- Backoff
return
}
}
states <- Exited
}()
} }
stop := func(cmd *Command) { stop := func(cmd *Command) {
@ -220,7 +216,6 @@ func (s *Service) Run(commands <-chan Command, events chan<- Event) {
stop(cmd) stop(cmd)
} }
} }
retries = 0
} }
onStopped := func(cmd *Command) { onStopped := func(cmd *Command) {
@ -237,13 +232,22 @@ func (s *Service) Run(commands <-chan Command, events chan<- Event) {
} }
} }
onExited := func(cmd *Command, retries int) bool { onExited := func(cmd *Command) {
sendEvent(Exited) sendEvent(Exited)
if s.Restart && retries < s.Retries { if s.StopRestart {
start(cmd) start(cmd)
return true
} }
return false }
onBackoff := func(cmd *Command) {
if retries < s.StartRetries {
sendEvent(Backoff)
start(cmd)
retries++
} else {
sendEvent(Exited)
retries = 0
}
} }
loop: loop:
@ -258,10 +262,10 @@ loop:
if s.state == Stopping { if s.state == Stopping {
onStopped(lastCommand) onStopped(lastCommand)
} else { } else {
if onExited(lastCommand, retries) { onExited(lastCommand)
retries++
}
} }
case Backoff:
onBackoff(lastCommand)
} }
if lastCommand != nil { if lastCommand != nil {
if lastCommand.Name == Restart && s.state == Running { if lastCommand.Name == Restart && s.state == Running {