Skip to content

progress - Is it racy or am i using it wrong? #1438

@fasmide

Description

@fasmide

Describe the bug
Having multiple go routines sending events to a tea app, using tea.Program.Send() - sometimes triggers the race detector.

Setup
Fairly standard ubuntu install

  • Ubuntu
  • foot

To Reproduce

Based on progress-animated, modified with two progress bars, and two go routines that simultaneously updates state

package main

import (
	"fmt"
	"os"
	"time"

	"github.com/charmbracelet/bubbles/progress"
	tea "github.com/charmbracelet/bubbletea"
)

func main() {
	prog := progress.New(progress.WithDefaultGradient())
	prog2 := progress.New(progress.WithScaledGradient("#000000", "#00ff00"))

	m := &model{
		progress:  &prog,
		progress2: &prog2,
	}

	p := tea.NewProgram(m)

	go func() {
		i := 0
		for {
			i++
			p.Send(tickMsg{})
			time.Sleep(time.Second)

		}
	}()

	go func() {
		i := 0
		for {
			i++
			p.Send(tickMsg{})
			time.Sleep(time.Second)

		}
	}()

	if _, err := p.Run(); err != nil {
		fmt.Println("Oh no!", err)
		os.Exit(1)
	}
}

type tickMsg struct{}

type model struct {
	progress  *progress.Model
	progress2 *progress.Model
}

func (m *model) Init() tea.Cmd {
	return nil
}

func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {

	switch msg := msg.(type) {
	case tea.KeyMsg:
		return m, tea.Quit

	case tickMsg:
		if m.progress.Percent() == 1.0 {
			return m, tea.Quit
		}

		return m, tea.Batch(
			m.progress.IncrPercent(0.1),
			m.progress2.IncrPercent(0.1),
		)

	// FrameMsg is sent when the progress bar wants to animate itself
	case progress.FrameMsg:
		progressModel, cmd := m.progress.Update(msg)
		if cmd != nil {
			p := progressModel.(progress.Model)
			m.progress = &p
		}

		progressModel2, cmd2 := m.progress2.Update(msg)
		if cmd2 != nil {
			p2 := progressModel2.(progress.Model)
			m.progress2 = &p2
		}
		return m, tea.Batch(cmd, cmd2)

	default:
		return m, nil
	}
}

func (m *model) View() string {
	return "\n" +
		m.progress.View() + "\n\n" +
		m.progress2.View() + "\n\n" +
		"Press any key to quit"
}

go.mod

module asd

go 1.24.2

require (
	github.com/charmbracelet/bubbles v0.21.0
	github.com/charmbracelet/bubbletea v1.3.5
)

require (
	github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
	github.com/charmbracelet/colorprofile v0.3.1 // indirect
	github.com/charmbracelet/harmonica v0.2.0 // indirect
	github.com/charmbracelet/lipgloss v1.1.0 // indirect
	github.com/charmbracelet/x/ansi v0.9.2 // indirect
	github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
	github.com/charmbracelet/x/term v0.2.1 // indirect
	github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
	github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
	github.com/mattn/go-isatty v0.0.20 // indirect
	github.com/mattn/go-localereader v0.0.1 // indirect
	github.com/mattn/go-runewidth v0.0.16 // indirect
	github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
	github.com/muesli/cancelreader v0.2.2 // indirect
	github.com/muesli/termenv v0.16.0 // indirect
	github.com/rivo/uniseg v0.4.7 // indirect
	github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
	golang.org/x/sync v0.15.0 // indirect
	golang.org/x/sys v0.33.0 // indirect
	golang.org/x/text v0.26.0 // indirect
)

Running the above, triggers the race detector from time to time, i would say 1/2 times on my machine.

