@marcel91200 / caddy Mirror · read-only / commits / a72acd21
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 13, 21:45 UTC

core: Retry dynamic config load if config unchanged

(see discussion in #4603)

Matthew Holt committed Mar 3, 2022 at 21:41 UTC a72acd21b0ef17d6cd9064dd105042b6edb3b8dc
2 files changed +33 -14
admin.go
+1 -1
@@ -938,7 +938,7 @@ func handleConfig(w http.ResponseWriter, r *http.Request) error {
938 forceReload := r.Header.Get("Cache-Control") == "must-revalidate"
939
940 err := changeConfig(r.Method, r.URL.Path, body, forceReload)
941 - if err != nil {
941 + if err != nil && !errors.Is(err, errSameConfig) {
942 return err
943 }
944
caddy.go
+32 -13
@@ -18,6 +18,7 @@ import (
18 "bytes"
19 "context"
20 "encoding/json"
21 + "errors"
22 "fmt"
23 "io"
24 "log"
@@ -110,14 +111,19 @@ func Load(cfgJSON []byte, forceReload bool) error {
111 }
112 }()
113
113 - return changeConfig(http.MethodPost, "/"+rawConfigKey, cfgJSON, forceReload)
114 + err := changeConfig(http.MethodPost, "/"+rawConfigKey, cfgJSON, forceReload)
115 + if errors.Is(err, errSameConfig) {
116 + err = nil // not really an error
117 + }
118 + return err
119 }
120
121 // changeConfig changes the current config (rawCfg) according to the
122 // method, traversed via the given path, and uses the given input as
123 // the new value (if applicable; i.e. "DELETE" doesn't have an input).
124 // If the resulting config is the same as the previous, no reload will
120 -// occur unless forceReload is true. This function is safe for
125 +// occur unless forceReload is true. If the config is unchanged and not
126 +// forcefully reloaded, then errConfigUnchanged This function is safe for
127 // concurrent use.
128 func changeConfig(method, path string, input []byte, forceReload bool) error {
129 switch method {
@@ -149,7 +155,7 @@ func changeConfig(method, path string, input []byte, forceReload bool) error {
155 // if nothing changed, no need to do a whole reload unless the client forces it
156 if !forceReload && bytes.Equal(rawCfgJSON, newCfg) {
157 Log().Info("config is unchanged")
152 - return nil
158 + return errSameConfig
159 }
160
161 // find any IDs in this config and index them
@@ -492,39 +498,47 @@ func finishSettingUp(ctx Context, cfg *Config) error {
498 zap.String("module", val.(Module).CaddyModule().ID.Name()),
499 zap.Int("load_delay", int(cfg.Admin.Config.LoadDelay)))
500
495 - runLoadedConfig := func(config []byte) {
501 + runLoadedConfig := func(config []byte) error {
502 logger.Info("applying dynamically-loaded config")
503 err := changeConfig(http.MethodPost, "/"+rawConfigKey, config, false)
498 - if err == nil {
499 - logger.Info("successfully applied dynamically-loaded config")
500 - } else {
504 + if errors.Is(err, errSameConfig) {
505 + return err
506 + }
507 + if err != nil {
508 logger.Error("failed to run dynamically-loaded config", zap.Error(err))
509 + return err
510 }
511 + logger.Info("successfully applied dynamically-loaded config")
512 + return nil
513 }
514
515 if cfg.Admin.Config.LoadDelay > 0 {
516 go func() {
507 - // the loop is here only to iterate if there is an error or a no-op config load,
508 - // in which case we simply wait the delay and try again
517 + // the loop is here to iterate ONLY if there is an error, a no-op config load,
518 + // or an unchanged config; in which case we simply wait the delay and try again
519 for {
520 timer := time.NewTimer(time.Duration(cfg.Admin.Config.LoadDelay))
521 select {
522 case <-timer.C:
523 loadedConfig, err := val.(ConfigLoader).LoadConfig(ctx)
524 if err != nil {
515 - Log().Error("failed loading dynamic config; will retry", zap.Error(err))
525 + logger.Error("failed loading dynamic config; will retry", zap.Error(err))
526 continue
527 }
528 if loadedConfig == nil {
519 - Log().Info("dynamically-loaded config was nil; will retry")
529 + logger.Info("dynamically-loaded config was nil; will retry")
530 + continue
531 + }
532 + err = runLoadedConfig(loadedConfig)
533 + if errors.Is(err, errSameConfig) {
534 + logger.Info("dynamically-loaded config was unchanged; will retry")
535 continue
536 }
522 - runLoadedConfig(loadedConfig)
537 case <-ctx.Done():
538 if !timer.Stop() {
539 <-timer.C
540 }
527 - Log().Info("stopping dynamic config loading")
541 + logger.Info("stopping dynamic config loading")
542 }
543 break
544 }
@@ -798,5 +812,10 @@ var (
812 rawCfgIndex map[string]string
813 )
814
815 +// errSameConfig is returned if the new config is the same
816 +// as the old one. This isn't usually an actual, actionable
817 +// error; it's mostly a sentinel value.
818 +var errSameConfig = errors.New("config is unchanged")
819 +
820 // ImportPath is the package import path for Caddy core.
821 const ImportPath = "github.com/caddyserver/caddy/v2"