github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/client/gc_test.go (about)

     1  package client
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/hashicorp/nomad/client/allocrunner"
     9  	"github.com/hashicorp/nomad/client/config"
    10  	"github.com/hashicorp/nomad/client/stats"
    11  	"github.com/hashicorp/nomad/helper/testlog"
    12  	"github.com/hashicorp/nomad/nomad"
    13  	"github.com/hashicorp/nomad/nomad/mock"
    14  	"github.com/hashicorp/nomad/nomad/structs"
    15  	"github.com/hashicorp/nomad/testutil"
    16  	"github.com/stretchr/testify/require"
    17  )
    18  
    19  func gcConfig() *GCConfig {
    20  	return &GCConfig{
    21  		DiskUsageThreshold:  80,
    22  		InodeUsageThreshold: 70,
    23  		Interval:            1 * time.Minute,
    24  		ReservedDiskMB:      0,
    25  		MaxAllocs:           100,
    26  	}
    27  }
    28  
    29  // exitAllocRunner is a helper that updates the allocs on the given alloc
    30  // runners to be terminal
    31  func exitAllocRunner(runners ...AllocRunner) {
    32  	for _, ar := range runners {
    33  		terminalAlloc := ar.Alloc().Copy()
    34  		terminalAlloc.DesiredStatus = structs.AllocDesiredStatusStop
    35  		ar.Update(terminalAlloc)
    36  	}
    37  }
    38  
    39  func TestIndexedGCAllocPQ(t *testing.T) {
    40  	t.Parallel()
    41  	pq := NewIndexedGCAllocPQ()
    42  
    43  	ar1, cleanup1 := allocrunner.TestAllocRunnerFromAlloc(t, mock.Alloc())
    44  	defer cleanup1()
    45  	ar2, cleanup2 := allocrunner.TestAllocRunnerFromAlloc(t, mock.Alloc())
    46  	defer cleanup2()
    47  	ar3, cleanup3 := allocrunner.TestAllocRunnerFromAlloc(t, mock.Alloc())
    48  	defer cleanup3()
    49  	ar4, cleanup4 := allocrunner.TestAllocRunnerFromAlloc(t, mock.Alloc())
    50  	defer cleanup4()
    51  
    52  	pq.Push(ar1.Alloc().ID, ar1)
    53  	pq.Push(ar2.Alloc().ID, ar2)
    54  	pq.Push(ar3.Alloc().ID, ar3)
    55  	pq.Push(ar4.Alloc().ID, ar4)
    56  
    57  	allocID := pq.Pop().allocRunner.Alloc().ID
    58  	if allocID != ar1.Alloc().ID {
    59  		t.Fatalf("expected alloc %v, got %v", allocID, ar1.Alloc().ID)
    60  	}
    61  
    62  	allocID = pq.Pop().allocRunner.Alloc().ID
    63  	if allocID != ar2.Alloc().ID {
    64  		t.Fatalf("expected alloc %v, got %v", allocID, ar1.Alloc().ID)
    65  	}
    66  
    67  	allocID = pq.Pop().allocRunner.Alloc().ID
    68  	if allocID != ar3.Alloc().ID {
    69  		t.Fatalf("expected alloc %v, got %v", allocID, ar1.Alloc().ID)
    70  	}
    71  
    72  	allocID = pq.Pop().allocRunner.Alloc().ID
    73  	if allocID != ar4.Alloc().ID {
    74  		t.Fatalf("expected alloc %v, got %v", allocID, ar1.Alloc().ID)
    75  	}
    76  
    77  	gcAlloc := pq.Pop()
    78  	if gcAlloc != nil {
    79  		t.Fatalf("expected nil, got %v", gcAlloc)
    80  	}
    81  }
    82  
    83  // MockAllocCounter implements AllocCounter interface.
    84  type MockAllocCounter struct {
    85  	allocs int
    86  }
    87  
    88  func (m *MockAllocCounter) NumAllocs() int {
    89  	return m.allocs
    90  }
    91  
    92  type MockStatsCollector struct {
    93  	availableValues []uint64
    94  	usedPercents    []float64
    95  	inodePercents   []float64
    96  	index           int
    97  }
    98  
    99  func (m *MockStatsCollector) Collect() error {
   100  	return nil
   101  }
   102  
   103  func (m *MockStatsCollector) Stats() *stats.HostStats {
   104  	if len(m.availableValues) == 0 {
   105  		return nil
   106  	}
   107  
   108  	available := m.availableValues[m.index]
   109  	usedPercent := m.usedPercents[m.index]
   110  	inodePercent := m.inodePercents[m.index]
   111  
   112  	if m.index < len(m.availableValues)-1 {
   113  		m.index = m.index + 1
   114  	}
   115  	return &stats.HostStats{
   116  		AllocDirStats: &stats.DiskStats{
   117  			Available:         available,
   118  			UsedPercent:       usedPercent,
   119  			InodesUsedPercent: inodePercent,
   120  		},
   121  	}
   122  }
   123  
   124  func TestAllocGarbageCollector_MarkForCollection(t *testing.T) {
   125  	t.Parallel()
   126  	logger := testlog.HCLogger(t)
   127  	gc := NewAllocGarbageCollector(logger, &MockStatsCollector{}, &MockAllocCounter{}, gcConfig())
   128  
   129  	ar1, cleanup1 := allocrunner.TestAllocRunnerFromAlloc(t, mock.Alloc())
   130  	defer cleanup1()
   131  
   132  	gc.MarkForCollection(ar1.Alloc().ID, ar1)
   133  
   134  	gcAlloc := gc.allocRunners.Pop()
   135  	if gcAlloc == nil || gcAlloc.allocRunner != ar1 {
   136  		t.Fatalf("bad gcAlloc: %v", gcAlloc)
   137  	}
   138  }
   139  
   140  func TestAllocGarbageCollector_Collect(t *testing.T) {
   141  	t.Parallel()
   142  	logger := testlog.HCLogger(t)
   143  	gc := NewAllocGarbageCollector(logger, &MockStatsCollector{}, &MockAllocCounter{}, gcConfig())
   144  
   145  	ar1, cleanup1 := allocrunner.TestAllocRunnerFromAlloc(t, mock.Alloc())
   146  	defer cleanup1()
   147  	ar2, cleanup2 := allocrunner.TestAllocRunnerFromAlloc(t, mock.Alloc())
   148  	defer cleanup2()
   149  
   150  	go ar1.Run()
   151  	go ar2.Run()
   152  
   153  	gc.MarkForCollection(ar1.Alloc().ID, ar1)
   154  	gc.MarkForCollection(ar2.Alloc().ID, ar2)
   155  
   156  	// Exit the alloc runners
   157  	exitAllocRunner(ar1, ar2)
   158  
   159  	gc.Collect(ar1.Alloc().ID)
   160  	gcAlloc := gc.allocRunners.Pop()
   161  	if gcAlloc == nil || gcAlloc.allocRunner != ar2 {
   162  		t.Fatalf("bad gcAlloc: %v", gcAlloc)
   163  	}
   164  }
   165  
   166  func TestAllocGarbageCollector_CollectAll(t *testing.T) {
   167  	t.Parallel()
   168  	logger := testlog.HCLogger(t)
   169  	gc := NewAllocGarbageCollector(logger, &MockStatsCollector{}, &MockAllocCounter{}, gcConfig())
   170  
   171  	ar1, cleanup1 := allocrunner.TestAllocRunnerFromAlloc(t, mock.Alloc())
   172  	defer cleanup1()
   173  	ar2, cleanup2 := allocrunner.TestAllocRunnerFromAlloc(t, mock.Alloc())
   174  	defer cleanup2()
   175  
   176  	gc.MarkForCollection(ar1.Alloc().ID, ar1)
   177  	gc.MarkForCollection(ar2.Alloc().ID, ar2)
   178  
   179  	gc.CollectAll()
   180  	gcAlloc := gc.allocRunners.Pop()
   181  	if gcAlloc != nil {
   182  		t.Fatalf("bad gcAlloc: %v", gcAlloc)
   183  	}
   184  }
   185  
   186  func TestAllocGarbageCollector_MakeRoomForAllocations_EnoughSpace(t *testing.T) {
   187  	t.Parallel()
   188  	logger := testlog.HCLogger(t)
   189  	statsCollector := &MockStatsCollector{}
   190  	conf := gcConfig()
   191  	conf.ReservedDiskMB = 20
   192  	gc := NewAllocGarbageCollector(logger, statsCollector, &MockAllocCounter{}, conf)
   193  
   194  	ar1, cleanup1 := allocrunner.TestAllocRunnerFromAlloc(t, mock.Alloc())
   195  	defer cleanup1()
   196  	ar2, cleanup2 := allocrunner.TestAllocRunnerFromAlloc(t, mock.Alloc())
   197  	defer cleanup2()
   198  
   199  	go ar1.Run()
   200  	go ar2.Run()
   201  
   202  	gc.MarkForCollection(ar1.Alloc().ID, ar1)
   203  	gc.MarkForCollection(ar2.Alloc().ID, ar2)
   204  
   205  	// Exit the alloc runners
   206  	exitAllocRunner(ar1, ar2)
   207  
   208  	// Make stats collector report 200MB free out of which 20MB is reserved
   209  	statsCollector.availableValues = []uint64{200 * MB}
   210  	statsCollector.usedPercents = []float64{0}
   211  	statsCollector.inodePercents = []float64{0}
   212  
   213  	alloc := mock.Alloc()
   214  	alloc.AllocatedResources.Shared.DiskMB = 150
   215  	if err := gc.MakeRoomFor([]*structs.Allocation{alloc}); err != nil {
   216  		t.Fatalf("err: %v", err)
   217  	}
   218  
   219  	// When we have enough disk available and don't need to do any GC so we
   220  	// should have two ARs in the GC queue
   221  	for i := 0; i < 2; i++ {
   222  		if gcAlloc := gc.allocRunners.Pop(); gcAlloc == nil {
   223  			t.Fatalf("err: %v", gcAlloc)
   224  		}
   225  	}
   226  }
   227  
   228  func TestAllocGarbageCollector_MakeRoomForAllocations_GC_Partial(t *testing.T) {
   229  	t.Parallel()
   230  	logger := testlog.HCLogger(t)
   231  	statsCollector := &MockStatsCollector{}
   232  	conf := gcConfig()
   233  	conf.ReservedDiskMB = 20
   234  	gc := NewAllocGarbageCollector(logger, statsCollector, &MockAllocCounter{}, conf)
   235  
   236  	ar1, cleanup1 := allocrunner.TestAllocRunnerFromAlloc(t, mock.Alloc())
   237  	defer cleanup1()
   238  	ar2, cleanup2 := allocrunner.TestAllocRunnerFromAlloc(t, mock.Alloc())
   239  	defer cleanup2()
   240  
   241  	go ar1.Run()
   242  	go ar2.Run()
   243  
   244  	gc.MarkForCollection(ar1.Alloc().ID, ar1)
   245  	gc.MarkForCollection(ar2.Alloc().ID, ar2)
   246  
   247  	// Exit the alloc runners
   248  	exitAllocRunner(ar1, ar2)
   249  
   250  	// Make stats collector report 80MB and 175MB free in subsequent calls
   251  	statsCollector.availableValues = []uint64{80 * MB, 80 * MB, 175 * MB}
   252  	statsCollector.usedPercents = []float64{0, 0, 0}
   253  	statsCollector.inodePercents = []float64{0, 0, 0}
   254  
   255  	alloc := mock.Alloc()
   256  	alloc.AllocatedResources.Shared.DiskMB = 150
   257  	if err := gc.MakeRoomFor([]*structs.Allocation{alloc}); err != nil {
   258  		t.Fatalf("err: %v", err)
   259  	}
   260  
   261  	// We should be GC-ing one alloc
   262  	if gcAlloc := gc.allocRunners.Pop(); gcAlloc == nil {
   263  		t.Fatalf("err: %v", gcAlloc)
   264  	}
   265  
   266  	if gcAlloc := gc.allocRunners.Pop(); gcAlloc != nil {
   267  		t.Fatalf("gcAlloc: %v", gcAlloc)
   268  	}
   269  }
   270  
   271  func TestAllocGarbageCollector_MakeRoomForAllocations_GC_All(t *testing.T) {
   272  	t.Parallel()
   273  	logger := testlog.HCLogger(t)
   274  	statsCollector := &MockStatsCollector{}
   275  	conf := gcConfig()
   276  	conf.ReservedDiskMB = 20
   277  	gc := NewAllocGarbageCollector(logger, statsCollector, &MockAllocCounter{}, conf)
   278  
   279  	ar1, cleanup1 := allocrunner.TestAllocRunnerFromAlloc(t, mock.Alloc())
   280  	defer cleanup1()
   281  	ar2, cleanup2 := allocrunner.TestAllocRunnerFromAlloc(t, mock.Alloc())
   282  	defer cleanup2()
   283  
   284  	go ar1.Run()
   285  	go ar2.Run()
   286  
   287  	gc.MarkForCollection(ar1.Alloc().ID, ar1)
   288  	gc.MarkForCollection(ar2.Alloc().ID, ar2)
   289  
   290  	// Exit the alloc runners
   291  	exitAllocRunner(ar1, ar2)
   292  
   293  	// Make stats collector report 80MB and 95MB free in subsequent calls
   294  	statsCollector.availableValues = []uint64{80 * MB, 80 * MB, 95 * MB}
   295  	statsCollector.usedPercents = []float64{0, 0, 0}
   296  	statsCollector.inodePercents = []float64{0, 0, 0}
   297  
   298  	alloc := mock.Alloc()
   299  	alloc.AllocatedResources.Shared.DiskMB = 150
   300  	if err := gc.MakeRoomFor([]*structs.Allocation{alloc}); err != nil {
   301  		t.Fatalf("err: %v", err)
   302  	}
   303  
   304  	// We should be GC-ing all the alloc runners
   305  	if gcAlloc := gc.allocRunners.Pop(); gcAlloc != nil {
   306  		t.Fatalf("gcAlloc: %v", gcAlloc)
   307  	}
   308  }
   309  
   310  func TestAllocGarbageCollector_MakeRoomForAllocations_GC_Fallback(t *testing.T) {
   311  	t.Parallel()
   312  	logger := testlog.HCLogger(t)
   313  	statsCollector := &MockStatsCollector{}
   314  	conf := gcConfig()
   315  	conf.ReservedDiskMB = 20
   316  	gc := NewAllocGarbageCollector(logger, statsCollector, &MockAllocCounter{}, conf)
   317  
   318  	ar1, cleanup1 := allocrunner.TestAllocRunnerFromAlloc(t, mock.Alloc())
   319  	cleanup1()
   320  	ar2, cleanup2 := allocrunner.TestAllocRunnerFromAlloc(t, mock.Alloc())
   321  	cleanup2()
   322  
   323  	go ar1.Run()
   324  	go ar2.Run()
   325  
   326  	gc.MarkForCollection(ar1.Alloc().ID, ar1)
   327  	gc.MarkForCollection(ar2.Alloc().ID, ar2)
   328  
   329  	// Exit the alloc runners
   330  	exitAllocRunner(ar1, ar2)
   331  
   332  	alloc := mock.Alloc()
   333  	alloc.AllocatedResources.Shared.DiskMB = 150
   334  	if err := gc.MakeRoomFor([]*structs.Allocation{alloc}); err != nil {
   335  		t.Fatalf("err: %v", err)
   336  	}
   337  
   338  	// We should be GC-ing one alloc
   339  	if gcAlloc := gc.allocRunners.Pop(); gcAlloc == nil {
   340  		t.Fatalf("err: %v", gcAlloc)
   341  	}
   342  
   343  	if gcAlloc := gc.allocRunners.Pop(); gcAlloc != nil {
   344  		t.Fatalf("gcAlloc: %v", gcAlloc)
   345  	}
   346  }
   347  
   348  // TestAllocGarbageCollector_MakeRoomFor_MaxAllocs asserts that when making room for new
   349  // allocs, terminal allocs are GC'd until old_allocs + new_allocs <= limit
   350  func TestAllocGarbageCollector_MakeRoomFor_MaxAllocs(t *testing.T) {
   351  	const maxAllocs = 6
   352  	require := require.New(t)
   353  
   354  	server, serverAddr, cleanupS := testServer(t, nil)
   355  	defer cleanupS()
   356  	testutil.WaitForLeader(t, server.RPC)
   357  
   358  	client, cleanup := TestClient(t, func(c *config.Config) {
   359  		c.GCMaxAllocs = maxAllocs
   360  		c.GCDiskUsageThreshold = 100
   361  		c.GCInodeUsageThreshold = 100
   362  		c.GCParallelDestroys = 1
   363  		c.GCInterval = time.Hour
   364  		c.RPCHandler = server
   365  		c.Servers = []string{serverAddr}
   366  		c.ConsulConfig.ClientAutoJoin = new(bool)
   367  	})
   368  	defer cleanup()
   369  	waitTilNodeReady(client, t)
   370  
   371  	job := mock.Job()
   372  	job.TaskGroups[0].Count = 1
   373  	job.TaskGroups[0].Tasks[0].Driver = "mock_driver"
   374  	job.TaskGroups[0].Tasks[0].Config = map[string]interface{}{
   375  		"run_for": "30s",
   376  	}
   377  
   378  	index := uint64(98)
   379  	nextIndex := func() uint64 {
   380  		index++
   381  		return index
   382  	}
   383  
   384  	upsertJobFn := func(server *nomad.Server, j *structs.Job) {
   385  		state := server.State()
   386  		require.NoError(state.UpsertJob(structs.MsgTypeTestSetup, nextIndex(), j))
   387  		require.NoError(state.UpsertJobSummary(nextIndex(), mock.JobSummary(j.ID)))
   388  	}
   389  
   390  	// Insert the Job
   391  	upsertJobFn(server, job)
   392  
   393  	upsertAllocFn := func(server *nomad.Server, a *structs.Allocation) {
   394  		state := server.State()
   395  		require.NoError(state.UpsertAllocs(structs.MsgTypeTestSetup, nextIndex(), []*structs.Allocation{a}))
   396  	}
   397  
   398  	upsertNewAllocFn := func(server *nomad.Server, j *structs.Job) *structs.Allocation {
   399  		alloc := mock.Alloc()
   400  		alloc.Job = j
   401  		alloc.JobID = j.ID
   402  		alloc.NodeID = client.NodeID()
   403  
   404  		upsertAllocFn(server, alloc)
   405  
   406  		return alloc.Copy()
   407  	}
   408  
   409  	var allocations []*structs.Allocation
   410  
   411  	// Fill the node with allocations
   412  	for i := 0; i < maxAllocs; i++ {
   413  		allocations = append(allocations, upsertNewAllocFn(server, job))
   414  	}
   415  
   416  	// Wait until the allocations are ready
   417  	testutil.WaitForResult(func() (bool, error) {
   418  		ar := len(client.getAllocRunners())
   419  
   420  		return ar == maxAllocs, fmt.Errorf("Expected %d allocs, got %d", maxAllocs, ar)
   421  	}, func(err error) {
   422  		t.Fatalf("Allocs did not start: %v", err)
   423  	})
   424  
   425  	// Mark the first three as terminal
   426  	for i := 0; i < 3; i++ {
   427  		allocations[i].DesiredStatus = structs.AllocDesiredStatusStop
   428  		upsertAllocFn(server, allocations[i].Copy())
   429  	}
   430  
   431  	// Wait until the allocations are stopped
   432  	testutil.WaitForResult(func() (bool, error) {
   433  		ar := client.getAllocRunners()
   434  		stopped := 0
   435  		for _, r := range ar {
   436  			if r.Alloc().TerminalStatus() {
   437  				stopped++
   438  			}
   439  		}
   440  
   441  		return stopped == 3, fmt.Errorf("Expected %d terminal allocs, got %d", 3, stopped)
   442  	}, func(err error) {
   443  		t.Fatalf("Allocs did not terminate: %v", err)
   444  	})
   445  
   446  	// Upsert a new allocation
   447  	// This does not get appended to `allocations` as we do not use them again.
   448  	upsertNewAllocFn(server, job)
   449  
   450  	// A single allocation should be GC'd
   451  	testutil.WaitForResult(func() (bool, error) {
   452  		ar := client.getAllocRunners()
   453  		destroyed := 0
   454  		for _, r := range ar {
   455  			if r.IsDestroyed() {
   456  				destroyed++
   457  			}
   458  		}
   459  
   460  		return destroyed == 1, fmt.Errorf("Expected %d gc'd ars, got %d", 1, destroyed)
   461  	}, func(err error) {
   462  		t.Fatalf("Allocs did not get GC'd: %v", err)
   463  	})
   464  
   465  	// Upsert a new allocation
   466  	// This does not get appended to `allocations` as we do not use them again.
   467  	upsertNewAllocFn(server, job)
   468  
   469  	// 2 allocations should be GC'd
   470  	testutil.WaitForResult(func() (bool, error) {
   471  		ar := client.getAllocRunners()
   472  		destroyed := 0
   473  		for _, r := range ar {
   474  			if r.IsDestroyed() {
   475  				destroyed++
   476  			}
   477  		}
   478  
   479  		return destroyed == 2, fmt.Errorf("Expected %d gc'd ars, got %d", 2, destroyed)
   480  	}, func(err error) {
   481  		t.Fatalf("Allocs did not get GC'd: %v", err)
   482  	})
   483  
   484  	// check that all 8 get run eventually
   485  	testutil.WaitForResult(func() (bool, error) {
   486  		ar := client.getAllocRunners()
   487  		if len(ar) != 8 {
   488  			return false, fmt.Errorf("expected 8 ARs, found %d: %v", len(ar), ar)
   489  		}
   490  		return true, nil
   491  	}, func(err error) {
   492  		require.NoError(err)
   493  	})
   494  }
   495  
   496  func TestAllocGarbageCollector_UsageBelowThreshold(t *testing.T) {
   497  	t.Parallel()
   498  	logger := testlog.HCLogger(t)
   499  	statsCollector := &MockStatsCollector{}
   500  	conf := gcConfig()
   501  	conf.ReservedDiskMB = 20
   502  	gc := NewAllocGarbageCollector(logger, statsCollector, &MockAllocCounter{}, conf)
   503  
   504  	ar1, cleanup1 := allocrunner.TestAllocRunnerFromAlloc(t, mock.Alloc())
   505  	defer cleanup1()
   506  	ar2, cleanup2 := allocrunner.TestAllocRunnerFromAlloc(t, mock.Alloc())
   507  	defer cleanup2()
   508  
   509  	go ar1.Run()
   510  	go ar2.Run()
   511  
   512  	gc.MarkForCollection(ar1.Alloc().ID, ar1)
   513  	gc.MarkForCollection(ar2.Alloc().ID, ar2)
   514  
   515  	// Exit the alloc runners
   516  	exitAllocRunner(ar1, ar2)
   517  
   518  	statsCollector.availableValues = []uint64{1000}
   519  	statsCollector.usedPercents = []float64{20}
   520  	statsCollector.inodePercents = []float64{10}
   521  
   522  	if err := gc.keepUsageBelowThreshold(); err != nil {
   523  		t.Fatalf("err: %v", err)
   524  	}
   525  
   526  	// We shouldn't GC any of the allocs since the used percent values are below
   527  	// threshold
   528  	for i := 0; i < 2; i++ {
   529  		if gcAlloc := gc.allocRunners.Pop(); gcAlloc == nil {
   530  			t.Fatalf("err: %v", gcAlloc)
   531  		}
   532  	}
   533  }
   534  
   535  func TestAllocGarbageCollector_UsedPercentThreshold(t *testing.T) {
   536  	t.Parallel()
   537  	logger := testlog.HCLogger(t)
   538  	statsCollector := &MockStatsCollector{}
   539  	conf := gcConfig()
   540  	conf.ReservedDiskMB = 20
   541  	gc := NewAllocGarbageCollector(logger, statsCollector, &MockAllocCounter{}, conf)
   542  
   543  	ar1, cleanup1 := allocrunner.TestAllocRunnerFromAlloc(t, mock.Alloc())
   544  	defer cleanup1()
   545  	ar2, cleanup2 := allocrunner.TestAllocRunnerFromAlloc(t, mock.Alloc())
   546  	defer cleanup2()
   547  
   548  	go ar1.Run()
   549  	go ar2.Run()
   550  
   551  	gc.MarkForCollection(ar1.Alloc().ID, ar1)
   552  	gc.MarkForCollection(ar2.Alloc().ID, ar2)
   553  
   554  	// Exit the alloc runners
   555  	exitAllocRunner(ar1, ar2)
   556  
   557  	statsCollector.availableValues = []uint64{1000, 800}
   558  	statsCollector.usedPercents = []float64{85, 60}
   559  	statsCollector.inodePercents = []float64{50, 30}
   560  
   561  	if err := gc.keepUsageBelowThreshold(); err != nil {
   562  		t.Fatalf("err: %v", err)
   563  	}
   564  
   565  	// We should be GC-ing only one of the alloc runners since the second time
   566  	// used percent returns a number below threshold.
   567  	if gcAlloc := gc.allocRunners.Pop(); gcAlloc == nil {
   568  		t.Fatalf("err: %v", gcAlloc)
   569  	}
   570  
   571  	if gcAlloc := gc.allocRunners.Pop(); gcAlloc != nil {
   572  		t.Fatalf("gcAlloc: %v", gcAlloc)
   573  	}
   574  }