diff --git a/manager/container.go b/manager/container.go index c599300d84..688e3564fd 100644 --- a/manager/container.go +++ b/manager/container.go @@ -138,7 +138,10 @@ func (cd *containerData) allowErrorLogging() bool { // periodic housekeeping to reset. This should be used sparingly, as calling OnDemandHousekeeping frequently // can have serious performance costs. func (cd *containerData) OnDemandHousekeeping(maxAge time.Duration) { - if cd.clock.Since(cd.statsLastUpdatedTime) > maxAge { + cd.lock.Lock() + timeSinceStatsLastUpdate := cd.clock.Since(cd.statsLastUpdatedTime) + cd.lock.Unlock() + if timeSinceStatsLastUpdate > maxAge { housekeepingFinishedChan := make(chan struct{}) cd.onDemandChan <- housekeepingFinishedChan select { @@ -555,6 +558,8 @@ func (cd *containerData) housekeepingTick(timer <-chan time.Time, longHousekeepi klog.V(3).Infof("[%s] Housekeeping took %s", cd.info.Name, duration) } cd.notifyOnDemand() + cd.lock.Lock() + defer cd.lock.Unlock() cd.statsLastUpdatedTime = cd.clock.Now() return true } diff --git a/manager/container_test.go b/manager/container_test.go index cdb1228ca2..63872ed6ea 100644 --- a/manager/container_test.go +++ b/manager/container_test.go @@ -319,3 +319,34 @@ func TestOnDemandHousekeepingReturnsAfterStopped(t *testing.T) { mockHandler.AssertExpectations(t) } + +func TestOnDemandHousekeepingRace(t *testing.T) { + statsList := itest.GenerateRandomStats(1, 4, 1*time.Second) + stats := statsList[0] + + cd, mockHandler, _, _ := newTestContainerData(t) + mockHandler.On("GetStats").Return(stats, nil) + + wg := sync.WaitGroup{} + wg.Add(1002) + + go func() { + time.Sleep(10 * time.Millisecond) + err := cd.Start() + assert.NoError(t, err) + wg.Done() + }() + + go func() { + t.Log("starting on demand goroutine") + for i := 0; i < 1000; i++ { + go func() { + time.Sleep(1 * time.Microsecond) + cd.OnDemandHousekeeping(0 * time.Millisecond) + wg.Done() + }() + } + wg.Done() + }() + wg.Wait() +}