perf(goja): refactor promise handling and implement program caching
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user