Skip to content

Commit 0814e1c

Browse files
committed
fix: simplify logger race fix to a mutex-guarded Loader.Logger() accessor
Per review feedback (github.com//pull/9581#issuecomment-5077942710): drop the loggerUpdates channel and drain logic in favor of a simple RLock-guarded Logger() getter on Loader, reusing the existing cfgMu rather than introducing new synchronization. Signed-off-by: Muhammad Waqar <mwaqar@confluent.io>
1 parent ef9f22e commit 0814e1c

3 files changed

Lines changed: 19 additions & 37 deletions

File tree

internal/cmd/server.go

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,6 @@ func server(ctx context.Context, stdout, stderr io.Writer, cfgPath string, hook
8888
return err
8989
}
9090

91-
// cfg.Logger is mutated concurrently on reload, so keep our own reference
92-
// and update it only via l.LoggerUpdates() below, never read cfg.Logger again.
93-
logger := cfg.Logger
94-
9591
l := loader.New(cfgPath, cfg, hook)
9692
if err := l.Start(ctx, stdout); err != nil {
9793
return err
@@ -103,20 +99,20 @@ func server(ctx context.Context, stdout, stderr io.Writer, cfgPath string, hook
10399

104100
for {
105101
select {
106-
// Pick up the latest logger whenever a config reload replaces it.
107-
case newLogger := <-l.LoggerUpdates():
108-
logger = newLogger
109102
// Exit if the config loader fails to start the runners.
110103
// Continuing with failed runners would cause EG to function incorrectly.
111104
case err := <-l.Errors():
112-
logger.Error(err, "failed to start runners")
105+
// Read the logger through the loader so the read is guarded by the
106+
// same mutex the reload path holds while replacing it. Reading
107+
// cfg.Logger directly here races with that swap.
108+
l.Logger().Error(err, "failed to start runners")
113109
// Wait for runners to finish before shutting down.
114110
// This is to make sure no orphaned runner process is left running in standalone mode.
115111
l.Wait()
116112
return err
117113
// Wait for the context to be done, which usually happens the process receives a SIGTERM or SIGINT.
118114
case <-ctx.Done():
119-
logger.Info("shutting down")
115+
l.Logger().Info("shutting down")
120116
// Wait for runners to finish before shutting down.
121117
// This is to make sure no orphaned runner process is left running in standalone mode.
122118
l.Wait()

internal/envoygateway/config/loader/configloader.go

Lines changed: 13 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -29,21 +29,17 @@ type Loader struct {
2929
hook HookFunc
3030
hookErr chan error
3131

32-
// loggerUpdates publishes the logger on each config reload; see LoggerUpdates.
33-
loggerUpdates chan logging.Logger
34-
3532
w filewatcher.FileWatcher
3633
}
3734

3835
func New(cfgPath string, cfg *config.Server, f HookFunc) *Loader {
3936
return &Loader{
40-
cfgPath: cfgPath,
41-
cfg: cfg,
42-
logger: cfg.Logger.WithName("config-loader"),
43-
hook: f,
44-
hookErr: make(chan error, 1),
45-
loggerUpdates: make(chan logging.Logger, 1),
46-
w: filewatcher.NewWatcher(),
37+
cfgPath: cfgPath,
38+
cfg: cfg,
39+
logger: cfg.Logger.WithName("config-loader"),
40+
hook: f,
41+
hookErr: make(chan error, 1),
42+
w: filewatcher.NewWatcher(),
4743
}
4844
}
4945

@@ -89,13 +85,11 @@ func (r *Loader) Start(ctx context.Context, logOut io.Writer) error {
8985
continue
9086
}
9187

92-
newLogger := logging.NewLogger(logOut, eg.Logging)
9388
r.cfgMu.Lock()
9489
r.cfg.EnvoyGateway = eg
9590
// update cfg logger
96-
r.cfg.Logger = newLogger
91+
r.cfg.Logger = logging.NewLogger(logOut, eg.Logging)
9792
r.cfgMu.Unlock()
98-
r.publishLogger(newLogger)
9993

10094
// cancel last
10195
if r.cancel != nil {
@@ -148,20 +142,12 @@ func (r *Loader) Errors() <-chan error {
148142
return r.hookErr
149143
}
150144

151-
// LoggerUpdates returns a channel that receives the latest logger on each config reload.
152-
func (r *Loader) LoggerUpdates() <-chan logging.Logger {
153-
return r.loggerUpdates
154-
}
155-
156-
func (r *Loader) publishLogger(l logging.Logger) {
157-
select {
158-
case <-r.loggerUpdates: // drain a stale, unconsumed value, if any
159-
default:
160-
}
161-
select {
162-
case r.loggerUpdates <- l:
163-
default:
164-
}
145+
// Logger returns the current logger, safe to call concurrently with a config
146+
// reload replacing it.
147+
func (r *Loader) Logger() logging.Logger {
148+
r.cfgMu.RLock()
149+
defer r.cfgMu.RUnlock()
150+
return r.cfg.Logger
165151
}
166152

167153
// Wait returns when success to acquire mutex, which means no hook is running.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Fixed a data race between config-reload and the standalone server's shutdown/error logging by having the config loader publish logger updates over a channel instead of a shared, unguarded field.
1+
Fixed a data race between config-reload and the standalone server's shutdown/error logging by reading the logger through a new mutex-guarded `Loader.Logger()` accessor instead of the shared `cfg.Logger` field directly.

0 commit comments

Comments
 (0)