github.com/google/skylark@v0.0.0-20181101142754-a5f7082aabed/example_test.go (about) 1 // Copyright 2017 The Bazel 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 skylark_test 6 7 import ( 8 "fmt" 9 "log" 10 "sort" 11 "strings" 12 "sync" 13 "sync/atomic" 14 "unsafe" 15 16 "github.com/google/skylark" 17 ) 18 19 // ExampleExecFile demonstrates a simple embedding 20 // of the Skylark interpreter into a Go program. 21 func ExampleExecFile() { 22 const data = ` 23 print(greeting + ", world") 24 25 squares = [x*x for x in range(10)] 26 ` 27 28 thread := &skylark.Thread{ 29 Print: func(_ *skylark.Thread, msg string) { fmt.Println(msg) }, 30 } 31 predeclared := skylark.StringDict{ 32 "greeting": skylark.String("hello"), 33 } 34 globals, err := skylark.ExecFile(thread, "apparent/filename.sky", data, predeclared) 35 if err != nil { 36 if evalErr, ok := err.(*skylark.EvalError); ok { 37 log.Fatal(evalErr.Backtrace()) 38 } 39 log.Fatal(err) 40 } 41 42 // Print the global environment. 43 var names []string 44 for name := range globals { 45 names = append(names, name) 46 } 47 sort.Strings(names) 48 fmt.Println("\nGlobals:") 49 for _, name := range names { 50 v := globals[name] 51 fmt.Printf("%s (%s) = %s\n", name, v.Type(), v.String()) 52 } 53 54 // Output: 55 // hello, world 56 // 57 // Globals: 58 // squares (list) = [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] 59 } 60 61 // ExampleThread_Load_sequential demonstrates a simple caching 62 // implementation of 'load' that works sequentially. 63 func ExampleThread_Load_sequential() { 64 fakeFilesystem := map[string]string{ 65 "c.sky": `load("b.sky", "b"); c = b + "!"`, 66 "b.sky": `load("a.sky", "a"); b = a + ", world"`, 67 "a.sky": `a = "Hello"`, 68 } 69 70 type entry struct { 71 globals skylark.StringDict 72 err error 73 } 74 75 cache := make(map[string]*entry) 76 77 var load func(_ *skylark.Thread, module string) (skylark.StringDict, error) 78 load = func(_ *skylark.Thread, module string) (skylark.StringDict, error) { 79 e, ok := cache[module] 80 if e == nil { 81 if ok { 82 // request for package whose loading is in progress 83 return nil, fmt.Errorf("cycle in load graph") 84 } 85 86 // Add a placeholder to indicate "load in progress". 87 cache[module] = nil 88 89 // Load and initialize the module in a new thread. 90 data := fakeFilesystem[module] 91 thread := &skylark.Thread{Load: load} 92 globals, err := skylark.ExecFile(thread, module, data, nil) 93 e = &entry{globals, err} 94 95 // Update the cache. 96 cache[module] = e 97 } 98 return e.globals, e.err 99 } 100 101 thread := &skylark.Thread{Load: load} 102 globals, err := load(thread, "c.sky") 103 if err != nil { 104 log.Fatal(err) 105 } 106 fmt.Println(globals["c"]) 107 108 // Output: 109 // "Hello, world!" 110 } 111 112 // ExampleThread_Load_parallel demonstrates a parallel implementation 113 // of 'load' with caching, duplicate suppression, and cycle detection. 114 func ExampleThread_Load_parallel() { 115 cache := &cache{ 116 cache: make(map[string]*entry), 117 fakeFilesystem: map[string]string{ 118 "c.sky": `load("a.sky", "a"); c = a * 2`, 119 "b.sky": `load("a.sky", "a"); b = a * 3`, 120 "a.sky": `a = 1; print("loaded a")`, 121 }, 122 } 123 124 // We load modules b and c in parallel by concurrent calls to 125 // cache.Load. Both of them load module a, but a is executed 126 // only once, as witnessed by the sole output of its print 127 // statement. 128 129 ch := make(chan string) 130 for _, name := range []string{"b", "c"} { 131 go func(name string) { 132 globals, err := cache.Load(name + ".sky") 133 if err != nil { 134 log.Fatal(err) 135 } 136 ch <- fmt.Sprintf("%s = %s", name, globals[name]) 137 }(name) 138 } 139 got := []string{<-ch, <-ch} 140 sort.Strings(got) 141 fmt.Println(strings.Join(got, "\n")) 142 143 // Output: 144 // loaded a 145 // b = 3 146 // c = 2 147 } 148 149 // ExampleThread_Load_parallelCycle demonstrates detection 150 // of cycles during parallel loading. 151 func ExampleThread_Load_parallelCycle() { 152 cache := &cache{ 153 cache: make(map[string]*entry), 154 fakeFilesystem: map[string]string{ 155 "c.sky": `load("b.sky", "b"); c = b * 2`, 156 "b.sky": `load("a.sky", "a"); b = a * 3`, 157 "a.sky": `load("c.sky", "c"); a = c * 5; print("loaded a")`, 158 }, 159 } 160 161 ch := make(chan string) 162 for _, name := range "bc" { 163 name := string(name) 164 go func() { 165 _, err := cache.Load(name + ".sky") 166 if err == nil { 167 log.Fatalf("Load of %s.sky succeeded unexpectedly", name) 168 } 169 ch <- err.Error() 170 }() 171 } 172 got := []string{<-ch, <-ch} 173 sort.Strings(got) 174 fmt.Println(strings.Join(got, "\n")) 175 176 // Output: 177 // cannot load a.sky: cannot load c.sky: cycle in load graph 178 // cannot load b.sky: cannot load a.sky: cannot load c.sky: cycle in load graph 179 } 180 181 // cache is a concurrency-safe, duplicate-suppressing, 182 // non-blocking cache of the doLoad function. 183 // See Section 9.7 of gopl.io for an explanation of this structure. 184 // It also features online deadlock (load cycle) detection. 185 type cache struct { 186 cacheMu sync.Mutex 187 cache map[string]*entry 188 189 fakeFilesystem map[string]string 190 } 191 192 type entry struct { 193 owner unsafe.Pointer // a *cycleChecker; see cycleCheck 194 globals skylark.StringDict 195 err error 196 ready chan struct{} 197 } 198 199 func (c *cache) Load(module string) (skylark.StringDict, error) { 200 return c.get(new(cycleChecker), module) 201 } 202 203 // get loads and returns an entry (if not already loaded). 204 func (c *cache) get(cc *cycleChecker, module string) (skylark.StringDict, error) { 205 c.cacheMu.Lock() 206 e := c.cache[module] 207 if e != nil { 208 c.cacheMu.Unlock() 209 // Some other goroutine is getting this module. 210 // Wait for it to become ready. 211 212 // Detect load cycles to avoid deadlocks. 213 if err := cycleCheck(e, cc); err != nil { 214 return nil, err 215 } 216 217 cc.setWaitsFor(e) 218 <-e.ready 219 cc.setWaitsFor(nil) 220 } else { 221 // First request for this module. 222 e = &entry{ready: make(chan struct{})} 223 c.cache[module] = e 224 c.cacheMu.Unlock() 225 226 e.setOwner(cc) 227 e.globals, e.err = c.doLoad(cc, module) 228 e.setOwner(nil) 229 230 // Broadcast that the entry is now ready. 231 close(e.ready) 232 } 233 return e.globals, e.err 234 } 235 236 func (c *cache) doLoad(cc *cycleChecker, module string) (skylark.StringDict, error) { 237 thread := &skylark.Thread{ 238 Print: func(_ *skylark.Thread, msg string) { fmt.Println(msg) }, 239 Load: func(_ *skylark.Thread, module string) (skylark.StringDict, error) { 240 // Tunnel the cycle-checker state for this "thread of loading". 241 return c.get(cc, module) 242 }, 243 } 244 data := c.fakeFilesystem[module] 245 return skylark.ExecFile(thread, module, data, nil) 246 } 247 248 // -- concurrent cycle checking -- 249 250 // A cycleChecker is used for concurrent deadlock detection. 251 // Each top-level call to Load creates its own cycleChecker, 252 // which is passed to all recursive calls it makes. 253 // It corresponds to a logical thread in the deadlock detection literature. 254 type cycleChecker struct { 255 waitsFor unsafe.Pointer // an *entry; see cycleCheck 256 } 257 258 func (cc *cycleChecker) setWaitsFor(e *entry) { 259 atomic.StorePointer(&cc.waitsFor, unsafe.Pointer(e)) 260 } 261 262 func (e *entry) setOwner(cc *cycleChecker) { 263 atomic.StorePointer(&e.owner, unsafe.Pointer(cc)) 264 } 265 266 // cycleCheck reports whether there is a path in the waits-for graph 267 // from resource 'e' to thread 'me'. 268 // 269 // The waits-for graph (WFG) is a bipartite graph whose nodes are 270 // alternately of type entry and cycleChecker. Each node has at most 271 // one outgoing edge. An entry has an "owner" edge to a cycleChecker 272 // while it is being readied by that cycleChecker, and a cycleChecker 273 // has a "waits-for" edge to an entry while it is waiting for that entry 274 // to become ready. 275 // 276 // Before adding a waits-for edge, the cache checks whether the new edge 277 // would form a cycle. If so, this indicates that the load graph is 278 // cyclic and that the following wait operation would deadlock. 279 func cycleCheck(e *entry, me *cycleChecker) error { 280 for e != nil { 281 cc := (*cycleChecker)(atomic.LoadPointer(&e.owner)) 282 if cc == nil { 283 break 284 } 285 if cc == me { 286 return fmt.Errorf("cycle in load graph") 287 } 288 e = (*entry)(atomic.LoadPointer(&cc.waitsFor)) 289 } 290 return nil 291 }