@ -10,13 +10,34 @@ import (
"time"
)
var currentCfg * Config
var currentCfgMu sync . Mutex
// Start runs Caddy with the given config.
func Start ( cfg Config ) error {
// allow only one call to Start at a time,
// since various calls to LoadModule()
// access shared map moduleInstances
startMu . Lock ( )
defer startMu . Unlock ( )
// prepare the config for use
cfg . runners = make ( map [ string ] Runner )
cfg . moduleStates = make ( map [ string ] interface { } )
// reset the shared moduleInstances map; but
// keep a temporary reference to the current
// one so we can transfer over any necessary
// state to the new modules; or in case this
// function returns an error, we need to put
// the "old" one back where we found it
var err error
oldModuleInstances := moduleInstances
defer func ( ) {
if err != nil {
moduleInstances = oldModuleInstances
}
} ( )
moduleInstances = make ( map [ string ] [ ] interface { } )
// load (decode) each runner module
for modName , rawMsg := range cfg . Modules {
val , err := LoadModule ( modName , rawMsg )
if err != nil {
@ -34,19 +55,60 @@ func Start(cfg Config) error {
}
}
// shut down down the old one s
// shut down down the old runner s
currentCfgMu . Lock ( )
if currentCfg != nil {
for _ , r := range currentCfg . runners {
for name , r := range currentCfg . runners {
err := r . Cancel ( )
if err != nil {
log . Println ( err )
log . Printf ( "[ERROR] cancel %s: %v" , name , err )
}
}
}
oldCfg := currentCfg
currentCfg = & cfg
currentCfgMu . Unlock ( )
// invoke unload callbacks on old configuration
for modName := range oldModuleInstances {
mod , err := GetModule ( modName )
if err != nil {
return err
}
if mod . OnUnload != nil {
var unloadingState interface { }
if oldCfg != nil {
unloadingState = oldCfg . moduleStates [ modName ]
}
err := mod . OnUnload ( unloadingState )
if err != nil {
log . Printf ( "[ERROR] module OnUnload: %s: %v" , modName , err )
continue
}
}
}
// invoke load callbacks on new configuration
for modName , instances := range moduleInstances {
mod , err := GetModule ( modName )
if err != nil {
return err
}
if mod . OnLoad != nil {
var priorState interface { }
if oldCfg != nil {
priorState = oldCfg . moduleStates [ modName ]
}
modState , err := mod . OnLoad ( instances , priorState )
if err != nil {
return fmt . Errorf ( "module OnLoad: %s: %v" , modName , err )
}
if modState != nil {
cfg . moduleStates [ modName ] = modState
}
}
}
// shut down listeners that are no longer being used
listenersMu . Lock ( )
for key , info := range listeners {
@ -74,7 +136,15 @@ type Runner interface {
type Config struct {
TestVal string ` json:"testval" `
Modules map [ string ] json . RawMessage ` json:"modules" `
// runners stores the decoded Modules values,
// keyed by module name.
runners map [ string ] Runner
// moduleStates stores the optional "global" state
// values of every module used by this configuration,
// keyed by module name.
moduleStates map [ string ] interface { }
}
// Duration is a JSON-string-unmarshable duration type.
@ -95,3 +165,23 @@ func (d *Duration) UnmarshalJSON(b []byte) error {
func ( d Duration ) MarshalJSON ( ) ( [ ] byte , error ) {
return [ ] byte ( fmt . Sprintf ( ` "%s" ` , time . Duration ( d ) . String ( ) ) ) , nil
}
// currentCfg is the currently-loaded configuration.
var (
currentCfg * Config
currentCfgMu sync . Mutex
)
// moduleInstances stores the individual instantiated
// values of modules, keyed by module name. The list
// of instances of each module get passed into the
// respective module's OnLoad callback, so they can
// set up any global state and/or make sure their
// configuration, when viewed as a whole, is valid.
// Since this list is shared, only one Start() routine
// must be allowed to happen at any given time.
var moduleInstances = make ( map [ string ] [ ] interface { } )
// startMu ensures that only one Start() happens at a time.
// This is important since
var startMu sync . Mutex