Jelajahi Sumber

Run Gosched() every 10000 ticks to introduce a preemption point. Fixes #97, closes #98.

Dmitry Panov 6 tahun lalu
induk
melakukan
f95411d186
2 mengubah file dengan 51 tambahan dan 0 penghapusan
  1. 44 0
      runtime_test.go
  2. 7 0
      vm.go

+ 44 - 0
runtime_test.go

@@ -4,6 +4,7 @@ import (
 	"errors"
 	"fmt"
 	"reflect"
+	"runtime"
 	"testing"
 	"time"
 )
@@ -1200,6 +1201,33 @@ func TestInterruptInWrappedFunction(t *testing.T) {
 	}
 }
 
+func TestRunLoopPreempt(t *testing.T) {
+	vm := New()
+	v, err := vm.RunString("(function() {for (;;) {}})")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	fn, ok := AssertFunction(v)
+	if !ok {
+		t.Fatal("Not a function")
+	}
+
+	go func() {
+		<-time.After(100 * time.Millisecond)
+		runtime.GC() // this hangs if the vm loop does not have any preemption points
+		vm.Interrupt(errors.New("hi"))
+	}()
+
+	v, err = fn(nil)
+	if err == nil {
+		t.Fatal("expected error")
+	}
+	if _, ok := err.(*InterruptedError); !ok {
+		t.Fatalf("Wrong error type: %T", err)
+	}
+}
+
 /*
 func TestArrayConcatSparse(t *testing.T) {
 function foo(a,b,c)
@@ -1250,3 +1278,19 @@ func BenchmarkCallNative(b *testing.B) {
 		vm.RunProgram(prg)
 	}
 }
+
+func BenchmarkMainLoop(b *testing.B) {
+	vm := New()
+
+	const SCRIPT = `
+		for (var i=0; i<100000; i++) {
+		}
+	`
+
+	prg := MustCompile("test.js", SCRIPT, true)
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		vm.RunProgram(prg)
+	}
+}

+ 7 - 0
vm.go

@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"log"
 	"math"
+	"runtime"
 	"strconv"
 	"sync"
 	"sync/atomic"
@@ -281,11 +282,17 @@ func (vm *vm) init() {
 func (vm *vm) run() {
 	vm.halt = false
 	interrupted := false
+	ticks := 0
 	for !vm.halt {
 		if interrupted = atomic.LoadUint32(&vm.interrupted) != 0; interrupted {
 			break
 		}
 		vm.prg.code[vm.pc].exec(vm)
+		ticks++
+		if ticks > 10000 {
+			runtime.Gosched()
+			ticks = 0
+		}
 	}
 
 	if interrupted {