fix(scheduler): fix data race (#2085)

* fix(scheduler): data race when pushing new tasks

the problem here is that scheduler can be closed in two ways:
- canceling the context given as argument to scheduler.RunScheduler()
- running scheduler.Shutdown()

because of this shutdown can trigger a data race between calling scheduler.inShutdown()
and actually pushing tasks into the pool workers

solved that by keeping a quit channel and listening on both quit channel and ctx.Done()
and closing the worker chan and scheduler afterwards.

Signed-off-by: Petu Eusebiu <peusebiu@cisco.com>

* refactor(scheduler): refactor into a single shutdown

before this we could stop scheduler either by closing the context
provided to RunScheduler(ctx) or by running Shutdown().

simplify things by getting rid of the external context in RunScheduler().
keep an internal context in the scheduler itself and pass it down to all tasks.

Signed-off-by: Petu Eusebiu <peusebiu@cisco.com>

---------

Signed-off-by: Petu Eusebiu <peusebiu@cisco.com>
This commit is contained in:
peusebiu
2023-12-11 20:00:34 +02:00
committed by GitHub
parent d71a1f494e
commit 7642e5af98
31 changed files with 494 additions and 326 deletions
+1 -1
View File
@@ -44,7 +44,7 @@ func New(
storeController storage.StoreController,
metadb mTypes.MetaDB,
log log.Logger,
) (Service, error) {
) (*BaseService, error) {
service := &BaseService{}
service.config = opts
+23
View File
@@ -170,6 +170,29 @@ func TestService(t *testing.T) {
})
}
func TestSyncRepo(t *testing.T) {
Convey("trigger context error", t, func() {
conf := syncconf.RegistryConfig{
URLs: []string{"http://localhost"},
}
service, err := New(conf, "", os.TempDir(), storage.StoreController{}, mocks.MetaDBMock{}, log.Logger{})
So(err, ShouldBeNil)
service.remote = mocks.SyncRemote{
GetRepoTagsFn: func(repo string) ([]string, error) {
return []string{"repo1", "repo2"}, nil
},
}
ctx, cancel := context.WithCancel(context.Background())
cancel()
err = service.SyncRepo(ctx, "repo")
So(err, ShouldEqual, ctx.Err())
})
}
func TestDestinationRegistry(t *testing.T) {
Convey("make StoreController", t, func() {
dir := t.TempDir()
+67 -78
View File
@@ -1901,15 +1901,15 @@ func TestConfigReloader(t *testing.T) {
hotReloader, err := cli.NewHotReloader(dctlr, cfgfile.Name())
So(err, ShouldBeNil)
reloadCtx := hotReloader.Start()
hotReloader.Start()
go func() {
// this blocks
if err := dctlr.Init(reloadCtx); err != nil {
if err := dctlr.Init(); err != nil {
return
}
if err := dctlr.Run(reloadCtx); err != nil {
if err := dctlr.Run(); err != nil {
return
}
}()
@@ -2051,15 +2051,15 @@ func TestConfigReloader(t *testing.T) {
hotReloader, err := cli.NewHotReloader(dctlr, cfgfile.Name())
So(err, ShouldBeNil)
reloadCtx := hotReloader.Start()
hotReloader.Start()
go func() {
// this blocks
if err := dctlr.Init(reloadCtx); err != nil {
if err := dctlr.Init(); err != nil {
return
}
if err := dctlr.Run(reloadCtx); err != nil {
if err := dctlr.Run(); err != nil {
return
}
}()
@@ -3938,6 +3938,12 @@ func TestPeriodicallySignaturesErr(t *testing.T) {
}
}
// start downstream server
updateDuration, err = time.ParseDuration("1s")
So(err, ShouldBeNil)
syncConfig.Registries[0].PollInterval = updateDuration
// start downstream server
dctlr, destBaseURL, _, _ := makeDownstreamServer(t, false, syncConfig)
@@ -4030,6 +4036,61 @@ func TestPeriodicallySignaturesErr(t *testing.T) {
So(err, ShouldBeNil)
So(len(index.Manifests), ShouldEqual, 0)
})
Convey("of type OCI image, error on downstream in canSkipReference()", func() { //nolint: dupl
// start downstream server
updateDuration, err = time.ParseDuration("1s")
So(err, ShouldBeNil)
syncConfig.Registries[0].PollInterval = updateDuration
dctlr, _, destDir, _ := makeDownstreamServer(t, false, syncConfig)
dcm := test.NewControllerManager(dctlr)
dcm.StartAndWait(dctlr.Config.HTTP.Port)
defer dcm.StopServer()
found, err := test.ReadLogFileAndSearchString(dctlr.Config.Log.Output,
"finished syncing all repos", 15*time.Second)
if err != nil {
panic(err)
}
if !found {
data, err := os.ReadFile(dctlr.Config.Log.Output)
So(err, ShouldBeNil)
t.Logf("downstream log: %s", string(data))
}
So(found, ShouldBeTrue)
time.Sleep(time.Second)
blob := referrers.Manifests[0]
blobsDir := path.Join(destDir, repoName, "blobs", string(blob.Digest.Algorithm()))
blobPath := path.Join(blobsDir, blob.Digest.Encoded())
err = os.MkdirAll(blobsDir, storageConstants.DefaultDirPerms)
So(err, ShouldBeNil)
err = os.WriteFile(blobPath, []byte("blob"), storageConstants.DefaultFilePerms)
So(err, ShouldBeNil)
err = os.Chmod(blobPath, 0o000)
So(err, ShouldBeNil)
found, err = test.ReadLogFileAndSearchString(dctlr.Config.Log.Output,
"couldn't check if the upstream oci references for image can be skipped", 30*time.Second)
if err != nil {
panic(err)
}
if !found {
data, err := os.ReadFile(dctlr.Config.Log.Output)
So(err, ShouldBeNil)
t.Logf("downstream log: %s", string(data))
}
So(found, ShouldBeTrue)
})
})
})
}
@@ -5200,78 +5261,6 @@ func TestOnDemandPullsOnce(t *testing.T) {
})
}
func TestError(t *testing.T) {
Convey("Verify periodically sync pushSyncedLocalImage() error", t, func() {
updateDuration, _ := time.ParseDuration("30m")
sctlr, srcBaseURL, _, _, _ := makeUpstreamServer(t, false, false)
scm := test.NewControllerManager(sctlr)
scm.StartAndWait(sctlr.Config.HTTP.Port)
defer scm.StopServer()
regex := ".*"
semver := true
var tlsVerify bool
syncRegistryConfig := syncconf.RegistryConfig{
Content: []syncconf.Content{
{
Prefix: testImage,
Tags: &syncconf.Tags{
Regex: &regex,
Semver: &semver,
},
},
},
URLs: []string{srcBaseURL},
PollInterval: updateDuration,
TLSVerify: &tlsVerify,
CertDir: "",
}
defaultVal := true
syncConfig := &syncconf.Config{
Enable: &defaultVal,
Registries: []syncconf.RegistryConfig{syncRegistryConfig},
}
dctlr, _, destDir, _ := makeDownstreamServer(t, false, syncConfig)
dcm := test.NewControllerManager(dctlr)
dcm.StartAndWait(dctlr.Config.HTTP.Port)
defer dcm.StopServer()
// give permission denied on pushSyncedLocalImage()
localRepoPath := path.Join(destDir, testImage, "blobs")
err := os.MkdirAll(localRepoPath, 0o755)
So(err, ShouldBeNil)
err = os.Chmod(localRepoPath, 0o000)
So(err, ShouldBeNil)
defer func() {
err = os.Chmod(localRepoPath, 0o755)
So(err, ShouldBeNil)
}()
found, err := test.ReadLogFileAndSearchString(dctlr.Config.Log.Output,
"couldn't commit image to local image store", 30*time.Second)
if err != nil {
panic(err)
}
if !found {
data, err := os.ReadFile(dctlr.Config.Log.Output)
So(err, ShouldBeNil)
t.Logf("downstream log: %s", string(data))
}
So(found, ShouldBeTrue)
})
}
func TestSignaturesOnDemand(t *testing.T) {
Convey("Verify sync signatures on demand feature", t, func() {
sctlr, srcBaseURL, srcDir, _, _ := makeUpstreamServer(t, false, false)