perf(goja): refactor promise handling and implement program caching

This commit is contained in:
ExplodingDragon
2026-01-31 23:50:46 +08:00
parent 74939887c3
commit 3c9684526a
2 changed files with 45 additions and 35 deletions

View File

@@ -1,7 +1,9 @@
package goja package goja
import ( import (
"crypto/md5"
"errors" "errors"
"fmt"
"io" "io"
"net/http" "net/http"
"path/filepath" "path/filepath"
@@ -14,10 +16,19 @@ import (
"github.com/dop251/goja_nodejs/eventloop" "github.com/dop251/goja_nodejs/eventloop"
"github.com/dop251/goja_nodejs/require" "github.com/dop251/goja_nodejs/require"
"github.com/dop251/goja_nodejs/url" "github.com/dop251/goja_nodejs/url"
lru "github.com/hashicorp/golang-lru/v2"
"gopkg.d7z.net/gitea-pages/pkg/core" "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) { func FilterInstGoJa(gl core.Params) (core.FilterInstance, error) {
var global struct { var global struct {
@@ -29,6 +40,9 @@ func FilterInstGoJa(gl core.Params) (core.FilterInstance, error) {
if err := gl.Unmarshal(&global); err != nil { if err := gl.Unmarshal(&global); err != nil {
return nil, err return nil, err
} }
sharedClient := &http.Client{
Timeout: 30 * time.Second,
}
return func(config core.Params) (core.FilterCall, error) { return func(config core.Params) (core.FilterCall, error) {
var param struct { var param struct {
Exec string `json:"exec"` 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(). debug := NewDebug(global.EnableDebug && param.Debug && request.URL.Query().
Get("debug") == "true", request, w) Get("debug") == "true", request, w)
program, err := goja.Compile(param.Exec, js, false)
if err != nil { hash := md5.Sum([]byte(js))
return debug.Flush(err) 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) registry := newRegistry(ctx, debug)
jsLoop := eventloop.NewEventLoop(eventloop.WithRegistry(registry), jsLoop := eventloop.NewEventLoop(eventloop.WithRegistry(registry),
eventloop.EnableConsole(true)) eventloop.EnableConsole(true))
@@ -63,9 +85,12 @@ func FilterInstGoJa(gl core.Params) (core.FilterInstance, error) {
defer closers.Close() defer closers.Close()
stop := make(chan error, 1) stop := make(chan error, 1)
defer close(stop)
jsLoop.RunOnLoop(func(vm *goja.Runtime) { jsLoop.RunOnLoop(func(vm *goja.Runtime) {
go func() {
<-ctx.Done()
vm.Interrupt("context done")
}()
err := func() error { err := func() error {
url.Enable(vm) url.Enable(vm)
buffer.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 { if err = EventInject(ctx, vm, jsLoop); err != nil {
return err return err
} }
if err = FetchInject(vm, jsLoop); err != nil { if err = FetchInject(ctx, vm, jsLoop, sharedClient); err != nil {
return err return err
} }
if global.EnableWebsocket { if global.EnableWebsocket {
@@ -106,34 +131,19 @@ func FilterInstGoJa(gl core.Params) (core.FilterInstance, error) {
stop <- err stop <- err
return return
} }
export := result.Export() if result != nil {
if export != nil { if _, ok := result.Export().(*goja.Promise); ok {
if promise, ok := export.(*goja.Promise); ok { vm.Set("__internal_resolve", func(goja.Value) { stop <- nil })
go func() { vm.Set("__internal_reject", func(reason goja.Value) { stop <- errors.New(reason.String()) })
for { vm.Set("__internal_promise", result)
switch promise.State() { _, err := vm.RunString(`__internal_promise.then(__internal_resolve).catch(__internal_reject)`)
case goja.PromiseStateFulfilled: if err != nil {
stop <- nil stop <- err
return }
case goja.PromiseStateRejected: return
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
} }
} else {
stop <- nil
} }
stop <- nil
}) })
resultErr := <-stop resultErr := <-stop
return debug.Flush(resultErr) return debug.Flush(resultErr)

View File

@@ -1,6 +1,7 @@
package goja package goja
import ( import (
"context"
"encoding/json" "encoding/json"
"io" "io"
"net/http" "net/http"
@@ -10,7 +11,7 @@ import (
"github.com/dop251/goja_nodejs/eventloop" "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 { return jsCtx.GlobalObject().Set("fetch", func(url string, options ...map[string]interface{}) *goja.Promise {
promise, resolve, reject := jsCtx.NewPromise() 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 { if err != nil {
loop.RunOnLoop(func(*goja.Runtime) { loop.RunOnLoop(func(*goja.Runtime) {
_ = reject(err) _ = reject(err)
@@ -45,7 +46,6 @@ func FetchInject(jsCtx *goja.Runtime, loop *eventloop.EventLoop) error {
} }
req.Header = headers req.Header = headers
client := &http.Client{}
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
loop.RunOnLoop(func(*goja.Runtime) { loop.RunOnLoop(func(*goja.Runtime) {