Skip to content

Commit

Permalink
Merge pull request YaoApp#170 from trheyi/main
Browse files Browse the repository at this point in the history
[fix] Runtime Memory Leak and Optimize API Invocation Logic
  • Loading branch information
trheyi authored Dec 20, 2023
2 parents 8d855b9 + 6ea46cf commit 7427b26
Show file tree
Hide file tree
Showing 8 changed files with 379 additions and 106 deletions.
116 changes: 84 additions & 32 deletions api/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,49 +22,66 @@ import (
func (path Path) defaultHandler(getArgs func(c *gin.Context) []interface{}) func(c *gin.Context) {
return func(c *gin.Context) {

ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// defer debug.FreeOSMemory()

path.setPayload(c)
var resp interface{} = path.runProcess(c, getArgs)
var status int = path.Out.Status
var contentType = path.reqContentType(c)

if resp == nil {
c.Done()
return
}
chRes := make(chan interface{}, 1)
go path.execProcess(ctx, chRes, c, getArgs)

// Response Headers
contentType = path.setResponseHeaders(c, resp, contentType)

// Format Body
body := resp
if path.Out.Body != nil {
res := any.Of(resp)
if res.IsMap() {
data := res.Map().MapStrAny.Dot()
body = helper.Bind(path.Out.Body, data)
select {
case resp := <-chRes:
close(chRes)
if resp == nil {
c.Done()
return
}
}

switch data := body.(type) {
case maps.Map, map[string]interface{}, []interface{}, []maps.Map, []map[string]interface{}:
c.JSON(status, data)
c.Done()
return
// Set ContentType
contentType = path.setResponseHeaders(c, resp, contentType)

case []byte:
c.Data(status, contentType, data)
c.Done()
return
// Format Body
body := resp
if path.Out.Body != nil {
res := any.Of(resp)
if res.IsMap() {
data := res.Map().MapStrAny.Dot()
body = helper.Bind(path.Out.Body, data)
}
}

switch data := body.(type) {
case maps.Map, map[string]interface{}, []interface{}, []maps.Map, []map[string]interface{}:
c.JSON(status, data)
c.Done()
return

case []byte:
c.Data(status, contentType, data)
c.Done()
return

case error:
c.JSON(500, gin.H{"message": data.Error(), "code": 500})

default:
if strings.HasPrefix(contentType, "application/json") {
c.JSON(status, body)
c.Done()
return
}

default:
if strings.HasPrefix(contentType, "application/json") {
c.JSON(status, body)
c.String(status, "%v", body)
c.Done()
return
}

c.String(status, "%v", body)
c.Done()
case <-c.Request.Context().Done():
c.Abort()
return
}
}
Expand All @@ -86,11 +103,14 @@ func (path Path) processHandler(getArgs func(c *gin.Context) []interface{}) func
func (path Path) redirectHandler(getArgs func(c *gin.Context) []interface{}) func(c *gin.Context) {
return func(c *gin.Context) {

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

path.setPayload(c)
contentType := path.reqContentType(c)

// run process
resp := path.runProcess(c, getArgs)
resp := path.runProcess(ctx, c, getArgs)

// Response Headers
path.setResponseHeaders(c, resp, contentType)
Expand Down Expand Up @@ -230,7 +250,38 @@ func (path Path) runStreamScript(ctx context.Context, c *gin.Context, getArgs fu
}
}

func (path Path) runProcess(c *gin.Context, getArgs func(c *gin.Context) []interface{}) interface{} {
func (path Path) execProcess(ctx context.Context, chRes chan<- interface{}, c *gin.Context, getArgs func(c *gin.Context) []interface{}) {
var args []interface{} = getArgs(c)
var process, err = process.Of(path.Process, args...)
if err != nil {
log.Error("[Path] %s %s", path.Path, err.Error())
chRes <- err
}

if sid, has := c.Get("__sid"); has { // 设定会话ID
if sid, ok := sid.(string); ok {
process.WithSID(sid)
}
}

if global, has := c.Get("__global"); has { // 设定全局变量
if global, ok := global.(map[string]interface{}); ok {
process.WithGlobal(global)
}
}

process.WithContext(ctx)
res, err := process.Exec()
if err != nil {
fmt.Println("err", err)
log.Error("[Path] %s %s", path.Path, err.Error())
chRes <- nil
return
}
chRes <- res
}

func (path Path) runProcess(ctx context.Context, c *gin.Context, getArgs func(c *gin.Context) []interface{}) interface{} {
var args []interface{} = getArgs(c)
var process = process.New(path.Process, args...)

Expand All @@ -246,6 +297,7 @@ func (path Path) runProcess(c *gin.Context, getArgs func(c *gin.Context) []inter
}
}

process.WithContext(ctx)
return process.Run()
}

Expand Down
7 changes: 7 additions & 0 deletions process/process.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package process

import (
"context"
"fmt"
"strings"

Expand Down Expand Up @@ -91,6 +92,12 @@ func (process *Process) WithGlobal(global map[string]interface{}) *Process {
return process
}

// WithContext set the context
func (process *Process) WithContext(ctx context.Context) *Process {
process.Context = ctx
return process
}

// handler get the process handler
func (process *Process) handler() (Handler, error) {
if hander, has := Handlers[process.Handler]; has && hander != nil {
Expand Down
3 changes: 3 additions & 0 deletions process/process.types.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package process

import "context"

// Process the process sturct
type Process struct {
Name string
Expand All @@ -10,6 +12,7 @@ type Process struct {
Args []interface{}
Global map[string]interface{} // Global vars
Sid string // Session ID
Context context.Context
}

// Handler the process handler
Expand Down
44 changes: 3 additions & 41 deletions runtime/v8/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,50 +3,11 @@ package v8
import (
"context"
"fmt"
"time"

"github.com/yaoapp/gou/runtime/v8/bridge"
"rogchap.com/v8go"
)

// NewContext create a new context
func (script *Script) NewContext(sid string, global map[string]interface{}) (*Context, error) {

timeout := script.Timeout
if timeout == 0 {
timeout = 100 * time.Millisecond
}

iso, err := SelectIso(timeout)
if err != nil {
return nil, err
}

var context *v8go.Context
var has bool

// load from cache
context, has = iso.contexts[script]

// re-compile and save to cache
if !has {
context, err = script.Compile(iso, timeout)
if err != nil {
iso.Unlock() // unlock iso
return nil, err
}
}

return &Context{
ID: script.ID,
Context: context,
SID: sid,
Data: global,
Root: script.Root,
Iso: iso,
}, nil
}

// Call call the script function
func (ctx *Context) Call(method string, args ...interface{}) (interface{}, error) {

Expand Down Expand Up @@ -170,8 +131,9 @@ func (ctx *Context) setData(global *v8go.Object) (*v8go.Value, error) {

// Close Context
func (ctx *Context) Close() error {
defer ctx.Iso.Unlock()
ctx.Context.Close()
ctx.Context = nil
ctx.Data = nil
ctx.SID = ""
return nil
return ctx.Iso.Unlock()
}
Loading

0 comments on commit 7427b26

Please sign in to comment.