From 3c9684526add85c893adc05cede8307699e20e2d Mon Sep 17 00:00:00 2001 From: ExplodingDragon Date: Sat, 31 Jan 2026 23:50:46 +0800 Subject: [PATCH] perf(goja): refactor promise handling and implement program caching --- pkg/filters/goja/goja.go | 74 ++++++++++++++++++++--------------- pkg/filters/goja/var_fetch.go | 6 +-- 2 files changed, 45 insertions(+), 35 deletions(-) diff --git a/pkg/filters/goja/goja.go b/pkg/filters/goja/goja.go index 947789d..3259bce 100644 --- a/pkg/filters/goja/goja.go +++ b/pkg/filters/goja/goja.go @@ -1,7 +1,9 @@ package goja import ( + "crypto/md5" "errors" + "fmt" "io" "net/http" "path/filepath" @@ -14,10 +16,19 @@ import ( "github.com/dop251/goja_nodejs/eventloop" "github.com/dop251/goja_nodejs/require" "github.com/dop251/goja_nodejs/url" + lru "github.com/hashicorp/golang-lru/v2" "gopkg.d7z.net/gitea-pages/pkg/core" ) -// todo: 新增超时配置 +var programCache *lru.Cache[string, *goja.Program] + +func init() { + var err error + programCache, err = lru.New[string, *goja.Program](1024) + if err != nil { + panic(err) + } +} func FilterInstGoJa(gl core.Params) (core.FilterInstance, error) { var global struct { @@ -29,6 +40,9 @@ func FilterInstGoJa(gl core.Params) (core.FilterInstance, error) { if err := gl.Unmarshal(&global); err != nil { return nil, err } + sharedClient := &http.Client{ + Timeout: 30 * time.Second, + } return func(config core.Params) (core.FilterCall, error) { var param struct { Exec string `json:"exec"` @@ -48,10 +62,18 @@ func FilterInstGoJa(gl core.Params) (core.FilterInstance, error) { debug := NewDebug(global.EnableDebug && param.Debug && request.URL.Query(). Get("debug") == "true", request, w) - program, err := goja.Compile(param.Exec, js, false) - if err != nil { - return debug.Flush(err) + + hash := md5.Sum([]byte(js)) + cacheKey := fmt.Sprintf("%s:%x", param.Exec, hash) + program, ok := programCache.Get(cacheKey) + if !ok { + program, err = goja.Compile(param.Exec, js, false) + if err != nil { + return debug.Flush(err) + } + programCache.Add(cacheKey, program) } + registry := newRegistry(ctx, debug) jsLoop := eventloop.NewEventLoop(eventloop.WithRegistry(registry), eventloop.EnableConsole(true)) @@ -63,9 +85,12 @@ func FilterInstGoJa(gl core.Params) (core.FilterInstance, error) { defer closers.Close() stop := make(chan error, 1) - defer close(stop) jsLoop.RunOnLoop(func(vm *goja.Runtime) { + go func() { + <-ctx.Done() + vm.Interrupt("context done") + }() err := func() error { url.Enable(vm) buffer.Enable(vm) @@ -84,7 +109,7 @@ func FilterInstGoJa(gl core.Params) (core.FilterInstance, error) { if err = EventInject(ctx, vm, jsLoop); err != nil { return err } - if err = FetchInject(vm, jsLoop); err != nil { + if err = FetchInject(ctx, vm, jsLoop, sharedClient); err != nil { return err } if global.EnableWebsocket { @@ -106,34 +131,19 @@ func FilterInstGoJa(gl core.Params) (core.FilterInstance, error) { stop <- err return } - export := result.Export() - if export != nil { - if promise, ok := export.(*goja.Promise); ok { - go func() { - for { - switch promise.State() { - case goja.PromiseStateFulfilled: - stop <- nil - return - case goja.PromiseStateRejected: - switch data := promise.Result().Export().(type) { - case error: - stop <- data - default: - stop <- errors.New(promise.Result().String()) - } - return - default: - time.Sleep(time.Millisecond * 5) - } - } - }() - } else { - stop <- nil + if result != nil { + if _, ok := result.Export().(*goja.Promise); ok { + vm.Set("__internal_resolve", func(goja.Value) { stop <- nil }) + vm.Set("__internal_reject", func(reason goja.Value) { stop <- errors.New(reason.String()) }) + vm.Set("__internal_promise", result) + _, err := vm.RunString(`__internal_promise.then(__internal_resolve).catch(__internal_reject)`) + if err != nil { + stop <- err + } + return } - } else { - stop <- nil } + stop <- nil }) resultErr := <-stop return debug.Flush(resultErr) diff --git a/pkg/filters/goja/var_fetch.go b/pkg/filters/goja/var_fetch.go index de86d3a..9009d2b 100644 --- a/pkg/filters/goja/var_fetch.go +++ b/pkg/filters/goja/var_fetch.go @@ -1,6 +1,7 @@ package goja import ( + "context" "encoding/json" "io" "net/http" @@ -10,7 +11,7 @@ import ( "github.com/dop251/goja_nodejs/eventloop" ) -func FetchInject(jsCtx *goja.Runtime, loop *eventloop.EventLoop) error { +func FetchInject(ctx context.Context, jsCtx *goja.Runtime, loop *eventloop.EventLoop, client *http.Client) error { return jsCtx.GlobalObject().Set("fetch", func(url string, options ...map[string]interface{}) *goja.Promise { promise, resolve, reject := jsCtx.NewPromise() @@ -36,7 +37,7 @@ func FetchInject(jsCtx *goja.Runtime, loop *eventloop.EventLoop) error { } } - req, err := http.NewRequest(method, url, body) + req, err := http.NewRequestWithContext(ctx, method, url, body) if err != nil { loop.RunOnLoop(func(*goja.Runtime) { _ = reject(err) @@ -45,7 +46,6 @@ func FetchInject(jsCtx *goja.Runtime, loop *eventloop.EventLoop) error { } req.Header = headers - client := &http.Client{} resp, err := client.Do(req) if err != nil { loop.RunOnLoop(func(*goja.Runtime) {