hide-org / hide

🤖 Headless IDE for AI agents
https://hide.sh
MIT License
112 stars 5 forks source link

Race condition in TestDockerImageManager_BuildImage #90

Open artmoskvin opened 3 weeks ago

artmoskvin commented 3 weeks ago

If you run go test -race ./... you'll see something like:

=== RUN   TestDockerImageManager_BuildImage/Build_image
{"level":"debug","buildContextPath":"/workdir","time":"2024-08-29T15:41:30+02:00","message":"Building image"}
==================
WARNING: DATA RACE
Write at 0x00c00012e300 by goroutine 9:
  ??()
      -:0 +0x1026c7688
  sync/atomic.CompareAndSwapInt32()
      <autogenerated>:1 +0x18
  io.(*pipe).write()
      /opt/homebrew/Cellar/go/1.23.0/libexec/src/io/pipe.go:81 +0x70
  io.(*PipeWriter).Write()
      /opt/homebrew/Cellar/go/1.23.0/libexec/src/io/pipe.go:161 +0x44
  bufio.(*Writer).Flush()
      /opt/homebrew/Cellar/go/1.23.0/libexec/src/bufio/bufio.go:639 +0xc0
  github.com/docker/docker/pkg/archive.CompressStream.(*BufioWriterPool).NewWriteCloserWrapper.func1()
      /Users/artemm/go/pkg/mod/github.com/docker/docker@v26.1.3+incompatible/pkg/pools/pools.go:130 +0x44
  github.com/docker/docker/pkg/ioutils.(*writeCloserWrapper).Close()
      /Users/artemm/go/pkg/mod/github.com/docker/docker@v26.1.3+incompatible/pkg/ioutils/writers.go:43 +0x58
  github.com/docker/docker/pkg/archive.(*Tarballer).Do.func1()
      /Users/artemm/go/pkg/mod/github.com/docker/docker@v26.1.3+incompatible/pkg/archive/archive.go:919 +0x108
  runtime.deferreturn()
      /opt/homebrew/Cellar/go/1.23.0/libexec/src/runtime/panic.go:605 +0x5c
  github.com/docker/docker/pkg/archive.TarWithOptions.gowrap1()
      /Users/artemm/go/pkg/mod/github.com/docker/docker@v26.1.3+incompatible/pkg/archive/archive.go:849 +0x34

Previous read at 0x00c00012e300 by goroutine 8:
  reflect.typedmemmove()
      /opt/homebrew/Cellar/go/1.23.0/libexec/src/runtime/mbarrier.go:225 +0x0
  reflect.packEface()
      /opt/homebrew/Cellar/go/1.23.0/libexec/src/reflect/value.go:133 +0xa4
  reflect.valueInterface()
      /opt/homebrew/Cellar/go/1.23.0/libexec/src/reflect/value.go:1510 +0x148
  reflect.Value.Interface()
      /opt/homebrew/Cellar/go/1.23.0/libexec/src/reflect/value.go:1481 +0x74
  fmt.(*pp).printValue()
      /opt/homebrew/Cellar/go/1.23.0/libexec/src/fmt/print.go:769 +0x80
  fmt.(*pp).printValue()
      /opt/homebrew/Cellar/go/1.23.0/libexec/src/fmt/print.go:921 +0xf70
  fmt.(*pp).printArg()
      /opt/homebrew/Cellar/go/1.23.0/libexec/src/fmt/print.go:759 +0x8a0
  fmt.(*pp).doPrintf()
      /opt/homebrew/Cellar/go/1.23.0/libexec/src/fmt/print.go:1173 +0xc58
  fmt.Sprintf()
      /opt/homebrew/Cellar/go/1.23.0/libexec/src/fmt/print.go:239 +0x50
  github.com/stretchr/testify/mock.Arguments.Diff()
      /Users/artemm/go/pkg/mod/github.com/stretchr/testify@v1.9.0/mock/mock.go:939 +0x13c
  github.com/stretchr/testify/mock.(*Mock).findExpectedCall()
      /Users/artemm/go/pkg/mod/github.com/stretchr/testify@v1.9.0/mock/mock.go:368 +0x118
  github.com/stretchr/testify/mock.(*Mock).MethodCalled()
      /Users/artemm/go/pkg/mod/github.com/stretchr/testify@v1.9.0/mock/mock.go:476 +0x6c
  github.com/stretchr/testify/mock.(*Mock).Called()
      /Users/artemm/go/pkg/mod/github.com/stretchr/testify@v1.9.0/mock/mock.go:466 +0x150
  github.com/artmoskvin/hide/pkg/devcontainer/mocks.(*MockDockerImageClient).ImageBuild()
      /Users/artemm/Code/hide/pkg/devcontainer/mocks/mock_docker_image_client.go:22 +0x180
  github.com/artmoskvin/hide/pkg/devcontainer.(*DockerImageManager).BuildImage()
      /Users/artemm/Code/hide/pkg/devcontainer/image_manager.go:117 +0x8b8
  github.com/artmoskvin/hide/pkg/devcontainer_test.TestDockerImageManager_BuildImage.func10()
      /Users/artemm/Code/hide/pkg/devcontainer/image_manager_test.go:247 +0x2b8
  testing.tRunner()
      /opt/homebrew/Cellar/go/1.23.0/libexec/src/testing/testing.go:1690 +0x184
  testing.(*T).Run.gowrap1()
      /opt/homebrew/Cellar/go/1.23.0/libexec/src/testing/testing.go:1743 +0x40

