@@ -27,11 +27,13 @@ import (
2727 "fmt"
2828 "math/rand"
2929 "reflect"
30+ "sync"
31+ "sync/atomic"
3032 "testing"
3133 "time"
3234
3335 "github.com/ServiceWeaver/weaver/internal/reflection"
34- "github.com/ServiceWeaver/weaver/runtime"
36+ swruntime "github.com/ServiceWeaver/weaver/runtime"
3537 "github.com/ServiceWeaver/weaver/runtime/codegen"
3638 "github.com/ServiceWeaver/weaver/runtime/protos"
3739)
@@ -231,7 +233,7 @@ func New(t *testing.T, x Workload, opts Options) *Simulator {
231233 app := & protos.AppConfig {}
232234 if opts .Config != "" {
233235 var err error
234- app , err = runtime .ParseConfig ("" , opts .Config , codegen .ComponentConfigValidator )
236+ app , err = swruntime .ParseConfig ("" , opts .Config , codegen .ComponentConfigValidator )
235237 if err != nil {
236238 t .Fatalf ("sim.New: parse config: %v" , err )
237239 }
@@ -313,54 +315,107 @@ func validateWorkload(t reflect.Type) error {
313315
314316// Run runs simulations for the provided duration.
315317func (s * Simulator ) Run (duration time.Duration ) Results {
318+ // TODO(mwhittaker): Use a smarter algorithm to sweep over hyperparameters.
319+ // TODO(mwhittaker): Read and run graveyard entries.
316320 start := time .Now ()
317321 deadline := start .Add (duration )
318322 ctx , cancel := context .WithDeadline (context .Background (), deadline )
319323 defer cancel ()
320324
321- // TODO(mwhittaker): Use a smarter algorithm to sweep over hyperparameters.
322- // TODO(mwhittaker): Run simulations in multiple goroutines.
323- // TODO(mwhittaker): Read and run graveyard entries.
324- seed := time .Now ().UnixNano ()
325- count := 0
326- for numOps := 1 ; ; numOps ++ {
327- for _ , failureRate := range []float64 {0.0 , 0.01 , 0.05 , 0.1 } {
328- for _ , yieldRate := range []float64 {0.0 , 0.1 , 0.2 , 0.3 , 0.4 , 0.5 , 0.6 , 0.7 , 0.8 , 0.9 , 1.0 } {
329- for i := 0 ; i < 10 ; i ++ {
330- if time .Now ().After (deadline ) {
331- return Results {
332- NumSimulations : count ,
333- Duration : time .Since (start ),
334- }
325+ // Spawn n concurrent simulators. The simulators read options from the opts
326+ // channel and write errors and failed results to the errs and
327+ // failedResults channels. Simulation ends when we encounter an error or
328+ // successfully find an invariant violation.
329+ //
330+ // TODO(mwhittaker): Optimize things and pick a smarter value of n.
331+ const n = 1000
332+ opts := make (chan options , n )
333+ errs := make (chan error , n )
334+ failedResults := make (chan Results , n )
335+ done := sync.WaitGroup {}
336+ numSimulations := int64 (0 )
337+
338+ // Spawn n simulating goroutines that read from the opts channel.
339+ done .Add (n )
340+ for i := 0 ; i < n ; i ++ {
341+ go func () {
342+ defer done .Done ()
343+ for {
344+ select {
345+ case <- ctx .Done ():
346+ return
347+ case o := <- opts :
348+ atomic .AddInt64 (& numSimulations , 1 )
349+ switch r , err := s .runOne (ctx , o ); {
350+ case err != nil && err == ctx .Err ():
351+ // The simulation was cancelled because the deadline
352+ // was met. Stop executing simulations.
353+ return
354+ case err != nil :
355+ // The simulation failed to execute properly. Abort.
356+ errs <- err
357+ return
358+ case r .Err != nil :
359+ // The simulation successfully found an invariant
360+ // violation. Return the result and stop execution.
361+ failedResults <- r
362+ return
363+ default :
364+ // The simulation ran without finding an invariant
365+ // violation. Move on to the next simulation.
335366 }
367+ }
368+ }
369+ }()
370+ }
336371
337- seed ++
338- count ++
339- opts := options {
340- Seed : seed ,
341- NumOps : numOps ,
342- NumReplicas : 1 ,
343- FailureRate : failureRate ,
344- YieldRate : yieldRate ,
345- }
346- results , err := s .runOne (ctx , opts )
347- if err != nil && err == ctx .Err () {
348- return Results {
349- NumSimulations : count ,
350- Duration : time .Since (start ),
372+ // Spawn a goroutine that writes to the opts channel.
373+ done .Add (1 )
374+ go func () {
375+ defer done .Done ()
376+ seed := time .Now ().UnixNano ()
377+ for numOps := 1 ; ; numOps ++ {
378+ for _ , failureRate := range []float64 {0.0 , 0.01 , 0.05 , 0.1 } {
379+ for _ , yieldRate := range []float64 {0.0 , 0.1 , 0.2 , 0.3 , 0.4 , 0.5 , 0.6 , 0.7 , 0.8 , 0.9 , 1.0 } {
380+ for i := 0 ; i < 10 ; i ++ {
381+ seed ++
382+ o := options {
383+ Seed : seed ,
384+ NumOps : numOps ,
385+ NumReplicas : 1 ,
386+ FailureRate : failureRate ,
387+ YieldRate : yieldRate ,
388+ }
389+ select {
390+ case <- ctx .Done ():
391+ return
392+ case opts <- o :
351393 }
352- }
353- if err != nil {
354- s .t .Fatal (err )
355- }
356- if results .Err != nil {
357- results .NumSimulations = count
358- results .Duration = time .Since (start )
359- return results
360394 }
361395 }
362396 }
363397 }
398+ }()
399+
400+ select {
401+ case err := <- errs :
402+ // A simulation failed to execute properly.
403+ s .t .Fatal (err )
404+ return Results {}
405+ case r := <- failedResults :
406+ // A simulation successfully found an invariant violation.
407+ cancel ()
408+ done .Wait ()
409+ r .NumSimulations = int (numSimulations )
410+ r .Duration = time .Since (start )
411+ return r
412+ case <- ctx .Done ():
413+ // We hit our deadline. All simulations passed.
414+ done .Wait ()
415+ return Results {
416+ NumSimulations : int (numSimulations ),
417+ Duration : time .Since (start ),
418+ }
364419 }
365420}
366421
0 commit comments