From f37b64c2df26edd72aa57197471c38da79c4ab4a Mon Sep 17 00:00:00 2001 From: Star Date: Mon, 7 Oct 2024 23:02:11 +0800 Subject: [PATCH] many update support pool --- args.go | 117 +++++++++++- common.go | 50 +++++ dop251/goja/runtime.go | 5 +- go.mod | 10 +- gojs.go | 418 +++++++++++++++++++++++++++++------------ gojs_test.go | 20 +- lb.go | 77 ++++++++ makeGoja/go.mod | 11 +- makeGoja/go.sum | 14 ++ makeGoja/main.go | 3 +- modules/util/util.go | 15 ++ modules/util/util.ts | 2 + pool.go | 209 +++++++++++++++++++++ 13 files changed, 814 insertions(+), 137 deletions(-) create mode 100644 common.go create mode 100644 lb.go create mode 100644 pool.go diff --git a/args.go b/args.go index cac2f18..f3f710d 100644 --- a/args.go +++ b/args.go @@ -15,6 +15,13 @@ type Args struct { Logger *log.Logger } +type Obj struct { + This goja.Value + VM *goja.Runtime + Logger *log.Logger + O *goja.Object +} + func GetLogger(vm *goja.Runtime) *log.Logger { var logger *log.Logger if vm.GoData["logger"] != nil { @@ -174,9 +181,117 @@ func (args *Args) Func(index int) goja.Callable { return nil } -func (args *Args) Obj(index int) *goja.Object { +func (args *Args) Obj(index int) *Obj { + if len(args.Arguments) > index { + return &Obj{ + This: args.This, + VM: args.VM, + Logger: args.Logger, + O: args.Arguments[index].ToObject(args.VM), + } + } + return nil +} + +func (args *Args) Object(index int) *goja.Object { if len(args.Arguments) > index { return args.Arguments[index].ToObject(args.VM) } return nil } + +// -------- Object + +func (obj *Obj) Get(name string) goja.Value { + return obj.O.Get(name) +} + +func (obj *Obj) Int(name string) int { + v := obj.O.Get(name) + if v != nil { + return u.Int(v.Export()) + } + return 0 +} + +func (obj *Obj) Int64(name string) int64 { + v := obj.O.Get(name) + if v != nil { + return u.Int64(v.Export()) + } + return 0 +} + +func (obj *Obj) Any(name string) any { + v := obj.O.Get(name) + if v != nil { + return v.Export() + } + return nil +} + +func (obj *Obj) Str(name string) string { + v := obj.O.Get(name) + if v != nil { + return u.String(v.Export()) + } + return "" +} + +func (obj *Obj) Bytes(name string) []byte { + v := obj.O.Get(name) + if v != nil { + return u.Bytes(v.Export()) + } + return []byte{} +} + +func (obj *Obj) Bool(name string) bool { + v := obj.O.Get(name) + if v != nil { + return u.Bool(v.Export()) + } + return false +} + +func (obj *Obj) Map(name string) map[string]any { + v := obj.O.Get(name) + out := map[string]any{} + if v != nil { + u.Convert(v.Export(), &out) + } + return out +} + +func (obj *Obj) Path(name string) string { + return FindPath(obj.VM, obj.Str(name)) +} + +func (obj *Obj) Func(name string) goja.Callable { + v := obj.O.Get(name) + if v != nil { + return GetFunc(v) + } + return nil +} + +func (obj *Obj) Object(name string) *goja.Object { + v := obj.O.Get(name) + if v != nil { + return v.ToObject(obj.VM) + } + return nil +} + +func (obj *Obj) Arr(name string) []any { + v := obj.O.Get(name) + if v != nil { + arr := make([]any, 0) + obj.VM.ForOf(v, func(v goja.Value) bool { + arr = append(arr, v.Export()) + return true + }) + return arr + } + return nil +} diff --git a/common.go b/common.go new file mode 100644 index 0000000..a362ca6 --- /dev/null +++ b/common.go @@ -0,0 +1,50 @@ +package gojs + +import ( + "apigo.cc/apigo/gojs/dop251/goja" + "github.com/ssgo/log" + "github.com/ssgo/u" + "reflect" +) + +func MakeLogger(logger *log.Logger) Map { + return map[string]any{ + "info": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := MakeArgs(&argsIn, vm).Check(1) + logger.Info(args.Str(0), args.Map2Arr(1)...) + return nil + }, + "warn": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := MakeArgs(&argsIn, vm).Check(1) + logger.Warning(args.Str(0), args.Map2Arr(1)...) + return nil + }, + "error": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := MakeArgs(&argsIn, vm).Check(1) + logger.Error(args.Str(0), args.Map2Arr(1)...) + return nil + }, + "debug": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := MakeArgs(&argsIn, vm).Check(1) + logger.Debug(args.Str(0), args.Map2Arr(1)...) + return nil + }, + } +} + +func MakeMap(structObj any) Map { + m := Map{} + from := u.RealValue(reflect.ValueOf(structObj)) + if from.Kind() == reflect.Struct { + st := u.FlatStruct(structObj) + for k, v := range st.Values { + k = u.GetLowerName(k) + m[k] = v.Interface() + } + for k, v := range st.MethodValues { + k = u.GetLowerName(k) + m[k] = v.Interface() + } + } + return m +} diff --git a/dop251/goja/runtime.go b/dop251/goja/runtime.go index dbf8da7..978d738 100644 --- a/dop251/goja/runtime.go +++ b/dop251/goja/runtime.go @@ -13,6 +13,7 @@ import ( "reflect" "runtime" "strconv" + "sync" "time" "golang.org/x/text/collate" @@ -178,7 +179,9 @@ type RandSource func() float64 type Now func() time.Time type Runtime struct { - GoData map[string]any + GoData map[string]any + Locker sync.Mutex + CallbackLocker sync.Mutex global global globalObject *Object stringSingleton *stringObject diff --git a/go.mod b/go.mod index a55beb9..b9fd704 100644 --- a/go.mod +++ b/go.mod @@ -8,12 +8,12 @@ require ( github.com/google/pprof v0.0.0-20230207041349-798e818bf904 github.com/ssgo/dao v0.1.5 github.com/ssgo/db v1.7.9 - github.com/ssgo/httpclient v1.7.7 + github.com/ssgo/httpclient v1.7.8 github.com/ssgo/log v1.7.7 github.com/ssgo/tool v0.4.27 - github.com/ssgo/u v1.7.7 - golang.org/x/net v0.29.0 - golang.org/x/text v0.18.0 + github.com/ssgo/u v1.7.9 + golang.org/x/net v0.30.0 + golang.org/x/text v0.19.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -24,5 +24,5 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/ssgo/config v1.7.7 // indirect github.com/ssgo/standard v1.7.7 // indirect - golang.org/x/sys v0.25.0 // indirect + golang.org/x/sys v0.26.0 // indirect ) diff --git a/gojs.go b/gojs.go index e1bf65d..f7208eb 100644 --- a/gojs.go +++ b/gojs.go @@ -29,6 +29,12 @@ type Module struct { Example string } +type Program struct { + prg *goja.Program + startFile string + imports map[string]string +} + var modules = map[string]Module{} var modulesLock = sync.RWMutex{} @@ -39,7 +45,11 @@ func Register(name string, mod Module) { } func RunFile(file string, args ...any) (any, error) { - return Run(u.ReadFileN(file), file, args...) + if code, err := u.ReadFile(file); err == nil { + return Run(code, file, args...) + } else { + return nil, err + } } func Run(code string, refFile string, args ...any) (any, error) { @@ -52,6 +62,39 @@ func Run(code string, refFile string, args ...any) (any, error) { return r, err } +func RunProgram(plg *Program, args ...any) (any, error) { + rt := New() + var r any + err := rt.StartFromProgram(plg) + if err == nil { + r, err = rt.RunMain(args...) + } + return r, err +} + +func CompileFile(file string) (*Program, error) { + if code, err := u.ReadFile(file); err == nil { + return CompileMain(code, file) + } else { + return nil, err + } +} + +func CompileMain(code string, refFile string) (*Program, error) { + imports := makeImports(&code) + if !checkMainMatcher.MatchString(code) { + code = "function main(...Args){" + code + "}" + } + p, err := goja.Compile(refFile, code, false) + return &Program{prg: p, imports: imports, startFile: refFile}, err +} + +func CompileCode(code string, refFile string) (*Program, error) { + imports := makeImports(&code) + p, err := goja.Compile(refFile, code, false) + return &Program{prg: p, imports: imports, startFile: refFile}, err +} + var importModMatcher = regexp.MustCompile(`(?im)^\s*import\s+(.+?)\s+from\s+['"](.+?)['"]`) var requireModMatcher = regexp.MustCompile(`(?im)^\s*(const|let|var)\s+(.+?)\s*=\s*require\s*\(\s*['"](.+?)['"]\s*\)`) @@ -60,75 +103,150 @@ var requireModMatcher = regexp.MustCompile(`(?im)^\s*(const|let|var)\s+(.+?)\s*= var checkMainMatcher = regexp.MustCompile(`(?im)^\s*function\s+main\s*\(`) type Runtime struct { - VM *goja.Runtime + vm *goja.Runtime required map[string]bool file string srcCode string code string moduleLoader func(string) string started bool + dataLock sync.RWMutex } +func (rt *Runtime) lock() { + rt.vm.Locker.Lock() +} + +func (rt *Runtime) unlock() { + rt.vm.Locker.Unlock() +} + +//func (rt *Runtime) Free() { +// rt.vm.GoData = nil +// rt.vm = nil +//} + func (rt *Runtime) SetModuleLoader(fn func(filename string) string) { rt.moduleLoader = fn } func (rt *Runtime) GetCallStack() []string { callStacks := make([]string, 0) - for _, stack := range rt.VM.CaptureCallStack(0, nil) { + rt.lock() + stacks := rt.vm.CaptureCallStack(0, nil) + rt.unlock() + for _, stack := range stacks { callStacks = append(callStacks, stack.Position().String()) } return callStacks } -func (rt *Runtime) requireMod(name, realModName string) error { - if name != "" { - if rt.required[name] { - return nil - } - modulesLock.RLock() - mod, ok := modules[name] - modulesLock.RUnlock() - if !ok { - return errors.New("module not found: " + name) - } - - var err error - if mod.ObjectMaker != nil { - err = rt.VM.Set(realModName, mod.ObjectMaker(rt.VM)) - } else { - err = rt.VM.Set(realModName, mod.Object) - } - if err != nil { - return err - } - rt.required[name] = true - return nil - } else { - // 使用所有模块 - var allModules = map[string]Module{} - modulesLock.RLock() - for k, v := range modules { - allModules[k] = v - } - modulesLock.RUnlock() - - for k, v := range allModules { - if !rt.required[k] { - if err := rt.VM.Set(k, v); err != nil { - return err - } - rt.required[k] = true - } - } +func (rt *Runtime) requireSpecialMod(name string) error { + if rt.required[name] { return nil } + if name == "setTimeout" { + rt.required["setTimeout"] = true + fn := func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := MakeArgs(&argsIn, vm).Check(1) + callback := args.Func(0) + timeout := time.Duration(args.Int64(1)) * time.Millisecond + //locked := vm.Locker.TryLock() + //vm.Locker.Unlock() + //locked2 := vm.CallbackLocker.TryLock() + //vm.CallbackLocker.Unlock() + if callback != nil { + go func() { + if timeout > 0 { + time.Sleep(timeout) + } + //if !locked { + // vm.Locker.Lock() + //} + //if !locked2 { + // vm.CallbackLocker.Lock() + //} + if _, err := callback(args.This, args.Arguments[2:]...); err != nil { + //panic(vm.NewGoError(err)) + } + }() + } + return nil + } + rt.lock() + err := rt.vm.Set("setTimeout", fn) + rt.unlock() + return err + } + + return errors.New("module not found: " + name) } -func (rt *Runtime) makeImport(code string) (string, int, error) { - var modErr error - importCount := 0 - code = requireModMatcher.ReplaceAllStringFunc(code, func(str string) string { +func (rt *Runtime) requireMod(modName, realModName string) error { + if rt.required[modName] { + return nil + } + modulesLock.RLock() + mod, ok := modules[modName] + modulesLock.RUnlock() + if !ok { + // 不在注册的模块中,尝试引入特殊模块 + return rt.requireSpecialMod(modName) + } + + var err error + rt.lock() + if mod.ObjectMaker != nil { + err = rt.vm.Set(realModName, mod.ObjectMaker(rt.vm)) + } else { + err = rt.vm.Set(realModName, mod.Object) + } + rt.unlock() + if err != nil { + return err + } + rt.required[modName] = true + return nil +} + +func (rt *Runtime) requireAllMod() error { + var allModules = map[string]Module{} + modulesLock.RLock() + for k, v := range modules { + allModules[k] = v + } + modulesLock.RUnlock() + + for modName, mod := range allModules { + if !rt.required[modName] { + var err error + realModName := modName + if strings.ContainsRune(modName, '/') { + realModName = strings.ReplaceAll(realModName, "/", "_") + } + rt.lock() + if mod.ObjectMaker != nil { + err = rt.vm.Set(realModName, mod.ObjectMaker(rt.vm)) + } else { + err = rt.vm.Set(realModName, mod.Object) + } + rt.unlock() + if err != nil { + return err + } + rt.required[modName] = true + } + } + if err := rt.requireSpecialMod("setTimeout"); err != nil { + return err + } + return nil +} + +func makeImports(code *string) map[string]string { + importModules := map[string]string{} + *code = importModMatcher.ReplaceAllString(*code, "let $1 = require('$2')") + *code = requireModMatcher.ReplaceAllStringFunc(*code, func(str string) string { if m := requireModMatcher.FindStringSubmatch(str); m != nil && len(m) > 3 { optName := m[1] varName := m[2] @@ -143,12 +261,7 @@ func (rt *Runtime) makeImport(code string) (string, int, error) { if !ok { return str } - importCount++ - if modErr == nil { - if err := rt.requireMod(modName, realModName); err != nil { - modErr = err - } - } + importModules[modName] = realModName if varName != realModName { return fmt.Sprintf("%s %s = %s", optName, varName, realModName) } else { @@ -158,43 +271,36 @@ func (rt *Runtime) makeImport(code string) (string, int, error) { return str }) - if !rt.required["setTimeout"] && strings.Contains(code, "setTimeout") { - rt.required["setTimeout"] = true - err := rt.VM.Set("setTimeout", func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { - args := MakeArgs(&argsIn, vm).Check(2) - callback := args.Func(0) - timeout := time.Duration(args.Int64(1)) * time.Millisecond - if callback != nil { - go func() { - if timeout > 0 { - time.Sleep(timeout) - } - if _, err := callback(args.This, args.Arguments[2:]...); err != nil { - //panic(vm.NewGoError(err)) - } - }() + if strings.Contains(*code, "setTimeout") { + importModules["setTimeout"] = "setTimeout" + } + return importModules +} + +func (rt *Runtime) setImports(importModules map[string]string) error { + var modErr error + for k, v := range importModules { + if modErr == nil { + if err := rt.requireMod(k, v); err != nil { + modErr = err } - return nil - }) - if err != nil { - modErr = err } } - - return code, importCount, modErr + return modErr } func New() *Runtime { vm := goja.New() - vm.GoData = map[string]any{ - "logger": log.New(u.ShortUniqueId()), - } rt := &Runtime{ - VM: vm, + vm: vm, required: map[string]bool{}, } + vm.GoData = map[string]any{ + "logger": log.New(u.ShortUniqueId()), + } + // 处理模块引用 require.NewRegistryWithLoader(func(path string) ([]byte, error) { modFile := path @@ -215,16 +321,22 @@ func New() *Runtime { return nil, err } } - modCode = importModMatcher.ReplaceAllString(modCode, "let $1 = require('$2')") - modCode, _, _ = rt.makeImport(modCode) + imports := makeImports(&modCode) + if err := rt.setImports(imports); err != nil { + return nil, err + } return []byte(modCode), nil - }).Enable(rt.VM) + }).Enable(rt.vm) return rt } func (rt *Runtime) StartFromFile(file string) error { - return rt.StartFromCode(u.ReadFileN(file), file) + if code, err := u.ReadFile(file); err == nil { + return rt.StartFromCode(code, file) + } else { + return err + } } func (rt *Runtime) StartFromCode(code, refFile string) error { @@ -239,25 +351,29 @@ func (rt *Runtime) StartFromCode(code, refFile string) error { rt.file = absFile } refPath := filepath.Dir(refFile) - rt.VM.GoData["startPath"] = refPath + rt.SetGoData("startFile", refFile) + rt.SetGoData("startPath", refPath) if rt.srcCode == "" { rt.srcCode = code } rt.code = code - // 将 import 转换为 require - rt.code = importModMatcher.ReplaceAllString(rt.code, "let $1 = require('$2')") - // 按需加载引用 - var importCount int + //var importCount int var modErr error - rt.code, importCount, modErr = rt.makeImport(rt.code) - - // 如果没有import,默认import所有 - if modErr == nil && importCount == 0 { - modErr = rt.requireMod("", "") + //rt.code, importCount, modErr = rt.makeImport(rt.code) + imports := makeImports(&rt.code) + if len(imports) == 0 { + modErr = rt.requireAllMod() + } else { + modErr = rt.setImports(imports) } + + //// 如果没有import,默认import所有 + //if modErr == nil && importCount == 0 { + // modErr = rt.requireMod("", "") + //} if modErr != nil { return modErr } @@ -266,9 +382,35 @@ func (rt *Runtime) StartFromCode(code, refFile string) error { // 初始化主函数 if !checkMainMatcher.MatchString(rt.code) { - rt.code = "function main(...args){" + rt.code + "}" + rt.code = "function main(...Args){" + rt.code + "}" } - if _, err := rt.VM.RunScript(rt.file, rt.code); err != nil { + rt.lock() + _, err := rt.vm.RunScript(rt.file, rt.code) + rt.unlock() + if err != nil { + return err + } else { + rt.started = true + return nil + } +} + +func (rt *Runtime) StartFromProgram(plg *Program) error { + var modErr error + if len(plg.imports) == 0 { + modErr = rt.requireAllMod() + } else { + modErr = rt.setImports(plg.imports) + } + if modErr != nil { + return modErr + } + rt.SetGoData("startFile", plg.startFile) + rt.SetGoData("startPath", filepath.Dir(plg.startFile)) + rt.lock() + _, err := rt.vm.RunProgram(plg.prg) + rt.unlock() + if err != nil { return err } else { rt.started = true @@ -291,64 +433,102 @@ func (rt *Runtime) RunMain(args ...any) (any, error) { } } - if err := rt.VM.Set("__args", args); err != nil { + rt.lock() + err := rt.vm.Set("__args", args) + rt.unlock() + if err != nil { return nil, err } - jsResult, err := rt.VM.RunScript("main", "main(...__args)") + rt.lock() + r, err := rt.vm.RunScript("main", "main(...__args)") + rt.unlock() + return makeResult(r, err) +} - var result any - if err == nil { - if jsResult != nil && !jsResult.Equals(goja.Undefined()) { - result = jsResult.Export() - } - } - return result, err +func (rt *Runtime) RunVM(callback func(vm *goja.Runtime) (any, error)) (any, error) { + rt.lock() + r, err := callback(rt.vm) + rt.unlock() + return r, err } func (rt *Runtime) SetGoData(name string, value any) { - rt.VM.GoData[name] = value + rt.dataLock.Lock() + rt.vm.GoData[name] = value + rt.dataLock.Unlock() } func (rt *Runtime) GetGoData(name string) any { - return rt.VM.GoData[name] + rt.dataLock.RLock() + defer rt.dataLock.RUnlock() + return rt.vm.GoData[name] } func (rt *Runtime) Set(name string, value any) error { - return rt.VM.Set(name, value) + rt.lock() + defer rt.unlock() + return rt.vm.Set(name, value) } func (rt *Runtime) SetGlobal(global Map) error { var err error + rt.lock() + defer rt.unlock() for k, v := range global { - if err = rt.VM.Set(k, v); err != nil { + if err = rt.vm.Set(k, v); err != nil { return err } } return nil } +func (rt *Runtime) RunFile(file string) (any, error) { + if code, err := u.ReadFile(file); err == nil { + imports := makeImports(&code) + if err := rt.setImports(imports); err != nil { + return nil, err + } + rt.lock() + r, err := rt.vm.RunScript(file, code) + rt.unlock() + return makeResult(r, err) + } else { + return nil, err + } +} + func (rt *Runtime) RunCode(code string) (any, error) { - //if !rt.started { - // return nil, errors.New("runtime not started") - //} + imports := makeImports(&code) + if err := rt.setImports(imports); err != nil { + return nil, err + } + rt.lock() + r, err := rt.vm.RunScript("anonymous", code) + rt.unlock() + return makeResult(r, err) +} - code = importModMatcher.ReplaceAllString(code, "let $1 = require('$2')") - code, _, _ = rt.makeImport(code) +func (rt *Runtime) RunProgram(prg *Program) (any, error) { + var r goja.Value + err := rt.setImports(prg.imports) + if err == nil { + rt.lock() + r, err = rt.vm.RunProgram(prg.prg) + rt.unlock() + } + return makeResult(r, err) +} - jsResult, err := rt.VM.RunScript(rt.file, code) +func makeResult(r goja.Value, err error) (any, error) { var result any if err == nil { - if jsResult != nil && !jsResult.Equals(goja.Undefined()) { - result = jsResult.Export() + if r != nil && !r.Equals(goja.Undefined()) { + result = r.Export() } } return result, err } -//func RunFile(file string, args ...any) (any, error) { -// return Run(u.ReadFileN(file), file, args...) -//} - type WatchRunner struct { w *watcher.Watcher } diff --git a/gojs_test.go b/gojs_test.go index b3778b6..19fd01d 100644 --- a/gojs_test.go +++ b/gojs_test.go @@ -53,14 +53,16 @@ func (obj *Object) SetPlusFunc2(argsIn goja.FunctionCall, vm *goja.Runtime) goja //obj.plus = conf.Action args := gojs.MakeArgs(&argsIn, vm).Check(1) o := args.Obj(0) - baseNumber := o.Get("baseNumber") - if baseNumber != nil { - obj.baseNumber = int(baseNumber.ToInteger()) - } - action := o.Get("action") - if action != nil { - obj.plus = gojs.GetFunc(action) - } + //baseNumber := o.Get("baseNumber") + //if baseNumber != nil { + // obj.baseNumber = int(baseNumber.ToInteger()) + //} + obj.baseNumber = o.Int("baseNumber") + //action := o.Get("action") + //if action != nil { + // obj.plus = gojs.GetFunc(action) + //} + obj.plus = o.Func("action") return nil } @@ -165,7 +167,7 @@ func init() { // return &Object{id: id} //}, "test1": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { - //args := gojs.MakeArgs(&argsIn, VM).Check(3) + //Args := gojs.MakeArgs(&argsIn, vm).Check(3) return nil }, //"test1": func(id int, id2 uint16, id3 float64) *TestUser { diff --git a/lb.go b/lb.go new file mode 100644 index 0000000..08870ec --- /dev/null +++ b/lb.go @@ -0,0 +1,77 @@ +package gojs + +import ( + "github.com/ssgo/log" + "github.com/ssgo/u" + "runtime" + "sync" +) + +type LB struct { + args []any + pool []*Runtime + logger *log.Logger + debug bool + lock sync.RWMutex + num int +} + +type LBConfig struct { + Num uint + Args []any + Debug bool +} + +func (p *LB) Get() *Runtime { + p.lock.RLock() + i := u.GlobalRand1.Intn(p.num) + rt := p.pool[i] + p.lock.RUnlock() + return rt +} + +func NewLB(plg *Program, opt LBConfig, logger *log.Logger) *LB { + if opt.Num == 0 { + opt.Num = uint(runtime.NumCPU()) + } + if opt.Num > uint(runtime.NumCPU())*2 { + opt.Num = uint(runtime.NumCPU()) * 2 + } + p := &LB{ + pool: make([]*Runtime, opt.Num), + args: opt.Args, + logger: logger, + debug: opt.Debug, + num: int(opt.Num), + } + p.lock.Lock() + for i := 0; i < int(opt.Num); i++ { + rt := New() + err := rt.StartFromProgram(plg) + if err == nil { + _, err = rt.RunMain(p.args...) + } + if err != nil { + p.logger.Error(err.Error()) + } + p.pool[i] = rt + } + p.lock.Unlock() + return p +} + +func NewLBByFile(file string, opt LBConfig, logger *log.Logger) *LB { + if plg, err := CompileFile(file); err == nil { + return NewLB(plg, opt, logger) + } else { + return nil + } +} + +func NewLBByCode(code, refFile string, opt LBConfig, logger *log.Logger) *LB { + if plg, err := CompileCode(code, refFile); err == nil { + return NewLB(plg, opt, logger) + } else { + return nil + } +} diff --git a/makeGoja/go.mod b/makeGoja/go.mod index ca47188..abf078d 100644 --- a/makeGoja/go.mod +++ b/makeGoja/go.mod @@ -6,4 +6,13 @@ toolchain go1.22.5 require github.com/ssgo/u v1.7.7 -require gopkg.in/yaml.v3 v3.0.1 // indirect +require ( + github.com/dlclark/regexp2 v1.11.4 // indirect + github.com/dop251/base64dec v0.0.0-20231022112746-c6c9f9a96217 // indirect + github.com/dop251/goja v0.0.0-20240927123429-241b342198c2 // indirect + github.com/dop251/goja_nodejs v0.0.0-20240728170619-29b559befffc // indirect + github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect + github.com/google/pprof v0.0.0-20241001023024-f4c0cfd0cf1d // indirect + golang.org/x/text v0.19.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/makeGoja/go.sum b/makeGoja/go.sum index cb5f609..05c2e3b 100644 --- a/makeGoja/go.sum +++ b/makeGoja/go.sum @@ -1,5 +1,19 @@ +github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo= +github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dop251/base64dec v0.0.0-20231022112746-c6c9f9a96217 h1:16iT9CBDOniJwFGPI41MbUDfEk74hFaKTqudrX8kenY= +github.com/dop251/base64dec v0.0.0-20231022112746-c6c9f9a96217/go.mod h1:eIb+f24U+eWQCIsj9D/ah+MD9UP+wdxuqzsdLD+mhGM= +github.com/dop251/goja v0.0.0-20240927123429-241b342198c2 h1:Ux9RXuPQmTB4C1MKagNLme0krvq8ulewfor+ORO/QL4= +github.com/dop251/goja v0.0.0-20240927123429-241b342198c2/go.mod h1:MxLav0peU43GgvwVgNbLAj1s/bSGboKkhuULvq/7hx4= +github.com/dop251/goja_nodejs v0.0.0-20240728170619-29b559befffc h1:MKYt39yZJi0Z9xEeRmDX2L4ocE0ETKcHKw6MVL3R+co= +github.com/dop251/goja_nodejs v0.0.0-20240728170619-29b559befffc/go.mod h1:VULptt4Q/fNzQUJlqY/GP3qHyU7ZH46mFkBZe0ZTokU= +github.com/go-sourcemap/sourcemap v2.1.4+incompatible h1:a+iTbH5auLKxaNwQFg0B+TCYl6lbukKPc7b5x0n1s6Q= +github.com/go-sourcemap/sourcemap v2.1.4+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= +github.com/google/pprof v0.0.0-20241001023024-f4c0cfd0cf1d h1:Jaz2JzpQaQXyET0AjLBXShrthbpqMkhGiEfkcQAiAUs= +github.com/google/pprof v0.0.0-20241001023024-f4c0cfd0cf1d/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/ssgo/u v1.7.7 h1:d8PlbLDbDNZw2ILprLQemQ3UBCMEwix/q64j5S3c940= github.com/ssgo/u v1.7.7/go.mod h1:dUG/PBG5k9fSM7SOp8RZLsK0KytNxhtenpoLgjhfxpY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/makeGoja/main.go b/makeGoja/main.go index 32ba3c2..505a3f9 100644 --- a/makeGoja/main.go +++ b/makeGoja/main.go @@ -91,7 +91,8 @@ func replacePackage(root string) { if strings.Contains(str, "github.com/dop251/") { str = strings.ReplaceAll(str, "github.com/dop251/", "apigo.cc/apigo/gojs/dop251/") if strings.HasSuffix(f.FullName, "/dop251/goja/runtime.go") { - str = strings.ReplaceAll(str, "type Runtime struct {", "type Runtime struct {\n\tGoData map[string]any") + str = strings.ReplaceAll(str, "\"strconv\"", "\"strconv\"\n\t\"sync\"") + str = strings.ReplaceAll(str, "type Runtime struct {", "type Runtime struct {\n\tGoData map[string]any\n\tLocker sync.Mutex\n\tCallbackLocker sync.Mutex") } _ = u.WriteFile(f.FullName, str) fmt.Println(u.BGreen("文件更新"), u.Green(f.FullName)) diff --git a/modules/util/util.go b/modules/util/util.go index 2bdf899..8496fa0 100644 --- a/modules/util/util.go +++ b/modules/util/util.go @@ -225,6 +225,21 @@ func init() { } return vm.ToValue(buf.String()) }, + "sleep": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := gojs.MakeArgs(&argsIn, vm).Check(1) + //locked := vm.Locker.TryLock() + //vm.Locker.Unlock() + //locked2 := vm.CallbackLocker.TryLock() + //vm.CallbackLocker.Unlock() + time.Sleep(time.Duration(args.Int64(0)) * time.Millisecond) + //if !locked { + // vm.Locker.Lock() + //} + //if !locked2 { + // vm.CallbackLocker.Lock() + //} + return nil + }, "shell": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { args := gojs.MakeArgs(&argsIn, vm).Check(1) a := make([]string, len(args.Arguments)-1) diff --git a/modules/util/util.ts b/modules/util/util.ts index cf4485f..dde07a4 100644 --- a/modules/util/util.ts +++ b/modules/util/util.ts @@ -26,6 +26,7 @@ export default { sha256, sha512, tpl, + sleep, shell, toDatetime, fromDatetime, @@ -58,6 +59,7 @@ function sha1(data:any): string {return ''} function sha256(data:any): string {return ''} function sha512(data:any): string {return ''} function tpl(text:string, data:any, functions?:Object): string {return ''} +function sleep(ms:number): void {} function shell(cmd:string, ...args:string[]): string[] {return []} function toDatetime(timestamp:number): string {return ''} function fromDatetime(datetimeStr:string): number {return 0} diff --git a/pool.go b/pool.go new file mode 100644 index 0000000..723ea8d --- /dev/null +++ b/pool.go @@ -0,0 +1,209 @@ +package gojs + +import ( + "container/list" + "fmt" + "github.com/ssgo/log" + "github.com/ssgo/u" + "sync" +) + +type Pool struct { + plg *Program + args []any + pool *list.List + lock sync.Mutex + cond *sync.Cond + //waitChan chan bool + min uint + max uint + idle uint + total uint + maxTotal uint + free uint + waiting uint + maxWaiting uint + createTimes uint + logger *log.Logger + debug bool +} + +type PoolConfig struct { + Min, Idle, Max uint + Args []any + Debug bool +} + +func (p *Pool) Count() (total, maxTotal, maxWaiting, createTimes uint) { + p.lock.Lock() + defer p.lock.Unlock() + return p.total, p.maxTotal, p.maxWaiting, p.createTimes +} + +func (p *Pool) Get() *Runtime { + //t1 := time.Now().UnixMicro() + p.lock.Lock() + waiting := false + //fmt.Println(u.BBlue("check wait"), p.total, p.max, p.free) + if p.max != 0 && p.free == 0 && p.total > p.max { + waiting = true + p.waiting++ + if p.maxWaiting < p.waiting { + p.maxWaiting = p.waiting + } + if p.debug { + p.logger.Info("[go js pool] waiting " + fmt.Sprintln(u.BYellow(p.total), u.BYellow(p.pool.Len()), u.BMagenta(p.free), u.Dim(p.waiting))) + } + for p.free == 0 { + p.cond.Wait() + } + //p.lock.Unlock() + //<-p.waitChan + //p.lock.Lock() + if p.debug { + p.logger.Info("[go js pool] wait over " + fmt.Sprintln(u.BYellow(p.total), u.BYellow(p.pool.Len()), u.BMagenta(p.free), u.Dim(p.waiting))) + } + } + + var rt *Runtime + item := p.pool.Front() + if item != nil { + p.pool.Remove(item) + if rt1, ok := item.Value.(*Runtime); ok { + rt = rt1 + } + } + if rt != nil { + p.free-- + if waiting { + p.waiting-- + } + if p.debug { + p.logger.Info("[go js pool] get " + fmt.Sprintln(u.BYellow(p.total), u.BYellow(p.pool.Len()), u.BMagenta(p.free), u.Dim(p.waiting))) + } + } else { + //fmt.Println("new", p.total, p.free) + rt = p.new() + p.total++ + p.createTimes++ + if p.maxTotal < p.total { + p.maxTotal = p.total + } + if p.debug { + p.logger.Info("[go js pool] new " + fmt.Sprintln(u.BYellow(p.total), u.BYellow(p.pool.Len()), u.BMagenta(p.free), u.Dim(p.waiting))) + } + } + p.lock.Unlock() + //t2 := time.Now().UnixMicro() - t1 + //fmt.Println("get", u.BRed(t2)) + return rt +} + +func (p *Pool) Put(rt *Runtime) { + p.lock.Lock() + //t1 := time.Now().UnixMicro() + if p.idle != 0 && p.free >= p.idle { + p.total-- + if p.debug { + p.logger.Info("[go js pool] put out " + fmt.Sprintln(u.BYellow(p.total), u.BYellow(p.pool.Len()), u.BMagenta(p.free), u.Dim(p.waiting))) + } + p.lock.Unlock() + + //r, err := rt.RunCode("Object.keys(globalThis)") + //if vars, ok := r.([]any); ok { + // for _, k := range vars { + // _ = rt.vm.Set(u.String(k), goja.Undefined()) + // } + // r, err = rt.RunCode("Object.keys(globalThis)") + //} + //rt.RunCode("for(let k of Object.keys(globalThis)) delete globalThis[k]") + //r, err := rt.RunCode("Object.keys(globalThis)") + //fmt.Println(r, err) + //rt.Free() + return + } + p.pool.PushBack(rt) + p.free++ + if p.debug { + p.logger.Info("[go js pool] put " + fmt.Sprintln(u.BYellow(p.total), u.BYellow(p.pool.Len()), u.BMagenta(p.free), u.Dim(p.waiting))) + } + //t2 := time.Now().UnixMicro() - t1 + //fmt.Println("put", u.BYellow(t2)) + p.lock.Unlock() + if p.waiting > 0 { + p.cond.Signal() + //p.waitChan <- true + } +} + +func (p *Pool) new() *Runtime { + rt := New() + err := rt.StartFromProgram(p.plg) + if err == nil { + _, err = rt.RunMain(p.args...) + } + if err != nil { + p.logger.Error(err.Error()) + return nil + } + return rt +} + +func NewPool(plg *Program, opt PoolConfig, logger *log.Logger) *Pool { + if opt.Min == 0 { + opt.Min = 1 + } + if opt.Max != 0 && opt.Max < opt.Min { + opt.Max = opt.Min + } + if opt.Idle != 0 && opt.Idle < opt.Min { + opt.Idle = opt.Min + } + if opt.Idle != 0 && opt.Idle > opt.Max { + opt.Idle = opt.Max + } + p := &Pool{ + pool: list.New(), + plg: plg, + args: opt.Args, + min: opt.Min, + max: opt.Max, + idle: opt.Idle, + logger: logger, + debug: opt.Debug, + } + p.cond = sync.NewCond(&p.lock) + //p.waitChan = make(chan bool, opt.Max*10) + p.lock.Lock() + //t1 := time.Now().UnixMicro() + for i := 0; i < int(opt.Min); i++ { + item := p.new() + p.pool.PushBack(item) + p.total++ + p.free++ + p.createTimes++ + } + //t2 := time.Now().UnixMicro() - t1 + //fmt.Println("init", u.BBlue(t2)) + p.lock.Unlock() + if p.debug { + p.logger.Info("[go js pool] init " + fmt.Sprintln(u.BYellow(p.total), u.BYellow(p.pool.Len()), u.BMagenta(p.free), u.Dim(p.waiting))) + } + return p +} + +func NewPoolByFile(file string, opt PoolConfig, logger *log.Logger) *Pool { + if plg, err := CompileFile(file); err == nil { + return NewPool(plg, opt, logger) + } else { + return nil + } +} + +func NewPoolByCode(code, refFile string, opt PoolConfig, logger *log.Logger) *Pool { + if plg, err := CompileCode(code, refFile); err == nil { + return NewPool(plg, opt, logger) + } else { + return nil + } +}