diff --git a/ottomain.go b/ottomain.go index 0a6daf4..6a90e09 100644 --- a/ottomain.go +++ b/ottomain.go @@ -20,6 +20,7 @@ type otto_config struct { var ( once sync.Once semaphore chan struct{} + globalConfig otto_config configChecked bool configMutex sync.Mutex ) @@ -57,16 +58,18 @@ func initSemaphore() { if !configChecked { var cfg otto_config if err := env.Parse(&cfg); err != nil { - // Use default if env parse fails + // Use defaults if env parse fails cfg.MaxConcurrent = 50 + cfg.Timeout = 2 } + globalConfig = cfg semaphore = make(chan struct{}, cfg.MaxConcurrent) configChecked = true } } func ProcessRequest(script string, params map[string]interface{}) (response map[string]interface{}, err error) { - // Initialize semaphore once + // Initialize semaphore and config once once.Do(initSemaphore) // Acquire semaphore to limit concurrent executions @@ -78,10 +81,8 @@ func ProcessRequest(script string, params map[string]interface{}) (response map[ return nil, fmt.Errorf("too many concurrent JavaScript executions, please try again later") } - var cfg otto_config - if err := env.Parse(&cfg); err != nil { - return nil, fmt.Errorf("env vars parse error: %w", err) - } + // Use cached config instead of parsing every time + cfg := globalConfig vm := otto.New() @@ -99,28 +100,40 @@ func ProcessRequest(script string, params map[string]interface{}) (response map[ vm.Interrupt = make(chan func(), 1) timeoutDone := make(chan struct{}) + timeoutTimer := time.NewTimer(time.Duration(cfg.Timeout) * time.Second) + // Start timeout goroutine go func() { select { - case <-time.After(time.Duration(cfg.Timeout) * time.Second): + case <-timeoutTimer.C: + // Timer expired, try to interrupt select { case vm.Interrupt <- func() { panic(errors.New("some code took to long! Stopping after timeout")) }: + // Interrupt sent successfully case <-timeoutDone: - // Script completed before timeout, exit gracefully + // Script completed before timeout, timer already stopped return } case <-timeoutDone: - // Script completed before timeout, exit gracefully + // Script completed before timeout, stop timer + if !timeoutTimer.Stop() { + <-timeoutTimer.C + } return } }() defer func() { - close(timeoutDone) // Signal timeout goroutine to exit - // Clean up vm to help with garbage collection - vm = nil + // Signal timeout goroutine to exit and stop timer + close(timeoutDone) + if !timeoutTimer.Stop() { + select { + case <-timeoutTimer.C: + default: + } + } if r := recover(); r != nil { switch x := r.(type) { case error: