@ -54,7 +54,33 @@ func Run(newCfg *Config) error {
currentCfgMu . Lock ( )
currentCfgMu . Lock ( )
defer currentCfgMu . Unlock ( )
defer currentCfgMu . Unlock ( )
if newCfg != nil {
// run the new config and start all its apps
err := run ( newCfg , true )
if err != nil {
return err
}
// swap old config with the new one
oldCfg := currentCfg
currentCfg = newCfg
// Stop, Cleanup each old app
unsyncedStop ( oldCfg )
return nil
}
// run runs newCfg and starts all its apps if
// start is true. If any errors happen, cleanup
// is performed if any modules were provisioned;
// apps that were started already will be stopped,
// so this function should not leak resources if
// an error is returned.
func run ( newCfg * Config , start bool ) error {
if newCfg == nil {
return nil
}
// because we will need to roll back any state
// because we will need to roll back any state
// modifications if this function errors, we
// modifications if this function errors, we
// keep a single error value and scope all
// keep a single error value and scope all
@ -121,12 +147,18 @@ func Run(newCfg *Config) error {
return err
return err
}
}
if ! start {
return nil
}
// Start
// Start
err = func ( ) error {
return func ( ) error {
var started [ ] string
var started [ ] string
for name , a := range newCfg . apps {
for name , a := range newCfg . apps {
err := a . Start ( )
err := a . Start ( )
if err != nil {
if err != nil {
// an app failed to start, so we need to stop
// all other apps that were already started
for _ , otherAppName := range started {
for _ , otherAppName := range started {
err2 := newCfg . apps [ otherAppName ] . Stop ( )
err2 := newCfg . apps [ otherAppName ] . Stop ( )
if err2 != nil {
if err2 != nil {
@ -140,19 +172,6 @@ func Run(newCfg *Config) error {
}
}
return nil
return nil
} ( )
} ( )
if err != nil {
return err
}
}
// swap old config with the new one
oldCfg := currentCfg
currentCfg = newCfg
// Stop, Cleanup each old app
unsyncedStop ( oldCfg )
return nil
}
}
// Stop stops running the current configuration.
// Stop stops running the current configuration.
@ -168,26 +187,34 @@ func Stop() error {
return nil
return nil
}
}
// unsyncedStop stops oldC fg from running, but if
// unsyncedStop stops c fg from running, but if
// applicable, you need to acquire locks yourself.
// applicable, you need to acquire locks yourself.
// It is a no-op if oldC fg is nil. If any app
// It is a no-op if c fg is nil. If any app
// returns an error when stopping, it is logged
// returns an error when stopping, it is logged
// and the function continues with the next app.
// and the function continues with the next app.
func unsyncedStop ( oldCfg * Config ) {
// This function assumes all apps in cfg were
if oldCfg == nil {
// successfully started.
func unsyncedStop ( cfg * Config ) {
if cfg == nil {
return
return
}
}
// stop each app
// stop each app
for name , a := range oldC fg. apps {
for name , a := range c fg. apps {
err := a . Stop ( )
err := a . Stop ( )
if err != nil {
if err != nil {
log . Printf ( "[ERROR] stop %s: %v" , name , err )
log . Printf ( "[ERROR] stop %s: %v" , name , err )
}
}
}
}
// clean up all old modules
// clean up all modules
oldCfg . cancelFunc ( )
cfg . cancelFunc ( )
}
// Validate loads, provisions, and validates
// cfg, but does not start running it.
func Validate ( cfg * Config ) error {
return run ( cfg , false )
}
}
// Duration is a JSON-string-unmarshable duration type.
// Duration is a JSON-string-unmarshable duration type.