Files
synapbus/internal/jsruntime/pool.go
T
Algis DumbrisandClaude Opus 4.6 e8165511de feat: add sandboxed JS/TS runtime engine (internal/jsruntime)
Pure Go JavaScript/TypeScript execution engine using goja + esbuild.
Provides sandboxed code execution with a call() bridge for agent
actions, automatic TypeScript detection and transpilation, timeout
enforcement, max-calls limits, and a concurrent execution pool.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 07:53:25 +02:00

84 lines
2.0 KiB
Go

package jsruntime
import (
"context"
"fmt"
"sync"
)
// Pool manages a pool of reusable goja VMs for concurrent JavaScript execution.
// Each Execute call acquires a VM from the pool, runs the code, then releases
// the VM back. If all VMs are in use, callers block until one becomes available
// or the context is cancelled.
type Pool struct {
size int
available chan struct{} // semaphore — each token represents a "slot"
mu sync.Mutex
closed bool
}
// NewPool creates a new runtime pool with the given concurrency limit.
// The size determines how many concurrent Execute calls can run simultaneously.
func NewPool(size int) *Pool {
if size < 1 {
size = 1
}
p := &Pool{
size: size,
available: make(chan struct{}, size),
}
// Fill the semaphore
for i := 0; i < size; i++ {
p.available <- struct{}{}
}
return p
}
// Execute acquires a slot from the pool, runs the code, and releases the slot.
// A fresh goja VM is created for each execution to ensure clean state isolation.
// Blocks if all slots are in use; respects context cancellation.
func (p *Pool) Execute(ctx context.Context, code string, caller ToolCaller, opts ExecuteOptions) (*ExecuteResult, error) {
p.mu.Lock()
if p.closed {
p.mu.Unlock()
return nil, fmt.Errorf("pool is closed")
}
p.mu.Unlock()
// Acquire a slot (blocks if pool is exhausted)
select {
case <-p.available:
// Got a slot
case <-ctx.Done():
return nil, ctx.Err()
}
// Always release the slot when done
defer func() {
p.available <- struct{}{}
}()
// Execute with a fresh VM (created inside Execute)
return Execute(ctx, code, caller, opts)
}
// Size returns the configured pool size.
func (p *Pool) Size() int {
return p.size
}
// Available returns the number of available slots.
func (p *Pool) Available() int {
return len(p.available)
}
// Close marks the pool as closed. Subsequent Execute calls will return an error.
func (p *Pool) Close() {
p.mu.Lock()
defer p.mu.Unlock()
p.closed = true
}