github.com/aclements/go-misc@v0.0.0-20240129233631-2f6ede80790c/go-weave/amb/run.go (about)

     1  // Copyright 2016 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package amb
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"os"
    11  	"runtime"
    12  )
    13  
    14  // A Strategy describes how to explore a space of ambiguous values.
    15  // Such a space can be viewed as a tree, where a call to Amb
    16  // introduces a node with fan-out n and a call to Next terminates a
    17  // path.
    18  type Strategy interface {
    19  	// Amb returns an "ambiguous" value in the range [0, n). If
    20  	// the current path cannot be continued (for example, it's
    21  	// reached a maximum depth), it returns 0, false.
    22  	//
    23  	// The first call to Amb after constructing a Strategy or
    24  	// calling Next always starts at the root of the tree.
    25  	//
    26  	// Amb may panic with ErrNondeterminism if it detects that the
    27  	// application is behaving non-deterministically (for example,
    28  	// when replaying a previously explored path, the value of n
    29  	// is different from when Amb was called during a previous
    30  	// exploration of this path). This is best-effort and some
    31  	// strategies may not be able to detect this.
    32  	Amb(n int) (int, bool)
    33  
    34  	// Next terminates the current path. If there are no more
    35  	// paths to explore, Next returns false. A Strategy is not
    36  	// required to ever return false (for example, a randomized
    37  	// strategy may not know that it's explored the entire space).
    38  	Next() bool
    39  
    40  	// Reset resets the state of this Strategy to the point where
    41  	// no paths have been explored.
    42  	Reset()
    43  }
    44  
    45  // DefaultMaxDepth is the default maximum tree depth if it is
    46  // unspecified.
    47  var DefaultMaxDepth = 100
    48  
    49  // Scheduler uses a Strategy to execute a function repeatedly at
    50  // different points in a space of ambiguous values.
    51  type Scheduler struct {
    52  	// Strategy specifies the strategy for exploring the execution
    53  	// space.
    54  	Strategy Strategy
    55  
    56  	active bool
    57  }
    58  
    59  var curStrategy Strategy
    60  var curPanic interface{}
    61  
    62  // Run calls root repeatedly at different points in the ambiguous
    63  // value space.
    64  func (s *Scheduler) Run(root func()) {
    65  	if s.active {
    66  		panic("nested Run call")
    67  	}
    68  	s.active = true
    69  	defer func() { s.active = false }()
    70  
    71  	count = 0
    72  	startProgress()
    73  	defer stopProgress()
    74  
    75  	s.Strategy.Reset()
    76  	for {
    77  		s.run1(root)
    78  
    79  		if !s.Strategy.Next() {
    80  			break
    81  		}
    82  		count++
    83  	}
    84  }
    85  
    86  func (s *Scheduler) run1(root func()) {
    87  	defer func() {
    88  		err := recover()
    89  		if err != nil {
    90  			// TODO: Report path.
    91  			fmt.Println("failure:", err)
    92  			var buf []byte
    93  			for i := 1 << 10; i < 1<<20; i *= 2 {
    94  				buf = make([]byte, i)
    95  				buf = buf[:runtime.Stack(buf, false)]
    96  				if len(buf) < i {
    97  					break
    98  				}
    99  			}
   100  			os.Stdout.Write(buf)
   101  		}
   102  	}()
   103  	root()
   104  }
   105  
   106  // Amb returns a value in the range [0, n).
   107  //
   108  // Amb may panic with PathTerminated to indicate an execution path is
   109  // being forcibly terminated by the Strategy. If Amb is called on a
   110  // goroutine other than the goroutine that called Run, the goroutine
   111  // is responsible for recovering PathTerminated panics and forwarding
   112  // the panic to the goroutine that called Run.
   113  func (s *Scheduler) Amb(n int) int {
   114  	x, ok := s.Strategy.Amb(n)
   115  	if !ok {
   116  		panic(PathTerminated)
   117  	}
   118  	return x
   119  }
   120  
   121  // PathTerminated is panicked by Scheduler.Amb to indicate that Run
   122  // should continue to the next path.
   123  var PathTerminated = errors.New("path terminated")