Skip to content

Commit

Permalink
interp: add eval cancelation by semaphore
Browse files Browse the repository at this point in the history
  • Loading branch information
kortschak authored and traefiker committed Oct 29, 2019
1 parent 75a696a commit 714253c
Show file tree
Hide file tree
Showing 9 changed files with 479 additions and 65 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ generate: gen_all_syscall

tests:
GO111MODULE=off go test -v ./...
GO111MODULE=off go test -race ./interp

.PHONY: check gen_all_syscall gen_tests
91 changes: 91 additions & 0 deletions cmd/yaegi/yaegi_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package main

import (
"bytes"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"testing"
"time"
)

func TestYaegiCmdCancel(t *testing.T) {
tmp, err := ioutil.TempDir("", "yaegi-")
if err != nil {
t.Fatalf("failed to create tmp directory: %v", err)
}
defer func() {
err = os.RemoveAll(tmp)
if err != nil {
t.Errorf("failed to clean up %v: %v", tmp, err)
}
}()

yaegi := filepath.Join(tmp, "yaegi")
build := exec.Command("go", "build", "-race", "-o", yaegi, ".")
err = build.Run()
if err != nil {
t.Fatalf("failed to build yaegi command: %v", err)
}

// Test src must be terminated by a single newline.
tests := []string{
"for {}\n",
"select {}\n",
}
for _, src := range tests {
cmd := exec.Command(yaegi)
in, err := cmd.StdinPipe()
if err != nil {
t.Errorf("failed to get stdin pipe to yaegi command: %v", err)
}
var outBuf, errBuf bytes.Buffer
cmd.Stdout = &outBuf
cmd.Stderr = &errBuf

// https://golang.org/doc/articles/race_detector.html#Options
cmd.Env = []string{`GORACE="halt_on_error=1"`}

err = cmd.Start()
if err != nil {
t.Fatalf("failed to start yaegi command: %v", err)
}

_, err = in.Write([]byte(src))
if err != nil {
t.Errorf("failed pipe test source to yaegi command: %v", err)
}
time.Sleep(100 * time.Millisecond)
err = cmd.Process.Signal(os.Interrupt)
if err != nil {
t.Errorf("failed to send os.Interrupt to yaegi command: %v", err)
}

_, err = in.Write([]byte("1+1\n"))
if err != nil {
t.Errorf("failed to probe race: %v", err)
}
err = in.Close()
if err != nil {
t.Errorf("failed to close stdin pipe: %v", err)
}

err = cmd.Wait()
if err != nil {
if cmd.ProcessState.ExitCode() == 66 { // See race_detector.html article.
t.Errorf("race detected running yaegi command canceling %q: %v", src, err)
if testing.Verbose() {
t.Log(&errBuf)
}
} else {
t.Errorf("error running yaegi command for %q: %v", src, err)
}
continue
}

if outBuf.String() != "context canceled\n2\n" {
t.Errorf("unexpected output: %q", &outBuf)
}
}
}
20 changes: 12 additions & 8 deletions interp/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"go/token"
"reflect"
"strconv"
"sync/atomic"
)

// nkind defines the kind of AST, i.e. the grammar category
Expand Down Expand Up @@ -356,9 +357,9 @@ func (interp *Interpreter) ast(src, name string) (string, *node, error) {
var pkgName string

addChild := func(root **node, anc astNode, pos token.Pos, kind nkind, act action) *node {
interp.nindex++
var i interface{}
n := &node{anc: anc.node, interp: interp, index: interp.nindex, pos: pos, kind: kind, action: act, val: &i, gen: builtin[act]}
nindex := atomic.AddInt64(&interp.nindex, 1)
n := &node{anc: anc.node, interp: interp, index: nindex, pos: pos, kind: kind, action: act, val: &i, gen: builtin[act]}
n.start = n
if anc.node == nil {
*root = n
Expand All @@ -369,15 +370,15 @@ func (interp *Interpreter) ast(src, name string) (string, *node, error) {
if len(ancAst.List)+len(ancAst.Body) == len(anc.node.child) {
// All case clause children are collected.
// Split children in condition and body nodes to desambiguify the AST.
interp.nindex++
body := &node{anc: anc.node, interp: interp, index: interp.nindex, pos: pos, kind: caseBody, action: aNop, val: &i, gen: nop}
nindex = atomic.AddInt64(&interp.nindex, 1)
body := &node{anc: anc.node, interp: interp, index: nindex, pos: pos, kind: caseBody, action: aNop, val: &i, gen: nop}

if ts := anc.node.anc.anc; ts.kind == typeSwitch && ts.child[1].action == aAssign {
// In type switch clause, if a switch guard is assigned, duplicate the switch guard symbol
// in each clause body, so a different guard type can be set in each clause
name := ts.child[1].child[0].ident
interp.nindex++
gn := &node{anc: body, interp: interp, ident: name, index: interp.nindex, pos: pos, kind: identExpr, action: aNop, val: &i, gen: nop}
nindex = atomic.AddInt64(&interp.nindex, 1)
gn := &node{anc: body, interp: interp, ident: name, index: nindex, pos: pos, kind: identExpr, action: aNop, val: &i, gen: nop}
body.child = append(body.child, gn)
}

Expand Down Expand Up @@ -553,6 +554,9 @@ func (interp *Interpreter) ast(src, name string) (string, *node, error) {
case *ast.CommClause:
st.push(addChild(&root, anc, pos, commClause, aNop), nod)

case *ast.CommentGroup:
return false

case *ast.CompositeLit:
st.push(addChild(&root, anc, pos, compositeLitExpr, aCompositeLit), nod)

Expand Down Expand Up @@ -844,9 +848,9 @@ func (s *nodestack) top() astNode {

// dup returns a duplicated node subtree
func (interp *Interpreter) dup(nod, anc *node) *node {
interp.nindex++
nindex := atomic.AddInt64(&interp.nindex, 1)
n := *nod
n.index = interp.nindex
n.index = nindex
n.anc = anc
n.start = &n
n.pos = anc.pos
Expand Down
Loading

0 comments on commit 714253c

Please sign in to comment.