Skip to content

Commit cab584f

Browse files
authored
fix: docker container log consumer race (#3210)
Fix a data race in the docker container log consumer that occurs when the consumers slice is modified while it is being iterated over.
1 parent 147446a commit cab584f

File tree

2 files changed

+27
-6
lines changed

2 files changed

+27
-6
lines changed

docker.go

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"os"
1616
"path/filepath"
1717
"regexp"
18+
"slices"
1819
"sync"
1920
"time"
2021

@@ -79,6 +80,7 @@ type DockerContainer struct {
7980
provider *DockerProvider
8081
sessionID string
8182
terminationSignal chan bool
83+
consumersMtx sync.Mutex // protects consumers
8284
consumers []LogConsumer
8385

8486
// TODO: Remove locking and wait group once the deprecated StartLogProducer and
@@ -424,9 +426,29 @@ func (c *DockerContainer) FollowOutput(consumer LogConsumer) {
424426
// followOutput adds a LogConsumer to be sent logs from the container's
425427
// STDOUT and STDERR
426428
func (c *DockerContainer) followOutput(consumer LogConsumer) {
429+
c.consumersMtx.Lock()
430+
defer c.consumersMtx.Unlock()
431+
427432
c.consumers = append(c.consumers, consumer)
428433
}
429434

435+
// consumersCopy returns a copy of the current consumers.
436+
func (c *DockerContainer) consumersCopy() []LogConsumer {
437+
c.consumersMtx.Lock()
438+
defer c.consumersMtx.Unlock()
439+
440+
return slices.Clone(c.consumers)
441+
}
442+
443+
// resetConsumers resets the current consumers to the provided ones.
444+
func (c *DockerContainer) resetConsumers(consumers []LogConsumer) {
445+
c.consumersMtx.Lock()
446+
defer c.consumersMtx.Unlock()
447+
448+
c.consumers = c.consumers[:0]
449+
c.consumers = append(c.consumers, consumers...)
450+
}
451+
430452
// Deprecated: use c.Inspect(ctx).Name instead.
431453
// Name gets the name of the container.
432454
func (c *DockerContainer) Name(ctx context.Context) (string, error) {
@@ -761,8 +783,10 @@ func (c *DockerContainer) startLogProduction(ctx context.Context, opts ...LogPro
761783
}
762784

763785
// Setup the log writers.
764-
stdout := newLogConsumerWriter(StdoutLog, c.consumers)
765-
stderr := newLogConsumerWriter(StderrLog, c.consumers)
786+
787+
consumers := c.consumersCopy()
788+
stdout := newLogConsumerWriter(StdoutLog, consumers)
789+
stderr := newLogConsumerWriter(StderrLog, consumers)
766790

767791
// Setup the log production context which will be used to stop the log production.
768792
c.logProductionCtx, c.logProductionCancel = context.WithCancelCause(ctx)

lifecycle.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -189,10 +189,7 @@ var defaultLogConsumersHook = func(cfg *LogConsumerConfig) ContainerLifecycleHoo
189189
}
190190

191191
dockerContainer := c.(*DockerContainer)
192-
dockerContainer.consumers = dockerContainer.consumers[:0]
193-
for _, consumer := range cfg.Consumers {
194-
dockerContainer.followOutput(consumer)
195-
}
192+
dockerContainer.resetConsumers(cfg.Consumers)
196193

197194
return dockerContainer.startLogProduction(ctx, cfg.Opts...)
198195
},

0 commit comments

Comments
 (0)