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 }