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

View File

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