mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-26 13:43:47 +03:00
Separate currentCtxMu to protect currentCtx, and a new rawCfgMu to protect rawCfg and synchronize loads.
This commit is contained in:
parent
f857b32d65
commit
b51dc5d5d0
3 changed files with 42 additions and 20 deletions
4
admin.go
4
admin.go
|
@ -1016,9 +1016,9 @@ func handleConfigID(w http.ResponseWriter, r *http.Request) error {
|
||||||
id := parts[2]
|
id := parts[2]
|
||||||
|
|
||||||
// map the ID to the expanded path
|
// map the ID to the expanded path
|
||||||
currentCtxMu.RLock()
|
rawCfgMu.RLock()
|
||||||
expanded, ok := rawCfgIndex[id]
|
expanded, ok := rawCfgIndex[id]
|
||||||
currentCtxMu.RUnlock()
|
rawCfgMu.RUnlock()
|
||||||
if !ok {
|
if !ok {
|
||||||
return APIError{
|
return APIError{
|
||||||
HTTPStatus: http.StatusNotFound,
|
HTTPStatus: http.StatusNotFound,
|
||||||
|
|
52
caddy.go
52
caddy.go
|
@ -156,8 +156,8 @@ func changeConfig(method, path string, input []byte, ifMatchHeader string, force
|
||||||
return fmt.Errorf("method not allowed")
|
return fmt.Errorf("method not allowed")
|
||||||
}
|
}
|
||||||
|
|
||||||
currentCtxMu.Lock()
|
rawCfgMu.Lock()
|
||||||
defer currentCtxMu.Unlock()
|
defer rawCfgMu.Unlock()
|
||||||
|
|
||||||
if ifMatchHeader != "" {
|
if ifMatchHeader != "" {
|
||||||
// expect the first and last character to be quotes
|
// expect the first and last character to be quotes
|
||||||
|
@ -257,8 +257,8 @@ func changeConfig(method, path string, input []byte, ifMatchHeader string, force
|
||||||
// readConfig traverses the current config to path
|
// readConfig traverses the current config to path
|
||||||
// and writes its JSON encoding to out.
|
// and writes its JSON encoding to out.
|
||||||
func readConfig(path string, out io.Writer) error {
|
func readConfig(path string, out io.Writer) error {
|
||||||
currentCtxMu.RLock()
|
rawCfgMu.RLock()
|
||||||
defer currentCtxMu.RUnlock()
|
defer rawCfgMu.RUnlock()
|
||||||
return unsyncedConfigAccess(http.MethodGet, path, nil, out)
|
return unsyncedConfigAccess(http.MethodGet, path, nil, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -305,7 +305,7 @@ func indexConfigObjects(ptr any, configPath string, index map[string]string) err
|
||||||
// it as the new config, replacing any other current config.
|
// it as the new config, replacing any other current config.
|
||||||
// It does NOT update the raw config state, as this is a
|
// It does NOT update the raw config state, as this is a
|
||||||
// lower-level function; most callers will want to use Load
|
// lower-level function; most callers will want to use Load
|
||||||
// instead. A write lock on currentCtxMu is required! If
|
// instead. A write lock on rawCfgMu is required! If
|
||||||
// allowPersist is false, it will not be persisted to disk,
|
// allowPersist is false, it will not be persisted to disk,
|
||||||
// even if it is configured to.
|
// even if it is configured to.
|
||||||
func unsyncedDecodeAndRun(cfgJSON []byte, allowPersist bool) error {
|
func unsyncedDecodeAndRun(cfgJSON []byte, allowPersist bool) error {
|
||||||
|
@ -340,8 +340,10 @@ func unsyncedDecodeAndRun(cfgJSON []byte, allowPersist bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// swap old context (including its config) with the new one
|
// swap old context (including its config) with the new one
|
||||||
|
currentCtxMu.Lock()
|
||||||
oldCtx := currentCtx
|
oldCtx := currentCtx
|
||||||
currentCtx = ctx
|
currentCtx = ctx
|
||||||
|
currentCtxMu.Unlock()
|
||||||
|
|
||||||
// Stop, Cleanup each old app
|
// Stop, Cleanup each old app
|
||||||
unsyncedStop(oldCtx)
|
unsyncedStop(oldCtx)
|
||||||
|
@ -627,22 +629,35 @@ type ConfigLoader interface {
|
||||||
// stop the others. Stop should only be called
|
// stop the others. Stop should only be called
|
||||||
// if not replacing with a new config.
|
// if not replacing with a new config.
|
||||||
func Stop() error {
|
func Stop() error {
|
||||||
|
currentCtxMu.RLock()
|
||||||
|
ctx := currentCtx
|
||||||
|
currentCtxMu.RUnlock()
|
||||||
|
|
||||||
|
rawCfgMu.Lock()
|
||||||
|
unsyncedStop(ctx)
|
||||||
|
|
||||||
currentCtxMu.Lock()
|
currentCtxMu.Lock()
|
||||||
defer currentCtxMu.Unlock()
|
|
||||||
unsyncedStop(currentCtx)
|
|
||||||
currentCtx = Context{}
|
currentCtx = Context{}
|
||||||
|
currentCtxMu.Unlock()
|
||||||
|
|
||||||
rawCfgJSON = nil
|
rawCfgJSON = nil
|
||||||
rawCfgIndex = nil
|
rawCfgIndex = nil
|
||||||
rawCfg[rawConfigKey] = nil
|
rawCfg[rawConfigKey] = nil
|
||||||
|
rawCfgMu.Unlock()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// unsyncedStop stops cfg from running, but has
|
// unsyncedStop stops ctx from running, but has
|
||||||
// no locking around cfg. It is a no-op if cfg is
|
// no locking around ctx. It is a no-op if ctx has a
|
||||||
// nil. If any app returns an error when stopping,
|
// nil cfg. If any app returns an error when stopping,
|
||||||
// it is logged and the function continues stopping
|
// it is logged and the function continues stopping
|
||||||
// the next app. This function assumes all apps in
|
// the next app. This function assumes all apps in
|
||||||
// cfg were successfully started first.
|
// ctx were successfully started first.
|
||||||
|
//
|
||||||
|
// A lock on rawCfgMu is required, even though this
|
||||||
|
// function does not access rawCfg, that lock
|
||||||
|
// synchronizes the stop/start of apps.
|
||||||
func unsyncedStop(ctx Context) {
|
func unsyncedStop(ctx Context) {
|
||||||
if ctx.cfg == nil {
|
if ctx.cfg == nil {
|
||||||
return
|
return
|
||||||
|
@ -959,9 +974,8 @@ func Version() (simple, full string) {
|
||||||
// This function is experimental and might be changed
|
// This function is experimental and might be changed
|
||||||
// or removed in the future.
|
// or removed in the future.
|
||||||
func ActiveContext() Context {
|
func ActiveContext() Context {
|
||||||
// TODO: This locking might still be needed; more investigation is required (deadlock during Cleanup for the caddytls.TLS module).
|
currentCtxMu.RLock()
|
||||||
// currentCtxMu.RLock()
|
defer currentCtxMu.RUnlock()
|
||||||
// defer currentCtxMu.RUnlock()
|
|
||||||
return currentCtx
|
return currentCtx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -970,14 +984,12 @@ type CtxKey string
|
||||||
|
|
||||||
// This group of variables pertains to the current configuration.
|
// This group of variables pertains to the current configuration.
|
||||||
var (
|
var (
|
||||||
// currentCtxMu protects everything in this var block.
|
|
||||||
currentCtxMu sync.RWMutex
|
|
||||||
|
|
||||||
// currentCtx is the root context for the currently-running
|
// currentCtx is the root context for the currently-running
|
||||||
// configuration, which can be accessed through this value.
|
// configuration, which can be accessed through this value.
|
||||||
// If the Config contained in this value is not nil, then
|
// If the Config contained in this value is not nil, then
|
||||||
// a config is currently active/running.
|
// a config is currently active/running.
|
||||||
currentCtx Context
|
currentCtx Context
|
||||||
|
currentCtxMu sync.RWMutex
|
||||||
|
|
||||||
// rawCfg is the current, generic-decoded configuration;
|
// rawCfg is the current, generic-decoded configuration;
|
||||||
// we initialize it as a map with one field ("config")
|
// we initialize it as a map with one field ("config")
|
||||||
|
@ -995,6 +1007,10 @@ var (
|
||||||
// rawCfgIndex is the map of user-assigned ID to expanded
|
// rawCfgIndex is the map of user-assigned ID to expanded
|
||||||
// path, for converting /id/ paths to /config/ paths.
|
// path, for converting /id/ paths to /config/ paths.
|
||||||
rawCfgIndex map[string]string
|
rawCfgIndex map[string]string
|
||||||
|
|
||||||
|
// rawCfgMu protects all the rawCfg fields and also
|
||||||
|
// essentially synchronizes config changes/reloads.
|
||||||
|
rawCfgMu sync.RWMutex
|
||||||
)
|
)
|
||||||
|
|
||||||
// errSameConfig is returned if the new config is the same
|
// errSameConfig is returned if the new config is the same
|
||||||
|
|
|
@ -441,6 +441,12 @@ func (ctx Context) App(name string) (any, error) {
|
||||||
// or stop App modules. The caller is expected to assert to the
|
// or stop App modules. The caller is expected to assert to the
|
||||||
// concrete type.
|
// concrete type.
|
||||||
func (ctx Context) AppIfConfigured(name string) any {
|
func (ctx Context) AppIfConfigured(name string) any {
|
||||||
|
if ctx.cfg == nil {
|
||||||
|
// this can happen if the currently-active context
|
||||||
|
// is being accessed, but no config has successfully
|
||||||
|
// been loaded yet
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return ctx.cfg.apps[name]
|
return ctx.cfg.apps[name]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue