github.com/containerd/Containerd@v1.4.13/gc/scheduler/scheduler_test.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package scheduler
    18  
    19  import (
    20  	"context"
    21  	"sync"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/containerd/containerd/gc"
    26  	"gotest.tools/v3/assert"
    27  )
    28  
    29  func TestPauseThreshold(t *testing.T) {
    30  	cfg := &config{
    31  		// With 100μs, gc should run about every 5ms
    32  		PauseThreshold: 0.02,
    33  	}
    34  	tc := &testCollector{
    35  		d: time.Microsecond * 100,
    36  	}
    37  
    38  	scheduler := newScheduler(tc, cfg)
    39  
    40  	ctx, cancel := context.WithCancel(context.Background())
    41  	defer cancel()
    42  
    43  	go scheduler.run(ctx)
    44  
    45  	// Ensure every possible GC cycle runs
    46  	go func() {
    47  		tick := time.NewTicker(time.Microsecond * 100)
    48  		for {
    49  			select {
    50  			case <-tick.C:
    51  				tc.trigger(true)
    52  			case <-ctx.Done():
    53  				return
    54  			}
    55  		}
    56  	}()
    57  
    58  	time.Sleep(time.Millisecond * 15)
    59  
    60  	if c := tc.runCount(); c > 4 {
    61  		t.Fatalf("unexpected gc run count %d, expected less than 5", c)
    62  	}
    63  }
    64  
    65  func TestDeletionThreshold(t *testing.T) {
    66  	cfg := &config{
    67  		// Prevent GC from scheduling again before check
    68  		PauseThreshold:    0.001,
    69  		DeletionThreshold: 5,
    70  	}
    71  	tc := &testCollector{
    72  		d: time.Second,
    73  	}
    74  
    75  	scheduler := newScheduler(tc, cfg)
    76  
    77  	ctx, cancel := context.WithCancel(context.Background())
    78  	defer cancel()
    79  
    80  	go scheduler.run(ctx)
    81  
    82  	// Block until next GC finishes
    83  	gcWait := make(chan struct{})
    84  	go func() {
    85  		scheduler.wait(ctx, false)
    86  		close(gcWait)
    87  	}()
    88  
    89  	// Increment deletion count 5, checking GC hasn't run in
    90  	// between each call
    91  	for i := 0; i < 5; i++ {
    92  		time.Sleep(time.Millisecond)
    93  		if c := tc.runCount(); c != 0 {
    94  			t.Fatalf("GC ran unexpectedly")
    95  		}
    96  		tc.trigger(true)
    97  	}
    98  
    99  	select {
   100  	case <-gcWait:
   101  	case <-time.After(time.Millisecond * 30):
   102  		t.Fatal("GC wait timed out")
   103  	}
   104  
   105  	if c := tc.runCount(); c != 1 {
   106  		t.Fatalf("unexpected gc run count %d, expected 1", c)
   107  	}
   108  }
   109  
   110  func TestTrigger(t *testing.T) {
   111  	var (
   112  		cfg = &config{}
   113  		tc  = &testCollector{
   114  			d: time.Millisecond * 10,
   115  		}
   116  		ctx, cancel = context.WithCancel(context.Background())
   117  		scheduler   = newScheduler(tc, cfg)
   118  		stats       gc.Stats
   119  		err         error
   120  	)
   121  
   122  	defer cancel()
   123  	go scheduler.run(ctx)
   124  
   125  	// Block until next GC finishes
   126  	gcWait := make(chan struct{})
   127  	go func() {
   128  		stats, err = scheduler.ScheduleAndWait(ctx)
   129  		close(gcWait)
   130  	}()
   131  
   132  	select {
   133  	case <-gcWait:
   134  	case <-time.After(time.Millisecond * 10):
   135  		t.Fatal("GC wait timed out")
   136  	}
   137  
   138  	if err != nil {
   139  		t.Fatalf("GC failed: %#v", err)
   140  	}
   141  
   142  	assert.Equal(t, tc.d, stats.Elapsed())
   143  
   144  	if c := tc.runCount(); c != 1 {
   145  		t.Fatalf("unexpected gc run count %d, expected 1", c)
   146  	}
   147  }
   148  
   149  func TestStartupDelay(t *testing.T) {
   150  	var (
   151  		cfg = &config{
   152  			// Prevent GC from scheduling again before check
   153  			PauseThreshold: 0.001,
   154  			StartupDelay:   duration(time.Millisecond),
   155  		}
   156  		tc = &testCollector{
   157  			d: time.Second,
   158  		}
   159  		ctx, cancel = context.WithCancel(context.Background())
   160  		scheduler   = newScheduler(tc, cfg)
   161  	)
   162  	defer cancel()
   163  	go scheduler.run(ctx)
   164  
   165  	time.Sleep(time.Millisecond * 30)
   166  
   167  	if c := tc.runCount(); c != 1 {
   168  		t.Fatalf("unexpected gc run count %d, expected 1", c)
   169  	}
   170  }
   171  
   172  type testCollector struct {
   173  	d  time.Duration
   174  	gc int
   175  	m  sync.Mutex
   176  
   177  	callbacks []func(bool)
   178  }
   179  
   180  func (tc *testCollector) trigger(delete bool) {
   181  	for _, f := range tc.callbacks {
   182  		f(delete)
   183  	}
   184  }
   185  
   186  func (tc *testCollector) runCount() int {
   187  	tc.m.Lock()
   188  	c := tc.gc
   189  	tc.m.Unlock()
   190  	return c
   191  }
   192  
   193  func (tc *testCollector) RegisterMutationCallback(f func(bool)) {
   194  	tc.callbacks = append(tc.callbacks, f)
   195  }
   196  
   197  func (tc *testCollector) GarbageCollect(context.Context) (gc.Stats, error) {
   198  	tc.m.Lock()
   199  	tc.gc++
   200  	tc.m.Unlock()
   201  	return gcStats{elapsed: tc.d}, nil
   202  }
   203  
   204  type gcStats struct {
   205  	elapsed time.Duration
   206  }
   207  
   208  // Elapsed returns the duration which elapsed during a collection
   209  func (s gcStats) Elapsed() time.Duration {
   210  	return s.elapsed
   211  }