github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/vm/dispatcher/pool_test.go (about)

     1  // Copyright 2024 syzkaller project authors. All rights reserved.
     2  // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
     3  
     4  package dispatcher
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"runtime"
    10  	"sync"
    11  	"sync/atomic"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/stretchr/testify/assert"
    16  )
    17  
    18  func TestPoolDefault(t *testing.T) {
    19  	count := 3
    20  	pool := makePool(count)
    21  
    22  	mgr := NewPool[*testInstance](
    23  		count,
    24  		func(_ context.Context, idx int) (*testInstance, error) {
    25  			pool[idx].reset()
    26  			return &pool[idx], nil
    27  		},
    28  		func(ctx context.Context, inst *testInstance, _ UpdateInfo) {
    29  			pool[inst.Index()].run(ctx)
    30  		},
    31  	)
    32  
    33  	ctx, cancel := context.WithCancel(context.Background())
    34  	done := make(chan bool)
    35  	go func() {
    36  		mgr.Loop(ctx)
    37  		close(done)
    38  	}()
    39  
    40  	// Eventually all instances are up and busy.
    41  	for i := 0; i < count; i++ {
    42  		pool[i].waitRun()
    43  	}
    44  
    45  	// The pool restarts failed jobs.
    46  	for i := 0; i < 10; i++ {
    47  		pool[0].stopRun()
    48  		pool[2].stopRun()
    49  
    50  		pool[0].waitRun()
    51  		pool[2].waitRun()
    52  	}
    53  
    54  	cancel()
    55  	<-done
    56  }
    57  
    58  func TestPoolSplit(t *testing.T) {
    59  	count := 3
    60  	pool := makePool(count)
    61  	var defaultCount atomic.Int64
    62  
    63  	mgr := NewPool[*testInstance](
    64  		count,
    65  		func(_ context.Context, idx int) (*testInstance, error) {
    66  			pool[idx].reset()
    67  			return &pool[idx], nil
    68  		},
    69  		func(ctx context.Context, inst *testInstance, _ UpdateInfo) {
    70  			defaultCount.Add(1)
    71  			pool[inst.Index()].run(ctx)
    72  			defaultCount.Add(-1)
    73  		},
    74  	)
    75  
    76  	done := make(chan bool)
    77  	ctx, cancel := context.WithCancel(context.Background())
    78  	go func() {
    79  		mgr.Loop(ctx)
    80  		close(done)
    81  	}()
    82  
    83  	startedRuns := make(chan bool)
    84  	stopRuns := make(chan bool)
    85  	job := func(ctx context.Context, _ *testInstance, _ UpdateInfo) {
    86  		startedRuns <- true
    87  		select {
    88  		case <-ctx.Done():
    89  		case <-stopRuns:
    90  		}
    91  	}
    92  	go mgr.Run(ctx, job)
    93  
    94  	// So far, there are no reserved instances.
    95  	for i := 0; i < count; i++ {
    96  		pool[i].waitRun()
    97  	}
    98  
    99  	// Dedicate one instance to the pool.
   100  	mgr.ReserveForRun(1)
   101  
   102  	// The first job must start.
   103  	<-startedRuns
   104  	// Two default jobs are running.
   105  	assert.EqualValues(t, 2, defaultCount.Load())
   106  	stopRuns <- true
   107  
   108  	// Take away the pool instance.
   109  	mgr.ReserveForRun(0)
   110  	// All instances must be busy with the default jobs.
   111  	for i := 0; i < count; i++ {
   112  		pool[i].waitRun()
   113  	}
   114  	assert.EqualValues(t, 3, defaultCount.Load())
   115  
   116  	// Now let's create and finish more jobs.
   117  	for i := 0; i < 10; i++ {
   118  		go mgr.Run(ctx, job)
   119  	}
   120  	mgr.ReserveForRun(2)
   121  	for i := 0; i < 10; i++ {
   122  		<-startedRuns
   123  		stopRuns <- true
   124  	}
   125  
   126  	cancel()
   127  	<-done
   128  }
   129  
   130  func TestPoolStress(t *testing.T) {
   131  	// The test to aid the race detector.
   132  	mgr := NewPool[*nilInstance](
   133  		10,
   134  		func(_ context.Context, idx int) (*nilInstance, error) {
   135  			return &nilInstance{}, nil
   136  		},
   137  		func(ctx context.Context, _ *nilInstance, _ UpdateInfo) {
   138  			<-ctx.Done()
   139  		},
   140  	)
   141  	done := make(chan bool)
   142  	ctx, cancel := context.WithCancel(context.Background())
   143  
   144  	go func() {
   145  		mgr.Loop(ctx)
   146  		close(done)
   147  	}()
   148  	go func() {
   149  		for i := 0; i < 128; i++ {
   150  			mgr.TogglePause(i%2 == 0)
   151  			runtime.Gosched()
   152  		}
   153  	}()
   154  	for i := 0; i < 128; i++ {
   155  		go mgr.Run(ctx, func(ctx context.Context, _ *nilInstance, _ UpdateInfo) {})
   156  		mgr.ReserveForRun(5 + i%5)
   157  	}
   158  
   159  	cancel()
   160  	<-done
   161  }
   162  
   163  func TestPoolNewDefault(t *testing.T) {
   164  	var originalCount atomic.Int64
   165  
   166  	// The test to aid the race detector.
   167  	mgr := NewPool[*nilInstance](
   168  		10,
   169  		func(_ context.Context, idx int) (*nilInstance, error) {
   170  			return &nilInstance{}, nil
   171  		},
   172  		func(ctx context.Context, _ *nilInstance, _ UpdateInfo) {
   173  			originalCount.Add(1)
   174  			<-ctx.Done()
   175  			originalCount.Add(-1)
   176  		},
   177  	)
   178  	done := make(chan bool)
   179  	ctx, cancel := context.WithCancel(context.Background())
   180  	go func() {
   181  		mgr.Loop(ctx)
   182  		close(done)
   183  	}()
   184  
   185  	for originalCount.Load() != 10 {
   186  		time.Sleep(time.Second / 10)
   187  	}
   188  
   189  	var newCount atomic.Int64
   190  	mgr.SetDefault(func(ctx context.Context, _ *nilInstance, _ UpdateInfo) {
   191  		newCount.Add(1)
   192  		<-ctx.Done()
   193  		newCount.Add(-1)
   194  	})
   195  
   196  	for newCount.Load() != 10 {
   197  		time.Sleep(time.Second / 10)
   198  	}
   199  	assert.Equal(t, int64(0), originalCount.Load())
   200  
   201  	cancel()
   202  	<-done
   203  }
   204  
   205  func TestPoolPause(t *testing.T) {
   206  	mgr := NewPool[*nilInstance](
   207  		10,
   208  		func(_ context.Context, idx int) (*nilInstance, error) {
   209  			return &nilInstance{}, nil
   210  		},
   211  		func(ctx context.Context, _ *nilInstance, _ UpdateInfo) {
   212  			t.Fatal("must not be called")
   213  		},
   214  	)
   215  	mgr.ReserveForRun(10)
   216  	mgr.TogglePause(true)
   217  	done := make(chan bool)
   218  	ctx, cancel := context.WithCancel(context.Background())
   219  	go func() {
   220  		mgr.Loop(ctx)
   221  		close(done)
   222  	}()
   223  
   224  	run := make(chan bool, 1)
   225  	go mgr.Run(ctx, func(ctx context.Context, _ *nilInstance, _ UpdateInfo) {
   226  		run <- true
   227  	})
   228  	time.Sleep(10 * time.Millisecond)
   229  	if len(run) != 0 {
   230  		t.Fatalf("job run while paused")
   231  	}
   232  	mgr.TogglePause(false)
   233  	<-run
   234  
   235  	mgr.Run(ctx, func(ctx context.Context, _ *nilInstance, _ UpdateInfo) {})
   236  
   237  	cancel()
   238  	<-done
   239  }
   240  
   241  func TestPoolCancelRun(t *testing.T) {
   242  	// The test to aid the race detector.
   243  	mgr := NewPool[*nilInstance](
   244  		10,
   245  		func(_ context.Context, idx int) (*nilInstance, error) {
   246  			return &nilInstance{}, nil
   247  		},
   248  		func(ctx context.Context, _ *nilInstance, _ UpdateInfo) {
   249  			<-ctx.Done()
   250  		},
   251  	)
   252  	var wg sync.WaitGroup
   253  	wg.Add(1)
   254  	ctx, cancel := context.WithCancel(context.Background())
   255  	go func() {
   256  		mgr.Loop(ctx)
   257  		wg.Done()
   258  	}()
   259  
   260  	mgr.ReserveForRun(2)
   261  
   262  	started := make(chan struct{})
   263  	// Schedule more jobs than could be processed simultaneously.
   264  	for i := 0; i < 15; i++ {
   265  		wg.Add(1)
   266  		go func() {
   267  			defer wg.Done()
   268  			mgr.Run(ctx, func(ctx context.Context, _ *nilInstance, _ UpdateInfo) {
   269  				select {
   270  				case <-ctx.Done():
   271  					return
   272  				case started <- struct{}{}:
   273  				}
   274  				<-ctx.Done()
   275  			})
   276  		}()
   277  	}
   278  
   279  	// Two can be started.
   280  	<-started
   281  	<-started
   282  
   283  	// Now stop the loop and the jbos.
   284  	cancel()
   285  
   286  	// Everything must really stop.
   287  	wg.Wait()
   288  }
   289  
   290  // Check that the loop terminates even if no one reads from the boot error channel.
   291  func TestPoolBootErrors(t *testing.T) {
   292  	var failCount atomic.Int64
   293  
   294  	mgr := NewPool[*testInstance](
   295  		3,
   296  		func(_ context.Context, idx int) (*testInstance, error) {
   297  			failCount.Add(1)
   298  			return nil, fmt.Errorf("boot error")
   299  		},
   300  		func(ctx context.Context, _ *testInstance, _ UpdateInfo) {
   301  			<-ctx.Done()
   302  		},
   303  	)
   304  
   305  	done := make(chan struct{})
   306  	ctx, cancel := context.WithCancel(context.Background())
   307  	go func() {
   308  		mgr.Loop(ctx)
   309  		close(done)
   310  	}()
   311  
   312  	// Wait till the boot error channel saturates.
   313  	for failCount.Load() < bootErrorChanCap {
   314  		time.Sleep(10 * time.Millisecond)
   315  	}
   316  
   317  	// Now terminate the loop.
   318  	cancel()
   319  	<-done
   320  }
   321  
   322  func makePool(count int) []testInstance {
   323  	var ret []testInstance
   324  	for i := 0; i < count; i++ {
   325  		ret = append(ret, testInstance{index: i})
   326  	}
   327  	return ret
   328  }
   329  
   330  type testInstance struct {
   331  	index  int
   332  	hasRun atomic.Bool
   333  	stop   chan bool
   334  }
   335  
   336  func (ti *testInstance) reset() {
   337  	ti.stop = make(chan bool)
   338  	ti.hasRun.Store(false)
   339  }
   340  
   341  func (ti *testInstance) run(ctx context.Context) {
   342  	ti.hasRun.Store(true)
   343  	select {
   344  	case <-ti.stop:
   345  	case <-ctx.Done():
   346  	}
   347  }
   348  
   349  func (ti *testInstance) waitRun() {
   350  	for !ti.hasRun.Load() {
   351  		time.Sleep(10 * time.Millisecond)
   352  	}
   353  }
   354  
   355  func (ti *testInstance) stopRun() {
   356  	close(ti.stop)
   357  	ti.hasRun.Store(false) // make subsequent waitRun() actually wait for the next command.
   358  }
   359  
   360  func (ti *testInstance) Index() int {
   361  	return ti.index
   362  }
   363  
   364  func (ti *testInstance) Close() error {
   365  	return nil
   366  }
   367  
   368  type nilInstance struct {
   369  }
   370  
   371  func (ni *nilInstance) Close() error {
   372  	return nil
   373  }