Skip to content

Proxy scheduler test may fail with data race warning #4646

@horoshev

Description

@horoshev

Description

Found a bug when running TestSchedule test.
When creating a new entry, a private add function is used, which accesses the shared variables indexDirty and entries without mutex.
This leads to data race errors.

Also there is an anonymous function call. Should it be in a goroutine?

// scheduler_test.go:68:72
func() {
    s.Lock()
    s.add(ref3, 1*timeUnit, entryTypeBlob)
    s.Unlock()
}()

Reproduce

To reproduce the error consistently, I increased the number of entries to 20.

func testRefsN(t *testing.T, n int) []reference.Reference {
	refs := make([]reference.Reference, 0, n)
	for i := range n {
		name := "testrepo@sha256:" + fmt.Sprintf("%064d", i)

		ref, err := reference.Parse(name)
		if err != nil {
			t.Fatalf("could not parse reference: %v", err)
		}

		refs = append(refs, ref)
	}

	return refs
}

func TestSchedule(t *testing.T) {
	refs := testRefsN(t, 20)
	timeUnit := time.Millisecond

	remainingRepos := map[string]bool{}
	for _, ref := range refs {
		remainingRepos[ref.String()] = true
	}

	var mu sync.Mutex
	s := New(dcontext.Background(), inmemory.New(), "/ttl")
	deleteFunc := func(repoName reference.Reference) error {
		if len(remainingRepos) == 0 {
			t.Fatal("Incorrect expiry count")
		}
		_, ok := remainingRepos[repoName.String()]
		if !ok {
			t.Fatalf("Trying to remove nonexistent repo: %s", repoName)
		}
		t.Log("removing", repoName)
		mu.Lock()
		delete(remainingRepos, repoName.String())
		mu.Unlock()

		return nil
	}
	s.onBlobExpire = deleteFunc
	err := s.Start()
	if err != nil {
		t.Fatalf("Error starting ttlExpirationScheduler: %s", err)
	}

	for i, ref := range refs {
		s.add(ref, time.Duration(i)*timeUnit, entryTypeBlob)
	}

	// Ensure all repos are deleted
	<-time.After(50 * timeUnit)

	mu.Lock()
	defer mu.Unlock()
	if len(remainingRepos) != 0 {
		t.Fatalf("Repositories remaining: %#v", remainingRepos)
	}
}
go test -race ./registry/proxy/scheduler -run TestSchedule

Expected behavior

No response

registry version

v3.0.0

Additional Info

Output when test fails:

==================
WARNING: DATA RACE
Read at 0x00c00040c540 by goroutine 11:
  runtime.mapdelete_faststr()
      /home/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.2.darwin-arm64/src/internal/runtime/maps/runtime_faststr_swiss.go:396 +0x8c
  github.com/distribution/distribution/v3/registry/proxy/scheduler.(*TTLExpirationScheduler).add()
      /home/dev/distribution/registry/proxy/scheduler/scheduler.go:169 +0x278
  github.com/distribution/distribution/v3/registry/proxy/scheduler.TestScheduleRace()
      /home/dev/distribution/registry/proxy/scheduler/scheduler_test.go:93 +0x6f8
  testing.tRunner()
      /home/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.2.darwin-arm64/src/testing/testing.go:1792 +0x180
  testing.(*T).Run.gowrap1()
      /home/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.2.darwin-arm64/src/testing/testing.go:1851 +0x40

Previous write at 0x00c00040c540 by goroutine 22:
  runtime.mapaccess2()
      /home/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.2.darwin-arm64/src/internal/runtime/maps/runtime_swiss.go:117 +0x2dc
  github.com/distribution/distribution/v3/registry/proxy/scheduler.(*TTLExpirationScheduler).add.(*TTLExpirationScheduler).startTimer.func1()
      /home/dev/distribution/registry/proxy/scheduler/scheduler.go:204 +0x344

Goroutine 11 (running) created at:
  testing.(*T).Run()
      /home/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.2.darwin-arm64/src/testing/testing.go:1851 +0x684
  testing.runTests.func1()
      /home/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.2.darwin-arm64/src/testing/testing.go:2279 +0x7c
  testing.tRunner()
      /home/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.2.darwin-arm64/src/testing/testing.go:1792 +0x180
  testing.runTests()
      /home/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.2.darwin-arm64/src/testing/testing.go:2277 +0x77c
  testing.(*M).Run()
      /home/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.2.darwin-arm64/src/testing/testing.go:2142 +0xb68
  main.main()
      _testmain.go:55 +0x110

Goroutine 22 (running) created at:
  time.goFunc()
      /home/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.2.darwin-arm64/src/time/sleep.go:215 +0x40
==================

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions