main v0.0.10
parent baa9bc1ecc
commit 6b176cd294
  1. 37
      ottomain.go

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

Loading…
Cancel
Save