Skip to content

[Bug] Race condition on spinner.Suffix field in UpdateSpinnerMessage #4744

@e-gineer

Description

@e-gineer

Severity: HIGH

Bug Summary

The spinner.Suffix field is modified concurrently without synchronization when UpdateSpinnerMessage() is called from multiple goroutines, causing data races.

Location

  • File: pkg/statushooks/spinner.go
  • Function: UpdateSpinnerMessage() (line 111)
  • Line: 113 - s.spinner.Suffix = fmt.Sprintf(" %s", newMessage)

Reproduction

func TestSpinnerConcurrentUpdate(t *testing.T) {
    spinner := NewStatusSpinnerHook()
    spinner.Show()
    defer spinner.Hide()

    var wg sync.WaitGroup
    for i := 0; i < 100; i++ {
        wg.Add(1)
        go func(n int) {
            defer wg.Done()
            spinner.UpdateSpinnerMessage(fmt.Sprintf("msg-%d", n))
        }(i)
    }
    wg.Wait()
}

Run with: go test -race ./pkg/statushooks/...

Evidence

Race detector shows:

  • Write at spinner.Suffix in UpdateSpinnerMessage (line 113)
  • Concurrent reads/writes from multiple goroutines
  • Also races with Show() which reads spinner.Suffix (line 104)

Impact

  • When: Multiple goroutines update status messages concurrently (common in parallel operations)
  • Effect:
    • Corrupted spinner messages
    • Potential string corruption leading to garbled output
    • Data race failures in tests
    • Undefined behavior

Proposed Fix

Add mutex protection for spinner.Suffix modifications:

type StatusSpinner struct {
    spinner *spinner.Spinner
    cancel  chan struct{}
    delay   time.Duration
    visible bool
    mu      sync.RWMutex  // Add mutex
}

func (s *StatusSpinner) UpdateSpinnerMessage(newMessage string) {
    newMessage = s.truncateSpinnerMessageToScreen(newMessage)
    
    s.mu.Lock()
    s.spinner.Suffix = fmt.Sprintf(" %s", newMessage)
    visible := s.visible
    active := s.spinner.Active()
    s.mu.Unlock()
    
    if visible && !active {
        s.spinner.Start()
    }
}

Found by: Wave 3 Task 1 (Statushooks)
Test: TestSpinnerConcurrentUpdate in pkg/statushooks/statushooks_test.go

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions