Compare commits

..

2 Commits

  1. 58
      ottomain.go

@ -1,6 +1,7 @@
package ottoman package ottoman
import ( import (
"context"
"encoding/base64" "encoding/base64"
"errors" "errors"
"fmt" "fmt"
@ -99,40 +100,48 @@ func ProcessRequest(script string, params map[string]interface{}) (response map[
} }
vm.Interrupt = make(chan func(), 1) vm.Interrupt = make(chan func(), 1)
timeoutDone := make(chan struct{}) timeoutCtx, timeoutCancel := context.WithCancel(context.Background())
timeoutTimer := time.NewTimer(time.Duration(cfg.Timeout) * time.Second) timeoutTimer := time.NewTimer(time.Duration(cfg.Timeout) * time.Second)
// Start timeout goroutine // Start timeout goroutine
go func() { go func() {
select { select {
case <-timeoutTimer.C: case <-timeoutTimer.C:
// Timer expired, try to interrupt // Timer expired, try to interrupt (non-blocking)
select { select {
case vm.Interrupt <- func() { case vm.Interrupt <- func() {
panic(errors.New("some code took to long! Stopping after timeout")) panic(errors.New("some code took to long! Stopping after timeout"))
}: }:
// Interrupt sent // Interrupt sent successfully
case <-timeoutDone: case <-timeoutCtx.Done():
// Script already completed // Script already completed, cancel prevented interrupt
}
case <-timeoutDone:
// Script completed before timeout, ensure timer is stopped
timeoutTimer.Stop()
// Drain timer channel if needed (non-blocking)
select {
case <-timeoutTimer.C:
default: default:
// Can't send interrupt (channel full), but context cancelled
// Just exit - script might be done anyway
}
case <-timeoutCtx.Done():
// Script completed before timeout, stop timer
if !timeoutTimer.Stop() {
// Timer already fired, drain it
select {
case <-timeoutTimer.C:
default:
}
} }
} }
}() }()
defer func() { defer func() {
// Signal timeout goroutine to exit by closing channel // Cancel timeout context to signal goroutine to exit
close(timeoutDone) timeoutCancel()
// Stop timer - this is safe even if it already fired // Stop timer - this is safe even if it already fired
// We don't need to drain the channel here as goroutine handles it
timeoutTimer.Stop() timeoutTimer.Stop()
// Drain timer channel to prevent goroutine leak
select {
case <-timeoutTimer.C:
default:
}
if r := recover(); r != nil { if r := recover(); r != nil {
switch x := r.(type) { switch x := r.(type) {
@ -151,10 +160,7 @@ func ProcessRequest(script string, params map[string]interface{}) (response map[
return nil, fmt.Errorf("otto run error: %w", err) return nil, fmt.Errorf("otto run error: %w", err)
} }
getOttoValue := func(variable string, err error) (interface{}, error) { getOttoValue := func(variable string) (interface{}, error) {
if err != nil {
return nil, err
}
value, err := vm.Get(variable) value, err := vm.Get(variable)
if err != nil { if err != nil {
return nil, err return nil, err
@ -169,9 +175,9 @@ func ProcessRequest(script string, params map[string]interface{}) (response map[
response = make(map[string]interface{}) response = make(map[string]interface{})
for v := range params { for v := range params {
message, err := getOttoValue(v, err) message, getErr := getOttoValue(v)
if err != nil { if getErr != nil {
return nil, fmt.Errorf("otto get value error: %w", err) return nil, fmt.Errorf("otto get value error for '%s': %w", v, getErr)
} }
switch message := message.(type) { switch message := message.(type) {
@ -184,10 +190,14 @@ func ProcessRequest(script string, params map[string]interface{}) (response map[
rt := reflect.TypeOf(message) rt := reflect.TypeOf(message)
switch rt.Kind() { switch rt.Kind() {
case reflect.Slice, reflect.Array: case reflect.Slice, reflect.Array:
response[v] = []byte(message.([]byte)) // Safe type assertion for byte slice
if byteSlice, ok := message.([]byte); ok {
response[v] = byteSlice
} else {
response[v] = message
}
default: default:
response[v] = message response[v] = message
//response[v] = nil
} }
} }

Loading…
Cancel
Save