Goroutine 9 (running) created at:
  github.com/docker/docker/pkg/archive.TarWithOptions()
      /Users/artemm/go/pkg/mod/github.com/docker/docker@v26.1.3+incompatible/pkg/archive/archive.go:849 +0xa4
  github.com/artmoskvin/hide/pkg/devcontainer.(*DockerImageManager).BuildImage()
      /Users/artemm/Code/hide/pkg/devcontainer/image_manager.go:90 +0x3b4
  github.com/artmoskvin/hide/pkg/devcontainer_test.TestDockerImageManager_BuildImage.func10()
      /Users/artemm/Code/hide/pkg/devcontainer/image_manager_test.go:247 +0x2b8
  testing.tRunner()
      /opt/homebrew/Cellar/go/1.23.0/libexec/src/testing/testing.go:1690 +0x184
  testing.(*T).Run.gowrap1()
      /opt/homebrew/Cellar/go/1.23.0/libexec/src/testing/testing.go:1743 +0x40

Goroutine 8 (running) created at:
  testing.(*T).Run()
      /opt/homebrew/Cellar/go/1.23.0/libexec/src/testing/testing.go:1743 +0x5e0
  github.com/artmoskvin/hide/pkg/devcontainer_test.TestDockerImageManager_BuildImage()
      /Users/artemm/Code/hide/pkg/devcontainer/image_manager_test.go:241 +0x5ec
  testing.tRunner()
      /opt/homebrew/Cellar/go/1.23.0/libexec/src/testing/testing.go:1690 +0x184
  testing.(*T).Run.gowrap1()
      /opt/homebrew/Cellar/go/1.23.0/libexec/src/testing/testing.go:1743 +0x40
==================
{"level":"debug","tag":"test:latest","time":"2024-08-29T15:41:30+02:00","message":"Built image"}
    testing.go:1399: race detected during execution of test
    --- FAIL: TestDockerImageManager_BuildImage/Build_image (0.00s)

I suspect the problem is in the io.Reader returned by the archive.TarWithOptions() call. The archive is created asynchronously by go tb.Do():

func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) {
    tb, err := NewTarballer(srcPath, options)
    if err != nil {
        return nil, err
    }
    go tb.Do()
    return tb.Reader(), nil
}

At the same time the mock library tries to get a string representation of the args, one of which is the aforementioned io.Reader, at which point the race condition occurs.