Fixed a possible leak bug in goroutine #13108
Closed
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Problem:
The goroutine invoked by the ApplicationTermination method (go func() { ... }) has no termination mechanism.
The Close method calls s.ticker.Stop(), which only stops the ticker, but does not terminate the for loop in the goroutine.
The goroutine leaks because it is permanently waiting to receive from the channel in <-s.ticker.C.
This goroutine will remain in the program even after the command is terminated.
Fix:
Use context to manage the goroutine lifecycle.
By adding context.CancelFunc to the Stopping structure and notifying the goroutine of the cancellation with the Close method, the goroutine can be safely terminated.
Another small change is that within the ApplicationTermination method, the value 100 * time.Millisecond is written directly to time.NewTicker. This “magic number” could be confusing to the intent of the code and has been corrected.
package formatter import ( +++ "context" "fmt" "strings" "time" "github.com/docker/compose/v2/pkg/progress" ) +++ const tickerInterval = 100 * time.Millisecond type Stopping struct { api.LogConsumer enabled bool spinner *progress.Spinner ticker *time.Ticker startedAt time.Time +++ cancel context.CancelFunc } func NewStopping(l api.LogConsumer) *Stopping { s.spinner = progress.NewSpinner() hideCursor() s.startedAt = time.Now() --- s.ticker = time.NewTicker(100 * time.Millisecond) +++ s.ticker = time.NewTicker(tickerInterval) ctx, cancel := context.WithCancel(context.Background()) s.cancel = cancel go func() { for { --- <-s.ticker.C --- s.print() +++ select { +++ case <-s.ticker.C: +++ s.print() +++ case <-ctx.Done(): +++ return +++ } } }() } func (s *Stopping) Close() { showCursor() --- if s.ticker != nil { --- s.ticker.Stop() --- } +++ s.ticker.Stop() +++ s.cancel() s.clear() }Benefits:
Applying this goroutine leak fix will greatly improve the stability of the program.