fix(queue): Prevent massive goroutine leak from StripedProgress animations
Critical Fix: - Goroutine dump showed hundreds of leaked animation goroutines - Each queue refresh created NEW progress bars without stopping old ones - Animation goroutines continued running forever, consuming resources Root Cause: - BuildQueueView() creates new StripedProgress widgets on every refresh - StartAnimation() spawned goroutines for running jobs - Old widgets were discarded but goroutines never stopped - Fyne's Destroy() method not reliably called when rebuilding view Solution: - Track all active StripedProgress widgets in appState.queueActiveProgress - Stop ALL animations before rebuilding queue view - Stop ALL animations when leaving queue view (stopQueueAutoRefresh) - BuildQueueView now returns list of active progress bars - Prevents hundreds of leaked goroutines from accumulating Implementation: - Added queueActiveProgress []*ui.StripedProgress to appState - Modified BuildQueueView signature to return progress list - Stop old animations in refreshQueueView() before calling BuildQueueView - Stop all animations in stopQueueAutoRefresh() when navigating away - Track running job progress bars and append to activeProgress slice Files Changed: - main.go: appState field, refreshQueueView(), stopQueueAutoRefresh() - internal/ui/queueview.go: BuildQueueView(), buildJobItem() Impact: - Eliminates goroutine leak that caused resource exhaustion - Clean shutdown of animation goroutines on refresh and navigation - Should dramatically reduce memory usage and CPU overhead Reported-by: User (goroutine dump showing 900+ leaked goroutines) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
8cff33fcab
commit
da49a1dd7b
|
|
@ -212,7 +212,9 @@ func BuildQueueView(
|
|||
onViewLog func(string),
|
||||
onCopyCommand func(string),
|
||||
titleColor, bgColor, textColor color.Color,
|
||||
) (fyne.CanvasObject, *container.Scroll) {
|
||||
) (fyne.CanvasObject, *container.Scroll, []*StripedProgress) {
|
||||
// Track active progress animations to prevent goroutine leaks
|
||||
var activeProgress []*StripedProgress
|
||||
// Header
|
||||
title := canvas.NewText("JOB QUEUE", titleColor)
|
||||
title.TextStyle = fyne.TextStyle{Monospace: true, Bold: true}
|
||||
|
|
@ -264,7 +266,7 @@ func BuildQueueView(
|
|||
}
|
||||
|
||||
for _, job := range jobs {
|
||||
jobItems = append(jobItems, buildJobItem(job, queuePositions, onPause, onResume, onCancel, onRemove, onMoveUp, onMoveDown, onCopyError, onViewLog, onCopyCommand, bgColor, textColor))
|
||||
jobItems = append(jobItems, buildJobItem(job, queuePositions, onPause, onResume, onCancel, onRemove, onMoveUp, onMoveDown, onCopyError, onViewLog, onCopyCommand, bgColor, textColor, &activeProgress))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -280,7 +282,7 @@ func BuildQueueView(
|
|||
scrollable,
|
||||
)
|
||||
|
||||
return container.NewPadded(body), scrollable
|
||||
return container.NewPadded(body), scrollable, activeProgress
|
||||
}
|
||||
|
||||
// buildJobItem creates a single job item in the queue list
|
||||
|
|
@ -297,6 +299,7 @@ func buildJobItem(
|
|||
onViewLog func(string),
|
||||
onCopyCommand func(string),
|
||||
bgColor, textColor color.Color,
|
||||
activeProgress *[]*StripedProgress,
|
||||
) fyne.CanvasObject {
|
||||
// Status color
|
||||
statusColor := GetStatusColor(job.Status)
|
||||
|
|
@ -325,6 +328,8 @@ func buildJobItem(
|
|||
if job.Status == queue.JobStatusRunning {
|
||||
progress.SetActivity(job.Progress <= 0.01)
|
||||
progress.StartAnimation()
|
||||
// Track active progress to stop animation on next refresh (prevents goroutine leaks)
|
||||
*activeProgress = append(*activeProgress, progress)
|
||||
} else {
|
||||
progress.SetActivity(false)
|
||||
progress.StopAnimation()
|
||||
|
|
|
|||
23
main.go
23
main.go
|
|
@ -986,6 +986,7 @@ type appState struct {
|
|||
|
||||
queueAutoRefreshStop chan struct{}
|
||||
queueAutoRefreshRunning bool
|
||||
queueActiveProgress []*ui.StripedProgress // Track active progress animations to prevent goroutine leaks
|
||||
|
||||
// Main menu refresh throttling
|
||||
mainMenuLastRefresh time.Time
|
||||
|
|
@ -1781,7 +1782,16 @@ func (s *appState) refreshQueueView() {
|
|||
}}, jobs...)
|
||||
}
|
||||
|
||||
view, scroll := ui.BuildQueueView(
|
||||
// CRITICAL: Stop all active progress animations before rebuilding to prevent goroutine leaks
|
||||
// Each refresh creates new StripedProgress widgets, and old animation goroutines must be stopped
|
||||
for _, progress := range s.queueActiveProgress {
|
||||
if progress != nil {
|
||||
progress.StopAnimation()
|
||||
}
|
||||
}
|
||||
s.queueActiveProgress = nil
|
||||
|
||||
view, scroll, activeProgress := ui.BuildQueueView(
|
||||
jobs,
|
||||
func() { // onBack
|
||||
// Stop auto-refresh before navigating away for snappy response
|
||||
|
|
@ -1938,6 +1948,9 @@ func (s *appState) refreshQueueView() {
|
|||
}()
|
||||
}
|
||||
|
||||
// Store active progress bars to stop them on next refresh
|
||||
s.queueActiveProgress = activeProgress
|
||||
|
||||
s.setContent(container.NewPadded(view))
|
||||
}
|
||||
|
||||
|
|
@ -1984,6 +1997,14 @@ func (s *appState) stopQueueAutoRefresh() {
|
|||
}
|
||||
s.queueAutoRefreshStop = nil
|
||||
s.queueAutoRefreshRunning = false
|
||||
|
||||
// Stop all active progress animations to prevent goroutine leaks when leaving queue view
|
||||
for _, progress := range s.queueActiveProgress {
|
||||
if progress != nil {
|
||||
progress.StopAnimation()
|
||||
}
|
||||
}
|
||||
s.queueActiveProgress = nil
|
||||
}
|
||||
|
||||
// addConvertToQueue adds a conversion job to the queue
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user