Skip to content

Data race on App.session between Start's startup-info goroutine and ReplaceSession #4229

Description

@aheritier

Description

go test -race -shuffle=on ./pkg/tui (the test-race CI job added in #4221) fails intermittently, roughly 1 run in 5, on TestLoadSessionThenClickEditLabel. The race detector reports a write/read race on App.session in pkg/app:

  • App.ReplaceSession writes a.session (called from the bubbletea update loop when a past session is loaded).
  • The goroutine started by App.Start reads a.session to call runtime.EmitStartupInfo. reEmitStartupInfo has the same shape: its closure reads a.session from inside the goroutine pumpToEvents spawns.

This is a production data race, not a test artifact: startup info is emitted in the background while the UI can already replace the session. It was not part of the four causes fixed in #4228 (it did not fire in run 34493297831), which is why it is tracked separately.

Expected Behavior

go test -race -shuffle=on ./pkg/tui passes consistently.

Actual Behavior

TestLoadSessionThenClickEditLabel/new_tab (or another subtest, depending on timing) fails with WARNING: DATA RACE and the package fails.

Steps to Reproduce

go test -race -count=5 -run TestLoadSessionThenClickEditLabel ./pkg/tui

Fails about once per 5 iterations on an M-series Mac with Go 1.27.

Error output

WARNING: DATA RACE
Write at 0x00c000f845b8 by goroutine 1314:
  github.com/docker/docker-agent/pkg/app.(*App).ReplaceSession()
      pkg/app/app.go:1705 +0x9c
  github.com/docker/docker-agent/pkg/tui.(*appModel).handleLoadSession()
      pkg/tui/tui.go:1723 +0x7e8
  github.com/docker/docker-agent/pkg/tui.(*appModel).update()
      pkg/tui/tui.go:1260 +0x5688
  github.com/docker/docker-agent/pkg/tui.(*appModel).Update()
      pkg/tui/tui.go:857 +0x3c
  github.com/docker/docker-agent/pkg/tui/tuitest.(*captureModel).Update()
      pkg/tui/tuitest/driver.go:357 +0x8c
  charm.land/bubbletea/v2.(*Program).eventLoop()
      charm.land/bubbletea/v2@v2.0.9/tea.go:880 +0xed0
  charm.land/bubbletea/v2.(*Program).Run()
      charm.land/bubbletea/v2@v2.0.9/tea.go:1153 +0xe10
  github.com/docker/docker-agent/pkg/tui/tuitest.New.func1()
      pkg/tui/tuitest/driver.go:168 +0x84

Previous read at 0x00c000f845b8 by goroutine 1333:
  github.com/docker/docker-agent/pkg/app.(*App).Start.func1.1.1()
      pkg/app/app.go:168 +0x98

Goroutine 1314 (running) created at:
  github.com/docker/docker-agent/pkg/tui/tuitest.New()
      pkg/tui/tuitest/driver.go:166 +0x520
  github.com/docker/docker-agent/pkg/tui.testLoadSessionThenClickEditLabel()
      pkg/tui/session_load_click_test.go:85 +0x7b4
  github.com/docker/docker-agent/pkg/tui.TestLoadSessionThenClickEditLabel.func2()
      pkg/tui/session_load_click_test.go:35 +0x30
  testing.tRunner()
      GOROOT/src/testing/testing.go:2193 +0x164
  testing.(*T).Run.gowrap1()
      GOROOT/src/testing/testing.go:2258 +0x34

Goroutine 1333 (finished) created at:
  github.com/docker/docker-agent/pkg/app.(*App).Start.func1.1()
      pkg/app/app.go:166 +0x11c
==================

Additional context

Proposed fix: snapshot the session on the caller's goroutine (sess := a.session) before spawning the startup-info goroutines in Start and reEmitStartupInfo, so the background work never reads the field. ReplaceSession re-emits startup info for the new session anyway, so emitting for the snapshotted session is the intended behavior.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/coreCore agent runtime, session managementarea/tuiFor features/issues/fixes related to the TUI

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions