From 4a381bb338e8a0136c8a102bda98f2a60b2eeb32 Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 26 Dec 2023 02:57:16 +0800 Subject: [PATCH] [change] Integrate the new v8go api --- api/handler.go | 16 +-- runtime/v8/benchmark_test.go | 269 ----------------------------------- runtime/v8/bridge/bridge.go | 2 +- runtime/v8/context.go | 112 +++++++-------- runtime/v8/context_test.go | 144 ------------------- runtime/v8/isolate.go | 255 +++------------------------------ runtime/v8/script.go | 237 ++++++++---------------------- runtime/v8/types.go | 6 +- runtime/v8/v8.go | 1 + 9 files changed, 150 insertions(+), 892 deletions(-) delete mode 100644 runtime/v8/benchmark_test.go delete mode 100644 runtime/v8/context_test.go diff --git a/api/handler.go b/api/handler.go index d6ed7c3b..cd42a3ab 100644 --- a/api/handler.go +++ b/api/handler.go @@ -217,31 +217,27 @@ func (path Path) runStreamScript(ctx context.Context, c *gin.Context, getArgs fu } defer v8ctx.Close() - // make a new bridge function - ssEventT := v8go.NewFunctionTemplate(v8ctx.Isolate(), func(info *v8go.FunctionCallbackInfo) *v8go.Value { + v8ctx.WithFunction("ssEvent", func(info *v8go.FunctionCallbackInfo) *v8go.Value { args := info.Args() if len(args) != 2 { - return v8go.Null(v8ctx.Isolate()) + return v8go.Null(info.Context().Isolate()) } name := args[0].String() message, err := bridge.GoValue(args[1], info.Context()) if err != nil { - return v8go.Null(v8ctx.Isolate()) + return v8go.Null(info.Context().Isolate()) } onEvent(name, message) - return v8go.Null(v8ctx.Isolate()) + return v8go.Null(info.Context().Isolate()) }) - cancelT := v8go.NewFunctionTemplate(v8ctx.Isolate(), func(info *v8go.FunctionCallbackInfo) *v8go.Value { + v8ctx.WithFunction("cancel", func(info *v8go.FunctionCallbackInfo) *v8go.Value { onCancel() - return v8go.Null(v8ctx.Isolate()) + return v8go.Null(info.Context().Isolate()) }) - v8ctx.Global().Set("ssEvent", ssEventT.GetFunction(v8ctx.Context)) - v8ctx.Global().Set("cancel", cancelT.GetFunction(v8ctx.Context)) - args := getArgs(c) _, err = v8ctx.CallWith(ctx, method, args...) if err != nil { diff --git a/runtime/v8/benchmark_test.go b/runtime/v8/benchmark_test.go deleted file mode 100644 index 7d848317..00000000 --- a/runtime/v8/benchmark_test.go +++ /dev/null @@ -1,269 +0,0 @@ -package v8 - -// import ( -// "fmt" -// "testing" -// "time" - -// "github.com/yaoapp/gou/process" -// "github.com/yaoapp/kun/log" -// ) - -// func BenchmarkStd(b *testing.B) { -// b.ResetTimer() -// for i := 0; i < b.N; i++ { -// var _ string = fmt.Sprint(i) -// } -// b.StopTimer() -// } - -// func BenchmarkStdPB(b *testing.B) { -// b.ResetTimer() -// i := 0 -// b.RunParallel(func(pb *testing.PB) { -// i++ -// for pb.Next() { -// var _ string = fmt.Sprint(i) -// } -// }) -// b.StopTimer() -// } - -// func BenchmarkSelect(b *testing.B) { -// b.ResetTimer() -// var t *testing.T -// prepare(t) -// log.SetLevel(log.FatalLevel) - -// // run the Call function b.N times -// for n := 0; n < b.N; n++ { -// _, err := Select("runtime.basic") -// if err != nil { -// b.Fatal(err) -// } -// } -// b.StopTimer() -// } - -// func BenchmarkSelectIso(b *testing.B) { -// b.ResetTimer() -// var t *testing.T -// prepare(t) -// log.SetLevel(log.FatalLevel) - -// // run the Call function b.N times -// for n := 0; n < b.N; n++ { -// iso, err := SelectIso(500 * time.Millisecond) -// if err != nil { -// b.Fatal(err) -// } -// iso.Unlock() -// } -// b.StopTimer() -// } - -// func BenchmarkSelectIsoPB(b *testing.B) { -// b.ResetTimer() -// var t *testing.T -// prepare(t) -// log.SetLevel(log.FatalLevel) - -// // run the Call function b.N times -// b.RunParallel(func(pb *testing.PB) { -// for pb.Next() { -// iso, err := SelectIso(500 * time.Millisecond) -// if err != nil { -// b.Fatal(err) -// } -// iso.Unlock() -// } -// }) -// b.StopTimer() -// } - -// func BenchmarkNewContext(b *testing.B) { -// b.ResetTimer() -// var t *testing.T -// prepare(t) -// log.SetLevel(log.FatalLevel) - -// basic, err := Select("runtime.basic") -// if err != nil { -// b.Fatal(err) -// } - -// basic.Timeout = time.Minute * 5 - -// // run the Call function b.N times -// for n := 0; n < b.N; n++ { -// ctx, err := basic.NewContext("SID_1010", map[string]interface{}{"name": "testing"}) -// if err != nil { -// b.Fatal(err) -// } -// ctx.Close() -// } -// b.StopTimer() -// } - -// func BenchmarkNewContentPB(b *testing.B) { -// b.ResetTimer() -// var t *testing.T -// prepare(t) -// isolates.Resize(100, 100) -// log.SetLevel(log.FatalLevel) - -// basic, err := Select("runtime.basic") -// if err != nil { -// b.Fatal(err) -// } - -// basic.Timeout = time.Millisecond * 500 -// // run the Call function b.N times -// b.RunParallel(func(pb *testing.PB) { -// for pb.Next() { -// ctx, err := basic.NewContext("SID_1010", map[string]interface{}{"name": "testing"}) -// if err != nil { -// b.Fatal(err) -// } -// ctx.Close() -// } -// }) - -// b.StopTimer() -// } - -// func BenchmarkNewContentPBRelease(b *testing.B) { -// b.ResetTimer() -// var t *testing.T -// prepare(t) -// isolates.Resize(100, 100) -// log.SetLevel(log.FatalLevel) - -// SetHeapAvailableSize(2018051350) -// defer SetHeapAvailableSize(524288000) - -// DisablePrecompile() -// defer EnablePrecompile() - -// basic, err := Select("runtime.basic") -// if err != nil { -// b.Fatal(err) -// } - -// basic.Timeout = time.Millisecond * 500 -// // run the Call function b.N times -// b.RunParallel(func(pb *testing.PB) { -// for pb.Next() { -// ctx, err := basic.NewContext("SID_1010", map[string]interface{}{"name": "testing"}) -// if err != nil { -// b.Fatal(err) -// } -// ctx.Close() -// } -// }) - -// b.StopTimer() -// } - -// func BenchmarkCall(b *testing.B) { -// b.ResetTimer() -// var t *testing.T -// prepare(t) -// log.SetLevel(log.FatalLevel) - -// basic, err := Select("runtime.basic") -// if err != nil { -// b.Fatal(err) -// } - -// basic.Timeout = time.Minute * 5 -// ctx, err := basic.NewContext("SID_1010", map[string]interface{}{"name": "testing"}) -// if err != nil { -// b.Fatal(err) -// } -// defer ctx.Close() - -// // run the Call function b.N times -// for n := 0; n < b.N; n++ { -// _, err = ctx.Call("Hello", "world") -// if err != nil { -// b.Fatal(err) -// } -// } -// b.StopTimer() -// } - -// // -// // func BenchmarkCallPB(b *testing.B) { -// // b.ResetTimer() -// // var t *testing.T -// // prepare(t) -// // isolates.Resize(100, 100) -// // log.SetLevel(log.FatalLevel) - -// // basic, err := Select("runtime.basic") -// // if err != nil { -// // b.Fatal(err) -// // } - -// // basic.Timeout = time.Minute * 5 -// // ctx, err := basic.NewContext("SID_1010", map[string]interface{}{"name": "testing"}) -// // if err != nil { -// // b.Fatal(err) -// // } -// // defer ctx.Close() - -// // b.RunParallel(func(pb *testing.PB) { -// // for pb.Next() { -// // _, err = ctx.Call("Hello", "world") -// // if err != nil { -// // b.Fatal(err) -// // } -// // } -// // }) - -// // b.StopTimer() -// // } - -// func BenchmarkProcessScripts(b *testing.B) { -// b.ResetTimer() -// var t *testing.T -// prepare(t) - -// p, err := process.Of("scripts.runtime.basic.Hello", "world") -// if err != nil { -// t.Fatal(err) -// } - -// // run the Call function b.N times -// for n := 0; n < b.N; n++ { -// _, err := p.Exec() -// if err != nil { -// t.Fatal(err) -// } -// } -// b.StopTimer() -// } - -// func BenchmarkProcessScriptsPB(b *testing.B) { -// b.ResetTimer() -// var t *testing.T -// prepare(t) -// isolates.Resize(100, 100) - -// p, err := process.Of("scripts.runtime.basic.Hello", "world") -// if err != nil { -// t.Fatal(err) -// } - -// b.RunParallel(func(pb *testing.PB) { -// for pb.Next() { -// _, err := p.Exec() -// if err != nil { -// t.Fatal(err) -// } -// } -// }) - -// b.StopTimer() -// } diff --git a/runtime/v8/bridge/bridge.go b/runtime/v8/bridge/bridge.go index 922cac36..1e66651a 100644 --- a/runtime/v8/bridge/bridge.go +++ b/runtime/v8/bridge/bridge.go @@ -424,7 +424,7 @@ func ShareData(ctx *v8go.Context) (*Share, error) { iso, ok := data["ISO"].(string) // Isolate ID if !ok { - root = false + iso = "" } return &Share{ diff --git a/runtime/v8/context.go b/runtime/v8/context.go index daaa6c74..88908c5e 100644 --- a/runtime/v8/context.go +++ b/runtime/v8/context.go @@ -9,59 +9,59 @@ import ( ) // Call call the script function -func (ctx *Context) Call(method string, args ...interface{}) (interface{}, error) { - - global := ctx.Context.Global() - jsArgs, err := bridge.JsValues(ctx.Context, args) +func (context *Context) Call(method string, args ...interface{}) (interface{}, error) { + + // Set the global data + global := context.Global() + err := bridge.SetShareData(context.Context, global, &bridge.Share{ + Sid: context.Sid, + Root: context.Root, + Global: context.Data, + }) if err != nil { - return nil, fmt.Errorf("%s.%s %s", ctx.ID, method, err.Error()) + return nil, err } - defer bridge.FreeJsValues(jsArgs) - - jsData, err := ctx.setData(global) + // Run the method + jsArgs, err := bridge.JsValues(context.Context, args) if err != nil { return nil, err } - defer func() { - if !jsData.IsNull() && !jsData.IsUndefined() { - jsData.Release() - } - }() + defer bridge.FreeJsValues(jsArgs) jsRes, err := global.MethodCall(method, bridge.Valuers(jsArgs)...) if err != nil { - return nil, fmt.Errorf("%s.%s %+v", ctx.ID, method, err) + return nil, err } - goRes, err := bridge.GoValue(jsRes, ctx.Context) + goRes, err := bridge.GoValue(jsRes, context.Context) if err != nil { - return nil, fmt.Errorf("%s.%s %s", ctx.ID, method, err.Error()) + return nil, err } return goRes, nil } // CallWith call the script function -func (ctx *Context) CallWith(context context.Context, method string, args ...interface{}) (interface{}, error) { - - global := ctx.Context.Global() - jsArgs, err := bridge.JsValues(ctx.Context, args) +func (context *Context) CallWith(ctx context.Context, method string, args ...interface{}) (interface{}, error) { + + // Set the global data + global := context.Global() + err := bridge.SetShareData(context.Context, global, &bridge.Share{ + Sid: context.Sid, + Root: context.Root, + Global: context.Data, + }) if err != nil { - return nil, fmt.Errorf("%s.%s %s", ctx.ID, method, err.Error()) + return nil, err } - defer bridge.FreeJsValues(jsArgs) - - jsData, err := ctx.setData(global) + // Run the method + jsArgs, err := bridge.JsValues(context.Context, args) if err != nil { - return nil, fmt.Errorf("%s.%s %s", ctx.ID, method, err.Error()) + return nil, err } - defer func() { - if !jsData.IsNull() && !jsData.IsUndefined() { - jsData.Release() - } - }() + defer bridge.FreeJsValues(jsArgs) doneChan := make(chan bool, 1) resChan := make(chan interface{}, 1) @@ -86,7 +86,7 @@ func (ctx *Context) CallWith(context context.Context, method string, args ...int return } - goRes, err := bridge.GoValue(jsRes, ctx.Context) + goRes, err := bridge.GoValue(jsRes, context.Context) if err != nil { errChan <- err return @@ -97,43 +97,39 @@ func (ctx *Context) CallWith(context context.Context, method string, args ...int }() select { - case <-context.Done(): + case <-ctx.Done(): doneChan <- true - return nil, context.Err() + return nil, ctx.Err() case err := <-errChan: - return nil, fmt.Errorf("%s.%s %s", ctx.ID, method, err.Error()) + return nil, fmt.Errorf("%s.%s %s", context.ID, method, err.Error()) case goRes := <-resChan: return goRes, nil } } -func (ctx *Context) setData(global *v8go.Object) (*v8go.Value, error) { - goData := map[string]interface{}{ - "SID": ctx.SID, - "ROOT": ctx.Root, - "DATA": ctx.Data, - } - - jsData, err := bridge.JsValue(ctx.Context, goData) - if err != nil { - return nil, err - } - - err = global.Set("__yao_data", jsData) - if err != nil { - return nil, err - } - - return jsData, nil +// WithFunction add a function to the context +func (context *Context) WithFunction(name string, cb v8go.FunctionCallback) { + tmpl := v8go.NewFunctionTemplate(context.Isolate.Isolate, cb) + context.Global().Set(name, tmpl.GetFunction(context.Context)) } // Close Context -func (ctx *Context) Close() error { - ctx.Context.Close() - ctx.Context = nil - ctx.Data = nil - ctx.SID = "" - return ctx.Iso.Unlock() +func (context *Context) Close() error { + + context.Context.Close() + context.Context = nil + context.UnboundScript = nil + context.Data = nil + + if runtimeOption.Mode == "standard" { + context.Isolate.Dispose() + context.Isolate = nil + return nil + } + // Performance Mode + context.Isolate.Unlock() + context.Isolate = nil + return nil } diff --git a/runtime/v8/context_test.go b/runtime/v8/context_test.go deleted file mode 100644 index d1582983..00000000 --- a/runtime/v8/context_test.go +++ /dev/null @@ -1,144 +0,0 @@ -package v8 - -// import ( -// "context" -// "testing" -// "time" - -// "github.com/stretchr/testify/assert" -// ) - -// func TestCall(t *testing.T) { -// prepare(t) -// time.Sleep(20 * time.Millisecond) -// assert.Equal(t, 3, len(Scripts)) -// assert.Equal(t, 1, len(RootScripts)) -// assert.Equal(t, 2, len(chIsoReady)) - -// basic, err := Select("runtime.basic") -// if err != nil { -// t.Fatal(err) -// } - -// ctx, err := basic.NewContext("SID_1010", map[string]interface{}{"name": "testing"}) -// if err != nil { -// t.Fatal(err) -// } -// defer ctx.Close() - -// res, err := ctx.Call("Hello", "world") -// if err != nil { -// t.Fatal(err) -// } -// assert.Equal(t, "world", res) -// } - -// func TestCallTS(t *testing.T) { -// prepare(t) -// time.Sleep(20 * time.Millisecond) -// assert.Equal(t, 3, len(Scripts)) -// assert.Equal(t, 1, len(RootScripts)) -// assert.Equal(t, 2, len(chIsoReady)) - -// typescript, err := Select("runtime.typescript") -// if err != nil { -// t.Fatal(err) -// } - -// ctx, err := typescript.NewContext("SID_1010", map[string]interface{}{"name": "testing"}) -// if err != nil { -// t.Fatal(err) -// } -// defer ctx.Close() - -// res, err := ctx.Call("Hello", "world") -// if err != nil { -// t.Fatal(err) -// } -// assert.Equal(t, "world", res) -// } - -// func TestCallWith(t *testing.T) { -// prepare(t) -// time.Sleep(20 * time.Millisecond) -// assert.Equal(t, 3, len(Scripts)) -// assert.Equal(t, 1, len(RootScripts)) -// assert.Equal(t, 2, len(chIsoReady)) - -// basic, err := Select("runtime.basic") -// if err != nil { -// t.Fatal(err) -// } - -// ctx, err := basic.NewContext("SID_1010", map[string]interface{}{"name": "testing"}) -// if err != nil { -// t.Fatal(err) -// } -// defer ctx.Close() - -// context, cancel := context.WithCancel(context.Background()) -// defer cancel() - -// res, err := ctx.CallWith(context, "Cancel", "hello") -// if err != nil { -// t.Fatal(err) -// } -// assert.Equal(t, "hello", res) -// } - -// func TestCallWithCancel(t *testing.T) { -// prepare(t) -// time.Sleep(20 * time.Millisecond) -// assert.Equal(t, 3, len(Scripts)) -// assert.Equal(t, 1, len(RootScripts)) -// assert.Equal(t, 2, len(chIsoReady)) - -// basic, err := Select("runtime.basic") -// if err != nil { -// t.Fatal(err) -// } - -// ctx, err := basic.NewContext("SID_1010", map[string]interface{}{"name": "testing"}) -// if err != nil { -// t.Fatal(err) -// } -// defer ctx.Close() - -// context, cancel := context.WithCancel(context.Background()) -// defer cancel() - -// go func() { -// time.Sleep(200 * time.Millisecond) -// cancel() -// }() - -// _, err = ctx.CallWith(context, "Cancel", "hello") -// assert.Contains(t, err.Error(), "context canceled") -// } - -// func TestCallRelease(t *testing.T) { -// prepare(t) - -// SetHeapAvailableSize(2018051350) -// defer SetHeapAvailableSize(524288000) - -// DisablePrecompile() -// defer EnablePrecompile() - -// basic, err := Select("runtime.basic") -// if err != nil { -// t.Fatal(err) -// } - -// ctx, err := basic.NewContext("SID_1020", map[string]interface{}{"name": "testing"}) -// if err != nil { -// t.Fatal(err) -// } - -// assert.False(t, ctx.Iso.health()) -// ctx.Close() -// assert.Equal(t, 1, len(chIsoReady)) - -// time.Sleep(1 * time.Second) -// assert.Equal(t, 2, len(chIsoReady)) -// } diff --git a/runtime/v8/isolate.go b/runtime/v8/isolate.go index d9ae414d..37a757f2 100644 --- a/runtime/v8/isolate.go +++ b/runtime/v8/isolate.go @@ -38,6 +38,12 @@ var newContextLock = &sync.RWMutex{} // initialize create a new Isolate // in performance mode, the minSize isolates will be created func initialize() { + + v8go.YaoInit(uint(runtimeOption.HeapSizeLimit / 1024 / 1024)) + + // Make a global Isolate + makeGlobalIsolate() + isoReady = make(chan *store.Isolate, runtimeOption.MaxSize) store.Isolates = store.New() log.Trace( @@ -51,6 +57,10 @@ func initialize() { } } +func release() { + v8go.YaoDispose() +} + // addIsolate create a new and add to the isolates func addIsolate() (*store.Isolate, error) { @@ -94,15 +104,6 @@ func removeIsolate(iso *store.Isolate) { // it cost too much time and memory to compile all scripts // ignore the error func precompile(iso *store.Isolate) { - - for _, script := range Scripts { - script.compile(iso) - } - - for _, script := range RootScripts { - script.compile(iso) - } - return } @@ -132,14 +133,24 @@ func MakeTemplate(iso *v8go.Isolate) *v8go.ObjectTemplate { return template } +func makeGlobalIsolate() { + iso := v8go.YaoNewIsolate() + iso.AsGlobal() +} + func makeIsolate() *store.Isolate { - iso := v8go.NewIsolateHeapSize(int(runtimeOption.HeapSizeLimit / 1024 / 1024)) - new := &store.Isolate{ + // iso, err := v8go.YaoNewIsolateFromGlobal() + // if err != nil { + // log.Error("[V8] Create isolate failed: %s", err.Error()) + // return nil + // } + + iso := v8go.YaoNewIsolate() + return &store.Isolate{ Isolate: iso, Template: MakeTemplate(iso), Status: IsoReady, } - return new } // SelectIsoPerformance one ready isolate @@ -220,223 +231,3 @@ func Unlock(iso *store.Isolate) { go replaceIsolate(iso) } - -// ********************************************************************************************************************* -// -- Warning: The following code is deprecated -- -// ********************************************************************************************************************* - -// NewIsolate create a new Isolate -func NewIsolate() (*Isolate, error) { - - newIsolateLock.Lock() - defer newIsolateLock.Unlock() - - if isolates.Len >= runtimeOption.MaxSize { - log.Warn("[V8] The maximum number of v8 vm has been reached (%d)", runtimeOption.MaxSize) - return nil, fmt.Errorf("The maximum number of v8 vm has been reached (%d)", runtimeOption.MaxSize) - } - - new := newIsolate() - isolates.Add(new) - return new, nil -} - -func newIsolate() *Isolate { - - iso := v8go.NewIsolate() - template := MakeTemplate(iso) - new := &Isolate{ - Isolate: iso, - template: template, - status: IsoReady, - } - - if runtimeOption.Precompile { - new.Precompile() - } - return new -} - -// Precompile compile the loaded scirpts -func (iso *Isolate) Precompile() { - - for _, script := range Scripts { - timeout := script.Timeout - if timeout == 0 { - timeout = time.Millisecond * time.Duration(runtimeOption.ContextTimeout) - } - } - - for _, script := range RootScripts { - timeout := script.Timeout - if timeout == 0 { - timeout = time.Millisecond * 100 - } - } -} - -// SelectIso one ready isolate -func SelectIso(timeout time.Duration) (*Isolate, error) { - - // Create a new isolate - if len(chIsoReady) == 0 { - go NewIsolate() - } - - // make a timer - timer := time.NewTimer(timeout) - defer timer.Stop() - - select { - case <-timer.C: - return nil, fmt.Errorf("Select isolate timeout %v", timeout) - - case iso := <-chIsoReady: - iso.Lock() - return iso, nil - } -} - -// Resize set the maxSize -func (list *Isolates) Resize(minSize, maxSize int) error { - if maxSize > 100 { - log.Warn("[V8] the maximum value of maxSize is 100") - maxSize = 100 - } - - // Remove iso - isolates.Range(func(iso *Isolate) bool { - isolates.Remove(iso) - return true - }) - - runtimeOption.MinSize = minSize - runtimeOption.MaxSize = maxSize - runtimeOption.Validate() - chIsoReady = make(chan *Isolate, runtimeOption.MaxSize) - for i := 0; i < runtimeOption.MinSize; i++ { - _, err := NewIsolate() - if err != nil { - return err - } - } - - return nil -} - -// Add a isolate -func (list *Isolates) Add(iso *Isolate) { - list.Data.Store(iso.Key(), true) - list.Len = list.Len + 1 - chIsoReady <- iso -} - -// Remove a isolate -func (list *Isolates) Remove(iso *Isolate) { - defer iso.Dispose() - list.Data.Delete(iso.Key()) - list.Len = list.Len - 1 -} - -// Dispose the isolate -func (iso *Isolate) Dispose() { - iso.Isolate.Dispose() - iso.Isolate = nil - iso.template = nil - iso = nil -} - -// Range traverse isolates -func (list *Isolates) Range(callback func(iso *Isolate) bool) { - list.Data.Range(func(key, value any) bool { - return callback(key.(*Isolate)) - }) -} - -// Key return the key of the isolate -func (iso *Isolate) Key() string { - return fmt.Sprintf("%p", iso) -} - -// Lock the isolate -func (iso *Isolate) Lock() error { - iso.status = IsoBusy - return nil -} - -// Unlock the isolate -func (iso *Isolate) Unlock() error { - - if iso.health() && len(chIsoReady) <= runtimeOption.MinSize-1 { // the available isolates are less than min size - iso.status = IsoReady - chIsoReady <- iso - return nil - } - - // Remove the iso and create new one - go func() { - log.Info("[V8] VM %p will be removed", iso) - isolates.Remove(iso) - if len(chIsoReady) <= runtimeOption.MinSize-1 { // the available isolates are less than min size - NewIsolate() - } - }() - - return nil -} - -// Locked check if the isolate is locked -func (iso Isolate) Locked() bool { - return iso.status == IsoBusy -} - -// health check the isolate health -func (iso *Isolate) health() bool { - - // { - // "ExternalMemory": 0, - // "HeapSizeLimit": 1518338048, - // "MallocedMemory": 16484, - // "NumberOfDetachedContexts": 0, - // "NumberOfNativeContexts": 3, - // "PeakMallocedMemory": 24576, - // "TotalAvailableSize": 1518051356, - // "TotalHeapSize": 1261568, - // "TotalHeapSizeExecutable": 262144, - // "TotalPhysicalSize": 499164, - // "UsedHeapSize": 713616 - // } - - if iso.Isolate == nil { - return false - } - - stat := iso.Isolate.GetHeapStatistics() - if stat.TotalHeapSize > runtimeOption.HeapSizeRelease { - return false - } - - if stat.TotalAvailableSize < runtimeOption.HeapAvailableSize { // 500M - return false - } - - return true -} - -// MakeContext make a new context -func (iso *Isolate) MakeContext(script *Script) (*v8go.Context, error) { - newContext := v8go.NewContext(iso.Isolate, iso.template) - instance, err := iso.Isolate.CompileUnboundScript(script.Source, script.File, v8go.CompileOptions{}) - if err != nil { - newContext.Close() - return nil, err - } - - _, err = instance.Run(newContext) - if err != nil { - newContext.Close() - return nil, err - } - - return newContext, nil -} diff --git a/runtime/v8/script.go b/runtime/v8/script.go index ad97aa17..804efc3d 100644 --- a/runtime/v8/script.go +++ b/runtime/v8/script.go @@ -10,7 +10,6 @@ import ( "github.com/yaoapp/gou/application" "github.com/yaoapp/gou/process" "github.com/yaoapp/gou/runtime/v8/bridge" - "github.com/yaoapp/gou/runtime/v8/store" "github.com/yaoapp/kun/exception" "github.com/yaoapp/kun/log" "rogchap.com/v8go" @@ -137,6 +136,48 @@ func SelectRoot(id string) (*Script, error) { return script, nil } +// NewContext create a new context +func (script *Script) NewContext(sid string, global map[string]interface{}) (*Context, error) { + + timeout := script.Timeout + if timeout == 0 { + timeout = time.Duration(runtimeOption.ContextTimeout) * time.Millisecond + } + + if runtimeOption.Mode == "performance" { + return nil, fmt.Errorf("performance mode is not supported yet") + } + + iso, err := SelectIsoStandard(time.Duration(runtimeOption.DefaultTimeout) * time.Millisecond) + if err != nil { + return nil, err + } + + ctx := v8go.NewContext(iso, iso.Template) + + // Create instance of the script + instance, err := iso.CompileUnboundScript(script.Source, script.File, v8go.CompileOptions{}) + if err != nil { + return nil, fmt.Errorf("scripts.%s %s", script.ID, err.Error()) + } + v, err := instance.Run(ctx) + if err != nil { + return nil, fmt.Errorf("scripts.%s %s", script.ID, err.Error()) + } + defer v.Release() + + return &Context{ + ID: script.ID, + Sid: sid, + Data: global, + Root: script.Root, + Timeout: timeout, + Isolate: iso, + Context: ctx, + UnboundScript: instance, + }, nil +} + // Exec execute the script // the default mode is "standard" and the other value is "performance". // the "standard" mode save memory but will run slower. can be used in most cases, especially in arm64 device. @@ -157,7 +198,7 @@ func (script *Script) execPerformance(process *process.Process) interface{} { } defer Unlock(iso) - return "OK" + return "Performance Mode is not supported yet" // iso, ctx, err := MakeContext(script) // if err != nil { @@ -199,27 +240,6 @@ func (script *Script) execPerformance(process *process.Process) interface{} { // return goRes } -// MakeContext select a context -func MakeContext(script *Script) (*store.Isolate, *store.Context, error) { - iso, err := SelectIsoPerformance(time.Duration(runtimeOption.DefaultTimeout) * time.Millisecond) - if err != nil { - return nil, nil, err - } - - // ctx, has := store.GetContextFromCache(iso.Key(), script.ID) - // if has { - // return iso, ctx, nil - // } - - ctx, err := script.compile(iso) - if err != nil { - Unlock(iso) - return iso, nil, err - } - - return iso, ctx, nil -} - // execStandard execute the script in standard mode func (script *Script) execStandard(process *process.Process) interface{} { @@ -233,6 +253,13 @@ func (script *Script) execStandard(process *process.Process) interface{} { ctx := v8go.NewContext(iso, iso.Template) defer ctx.Close() + // Next Version will support this, snapshot will be used in the next version + // ctx, err := iso.Context() + // if err != nil { + // exception.New("scripts.%s.%s %s", 500, script.ID, process.Method, err.Error()).Throw() + // return nil + // } + // Create instance of the script instance, err := iso.CompileUnboundScript(script.Source, script.File, v8go.CompileOptions{}) if err != nil { @@ -253,6 +280,7 @@ func (script *Script) execStandard(process *process.Process) interface{} { Global: process.Global, }) if err != nil { + log.Error("scripts.%s.%s %s", script.ID, process.Method, err.Error()) exception.New("scripts.%s.%s %s", 500, script.ID, process.Method, err.Error()).Throw() return nil } @@ -260,18 +288,25 @@ func (script *Script) execStandard(process *process.Process) interface{} { // Run the method jsArgs, err := bridge.JsValues(ctx, process.Args) if err != nil { - return fmt.Errorf("%s.%s %s", script.ID, process.Method, err.Error()) + log.Error("scripts.%s.%s %s", script.ID, process.Method, err.Error()) + exception.New(err.Error(), 500).Throw() + return nil + } defer bridge.FreeJsValues(jsArgs) jsRes, err := global.MethodCall(process.Method, bridge.Valuers(jsArgs)...) if err != nil { - return fmt.Errorf("%s.%s %+v", script.ID, process.Method, err) + log.Error("scripts.%s.%s %s", script.ID, process.Method, err.Error()) + exception.New(err.Error(), 500).Throw() + return nil } goRes, err := bridge.GoValue(jsRes, ctx) if err != nil { - return fmt.Errorf("%s.%s %s", script.ID, process.Method, err.Error()) + log.Error("scripts.%s.%s %s", script.ID, process.Method, err.Error()) + exception.New(err.Error(), 500).Throw() + return nil } return goRes @@ -284,153 +319,3 @@ func (script *Script) ContextTimeout() time.Duration { } return time.Duration(runtimeOption.ContextTimeout) * time.Millisecond } - -// compile compile the script -// in performance mode the script will be compiled when the isolate is created -func (script *Script) compile(iso *store.Isolate) (*store.Context, error) { - v8ctx := v8go.NewContext(iso, iso.Template) - instance, err := iso.Isolate.CompileUnboundScript(script.Source, script.File, v8go.CompileOptions{}) - if err != nil { - log.Error("[v8] scripts.%s compile error %s", script.ID, err.Error()) - return nil, err - } - _, err = instance.Run(v8ctx) - if err != nil { - log.Error("[v8] scripts.%s compile error %s", script.ID, err.Error()) - return nil, err - } - - key := iso.Key() - ctx := store.NewContext(key, script.ID, v8ctx) - // store.SetContextCache(key, script.ID, ctx) - log.Trace("[v8] scripts.%s compile success", script.ID) - return ctx, nil -} - -// NewContext create a new context -func (script *Script) NewContext(sid string, global map[string]interface{}) (*Context, error) { - - timeout := script.Timeout - if timeout == 0 { - timeout = time.Duration(runtimeOption.ContextTimeout) * time.Millisecond - } - - iso, err := SelectIso(time.Duration(runtimeOption.DefaultTimeout) * time.Millisecond) - if err != nil { - return nil, err - } - - // context, err := iso.SelectContext(script, timeout) - // if err != nil { - // return nil, err - // } - - return &Context{ - ID: script.ID, - Context: nil, - SID: sid, - Data: global, - Root: script.Root, - Iso: iso, - }, nil -} - -// Compile the javascript -// func (script *Script) Compile(iso *Isolate, timeout time.Duration) (*v8go.Context, error) { - -// if iso.Isolate == nil { -// return nil, fmt.Errorf("isolate was removed") -// } - -// if timeout == 0 { -// timeout = time.Second * 5 -// } - -// ctx := v8go.NewContext(iso.Isolate, iso.template) -// instance, err := iso.CompileUnboundScript(script.Source, script.File, v8go.CompileOptions{}) -// if err != nil { -// return nil, err -// } - -// // console.log("foo", "bar", 1, 2, 3, 4) -// err = console.New().Set("console", ctx) -// if err != nil { -// return nil, err -// } - -// _, err = instance.Run(ctx) -// if err != nil { -// return nil, err -// } - -// // iso.contexts[script] = ctx // cache -// return ctx, nil -// } - -// debug : debug the script -// func (script *Script) debug(sid string, data map[string]interface{}, method string, args ...interface{}) (interface{}, error) { - -// timeout := script.Timeout -// if timeout == 0 { -// timeout = 100 * time.Millisecond -// } - -// iso, err := SelectIso(timeout) -// if err != nil { -// return nil, err -// } - -// defer iso.Unlock() - -// ctx := v8go.NewContext(iso.Isolate, iso.template) -// defer ctx.Close() - -// instance, err := iso.Isolate.CompileUnboundScript(script.Source, script.File, v8go.CompileOptions{}) -// if err != nil { -// return nil, err -// } - -// _, err = instance.Run(ctx) -// if err != nil { -// return nil, err -// } - -// global := ctx.Global() - -// jsArgs, err := bridge.JsValues(ctx, args) -// if err != nil { -// return nil, fmt.Errorf("%s.%s %s", script.ID, method, err.Error()) -// } -// defer bridge.FreeJsValues(jsArgs) - -// jsData, err := bridge.JsValue(ctx, map[string]interface{}{ -// "SID": sid, -// "ROOT": script.Root, -// "DATA": data, -// }) -// if err != nil { -// return nil, err -// } -// defer func() { -// if !jsData.IsNull() && !jsData.IsUndefined() { -// jsData.Release() -// } -// }() - -// err = global.Set("__yao_data", jsData) -// if err != nil { -// return nil, err -// } - -// res, err := global.MethodCall(method, bridge.Valuers(jsArgs)...) -// if err != nil { -// return nil, fmt.Errorf("%s.%s %+v", script.ID, method, err) -// } - -// goRes, err := bridge.GoValue(res, ctx) -// if err != nil { -// return nil, fmt.Errorf("%s.%s %s", script.ID, method, err.Error()) -// } - -// return goRes, nil -// } diff --git a/runtime/v8/types.go b/runtime/v8/types.go index 494d506f..3371394a 100644 --- a/runtime/v8/types.go +++ b/runtime/v8/types.go @@ -4,6 +4,7 @@ import ( "sync" "time" + "github.com/yaoapp/gou/runtime/v8/store" "rogchap.com/v8go" ) @@ -52,11 +53,12 @@ type Isolates struct { // Context v8 Context type Context struct { ID string // the script id - SID string // set the session id + Sid string // set the session id Data map[string]interface{} // set the global data Root bool Timeout time.Duration // terminate the execution after this time - Iso *Isolate + *store.Isolate + *v8go.UnboundScript *v8go.Context } diff --git a/runtime/v8/v8.go b/runtime/v8/v8.go index 14a42ba4..8499eb8a 100644 --- a/runtime/v8/v8.go +++ b/runtime/v8/v8.go @@ -24,4 +24,5 @@ func Stop() { store.Isolates.Remove(key) return true }) + release() }