many update

support pool
This commit is contained in:
Star 2024-10-07 23:02:11 +08:00
parent 205c4f20dc
commit f37b64c2df
13 changed files with 814 additions and 137 deletions

117
args.go
View File

@ -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
}

50
common.go Normal file
View File

@ -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
}

View File

@ -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

10
go.mod
View File

@ -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
)

418
gojs.go
View File

@ -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
}

View File

@ -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 {

77
lb.go Normal file
View File

@ -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
}
}

View File

@ -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
)

View File

@ -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=

View File

@ -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))

View File

@ -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)

View File

@ -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}

209
pool.go Normal file
View File

@ -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
}
}