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