Skip to content

Commit

Permalink
jsvm: add a timeout and use it to kill the VM
Browse files Browse the repository at this point in the history
Unfortunately, the only way to stop/kill Otto is via a panic, which
makes this code more complicated than it should be.

Make the Timeout be 5 seconds for now. It's high enough that no sane
middleware would hit it, but low enough to not let goroutine leaks build
up easily.

Fixes #496.
  • Loading branch information
mvdan authored and buger committed May 2, 2017
1 parent 4087896 commit 9694268
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 3 deletions.
45 changes: 42 additions & 3 deletions plugins.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,10 +141,45 @@ func (d *DynamicMiddleware) ProcessRequest(w http.ResponseWriter, r *http.Reques
// Run the middleware
middlewareClassname := d.MiddlewareClassName
vm := d.Spec.JSVM.VM.Copy()
vm.Interrupt = make(chan func(), 1)
log.WithFields(logrus.Fields{
"prefix": "jsvm",
}).Debug("Running: ", middlewareClassname)
returnRaw, _ := vm.Run(middlewareClassname + `.DoProcessRequest(` + string(asJsonRequestObj) + `, ` + string(sessionAsJsonObj) + `);`)
ret := make(chan otto.Value)
go func() {
defer func() {
// the VM executes the panic func that gets it
// to stop, so we must recover here to not crash
// the whole Go program.
recover()
// send a dummy value to the ret channel to
// signal that we died, since a panic will mean
// the regular send won't happen.
ret <- otto.Value{}
}()
returnRaw, _ := vm.Run(middlewareClassname + `.DoProcessRequest(` + string(asJsonRequestObj) + `, ` + string(sessionAsJsonObj) + `);`)
ret <- returnRaw
}()
var returnRaw otto.Value
t := time.NewTimer(d.Spec.JSVM.Timeout)
select {
case returnRaw = <-ret:
t.Stop()
case <-t.C:
t.Stop()
log.WithFields(logrus.Fields{
"prefix": "jsvm",
}).Error("JS middleware timed out after ", d.Spec.JSVM.Timeout)
vm.Interrupt <- func() {
// only way to stop the VM is to send it a func
// that panics.
panic("stop")
}
// wait for the vm goroutine to die, ensuring that we
// have no goroutine leak.
<-ret
return nil, 200
}
returnDataStr, _ := returnRaw.ToString()

// Decode the return object
Expand Down Expand Up @@ -222,10 +257,12 @@ func (d *DynamicMiddleware) ProcessRequest(w http.ResponseWriter, r *http.Reques
// --- Utility functions during startup to ensure a sane VM is present for each API Def ----

type JSVM struct {
VM *otto.Otto
VM *otto.Otto
Timeout time.Duration
}

// Init creates the JSVM with the core library (tyk.js)
// Init creates the JSVM with the core library (tyk.js) and sets up a
// default timeout.
func (j *JSVM) Init() {
vm := otto.New()

Expand All @@ -237,6 +274,8 @@ func (j *JSVM) Init() {

// Add environment API
j.LoadTykJSApi()

j.Timeout = 5 * time.Second
}

// LoadJSPaths will load JS classes and functionality in to the VM by file
Expand Down
1 change: 1 addition & 0 deletions plugins_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ func TestJSVMProcessTimeout(t *testing.T) {
req := httptest.NewRequest("GET", "/foo", strings.NewReader("body"))
jsvm := &JSVM{}
jsvm.Init()
jsvm.Timeout = time.Millisecond

// this js plugin just loops forever, keeping Otto at 100% CPU
// usage and running forever.
Expand Down

0 comments on commit 9694268

Please sign in to comment.