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")