racy-progress.mp4
==================
WARNING: DATA RACE
Read at 0x00c000367c10 by goroutine 83:
  github.com/charmbracelet/bubbles/progress.(*Model).SetPercent.(*Model).nextFrame.func1()
      /home/fas/go/pkg/mod/github.com/charmbracelet/bubbles@v0.21.0/progress/progress.go:289 +0x4c
  github.com/charmbracelet/bubbletea.Tick.func1()
      /home/fas/go/pkg/mod/github.com/charmbracelet/bubbletea@v1.3.5/commands.go:156 +0xe8
  github.com/charmbracelet/bubbletea.(*Program).handleCommands.func1.1()
      /home/fas/go/pkg/mod/github.com/charmbracelet/bubbletea@v1.3.5/tea.go:365 +0xd9

Previous write at 0x00c000367c10 by main goroutine:
  github.com/charmbracelet/bubbles/progress.(*Model).SetPercent()
      /home/fas/go/pkg/mod/github.com/charmbracelet/bubbles@v0.21.0/progress/progress.go:252 +0xdc
  github.com/charmbracelet/bubbles/progress.(*Model).IncrPercent()
      /home/fas/go/pkg/mod/github.com/charmbracelet/bubbles@v0.21.0/progress/progress.go:261 +0x754
  main.(*model).Update()
      /home/fas/github.com/charmbracelet/bubbletea/examples/progress-animated/main.go:73 +0x672
  github.com/charmbracelet/bubbletea.(*Program).eventLoop()
      /home/fas/go/pkg/mod/github.com/charmbracelet/bubbletea@v1.3.5/tea.go:525 +0xb53
  github.com/charmbracelet/bubbletea.(*Program).Run()
      /home/fas/go/pkg/mod/github.com/charmbracelet/bubbletea@v1.3.5/tea.go:679 +0x1590
  main.main()
      /home/fas/github.com/charmbracelet/bubbletea/examples/progress-animated/main.go:43 +0x4b7

Goroutine 83 (running) created at:
  github.com/charmbracelet/bubbletea.(*Program).handleCommands.func1()
      /home/fas/go/pkg/mod/github.com/charmbracelet/bubbletea@v1.3.5/tea.go:355 +0x24d
==================
==================
WARNING: DATA RACE
Read at 0x00c000372710 by goroutine 82:
  github.com/charmbracelet/bubbles/progress.(*Model).SetPercent.(*Model).nextFrame.func1()
      /home/fas/go/pkg/mod/github.com/charmbracelet/bubbles@v0.21.0/progress/progress.go:289 +0x4c
  github.com/charmbracelet/bubbletea.Tick.func1()
      /home/fas/go/pkg/mod/github.com/charmbracelet/bubbletea@v1.3.5/commands.go:156 +0xe8
  github.com/charmbracelet/bubbletea.(*Program).handleCommands.func1.1()
      /home/fas/go/pkg/mod/github.com/charmbracelet/bubbletea@v1.3.5/tea.go:365 +0xd9

Previous write at 0x00c000372710 by main goroutine:
  github.com/charmbracelet/bubbles/progress.(*Model).SetPercent()
      /home/fas/go/pkg/mod/github.com/charmbracelet/bubbles@v0.21.0/progress/progress.go:252 +0xdc
  github.com/charmbracelet/bubbles/progress.(*Model).IncrPercent()
      /home/fas/go/pkg/mod/github.com/charmbracelet/bubbles@v0.21.0/progress/progress.go:261 +0x6d4
  main.(*model).Update()
      /home/fas/github.com/charmbracelet/bubbletea/examples/progress-animated/main.go:72 +0x650
  github.com/charmbracelet/bubbletea.(*Program).eventLoop()
      /home/fas/go/pkg/mod/github.com/charmbracelet/bubbletea@v1.3.5/tea.go:525 +0xb53
  github.com/charmbracelet/bubbletea.(*Program).Run()
      /home/fas/go/pkg/mod/github.com/charmbracelet/bubbletea@v1.3.5/tea.go:679 +0x1590
  main.main()
      /home/fas/github.com/charmbracelet/bubbletea/examples/progress-animated/main.go:43 +0x4b7

Goroutine 82 (running) created at:
  github.com/charmbracelet/bubbletea.(*Program).handleCommands.func1()
      /home/fas/go/pkg/mod/github.com/charmbracelet/bubbletea@v1.3.5/tea.go:355 +0x24d
==================
Found 2 data race(s)
exit status 66

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions