jsexecutor

package module
v0.1.11 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 16, 2025 License: Apache-2.0 Imports: 8 Imported by: 2

README

js-executor

English | 简体中文

Test codecov Go Report Card GoDoc

JavaScript execution thread pool for Go, with built-in support for QuickJS / Goja / V8 engines.

Overview

js-executor provides a thread pool model for executing JavaScript code in parallel, running each engine instance in a native OS thread, and supports pluggable engine backends (such as QuickJS, Goja, and V8), initialization scripts, context passing, and robust resource management.

Features

  • Thread Pool Model: Efficiently handles multiple JavaScript tasks in parallel using native threads.
  • Built-in support for QuickJS / Goja / V8 engines.
  • Pluggable Engine Support: Easily integrates with different JavaScript engines (e.g., QuickJS, Goja, V8).
  • Initialization Scripts: Supports loading and reloading of initialization scripts for all threads.
  • Context Passing: Allows passing custom context data with each request and response.
  • Resource Management: Automatic thread lifecycle management, including idle timeout and max execution limits.
  • Timeout and Limits: Configurable execution timeout, memory limit, stack size, and more.

Usage Example

The following example demonstrates how to use the QuickJS, Goja, and V8Go engines.

QuickJS Example
import (
    "fmt"
    jsexecutor "github.com/buke/js-executor"
    quickjsengine "github.com/buke/js-executor/engines/quickjs-go"
)

func main() {
    // Prepare an initialization script
    initScript := &jsexecutor.InitScript{
        FileName: "hello.js",
        Content:  `function hello(name) { return "Hello, " + name + "!"; }`,
    }

    // Create a new executor with QuickJS engine
    executor, err := jsexecutor.NewExecutor(
        jsexecutor.WithJsEngine(quickjsengine.NewFactory()),
        jsexecutor.WithInitScripts(initScript),
    )
    if err != nil {
        panic(err)
    }
    defer executor.Stop()

    // Start the executor
    if err := executor.Start(); err != nil {
        panic(err)
    }

    // Execute a JS function
    req := &jsexecutor.JsRequest{
        Id:      "1",
        Service: "hello",
        Args:    []interface{}{"World"},
    }
    resp, err := executor.Execute(req)
    if err != nil {
        panic(err)
    }
    fmt.Println(resp.Result) // Output: Hello, World!
}
Goja Example
import (
    "fmt"
    jsexecutor "github.com/buke/js-executor"
    gojaengine "github.com/buke/js-executor/engines/goja"
)

func main() {
    // Prepare an initialization script
    initScript := &jsexecutor.InitScript{
        FileName: "hello.js",
        Content:  `function hello(name) { return "Hello, " + name + "!"; }`,
    }

    // Create a new executor with Goja engine
    executor, err := jsexecutor.NewExecutor(
        jsexecutor.WithJsEngine(gojaengine.NewFactory()),
        jsexecutor.WithInitScripts(initScript),
    )
    if err != nil {
        panic(err)
    }
    defer executor.Stop()

    // Start the executor
    if err := executor.Start(); err != nil {
        panic(err)
    }

    // Execute a JS function
    req := &jsexecutor.JsRequest{
        Id:      "goja-1",
        Service: "hello",
        Args:    []interface{}{"Goja"},
    }
    resp, err := executor.Execute(req)
    if err != nil {
        panic(err)
    }
    fmt.Println(resp.Result) // Output: Hello, Goja!
}
V8Go Example

Note: V8Go is not supported on Windows.

//go:build !windows

import (
    "fmt"
    jsexecutor "github.com/buke/js-executor"
    v8engine "github.com/buke/js-executor/engines/v8go"
)

func main() {
    // Prepare an initialization script
    initScript := &jsexecutor.InitScript{
        FileName: "hello.js",
        Content:  `function hello(name) { return "Hello, " + name + "!"; }`,
    }

    // Create a new executor with V8Go engine
    executor, err := jsexecutor.NewExecutor(
        jsexecutor.WithJsEngine(v8engine.NewFactory()),
        jsexecutor.WithInitScripts(initScript),
    )
    if err != nil {
        panic(err)
    }
    defer executor.Stop()

    // Start the executor
    if err := executor.Start(); err != nil {
        panic(err)
    }

    // Execute a JS function
    req := &jsexecutor.JsRequest{
        Id:      "v8go-1",
        Service: "hello",
        Args:    []interface{}{"V8Go"},
    }
    resp, err := executor.Execute(req)
    if err != nil {
        panic(err)
    }
    fmt.Println(resp.Result) // Output: Hello, V8Go!
}

Configuration

  • Pool Size: Set minimum and maximum thread pool size.
  • Queue Size: Set per-thread task queue size.
  • Timeouts: Configure execution timeout, thread idle timeout, and max executions per thread.
  • Engine Options: Configure memory limit, stack size, GC threshold, module import, etc.

Quick Start

  1. Install dependencies:

    go get github.com/buke/js-executor
    go get github.com/buke/js-executor/engines/quickjs-go
    
  2. See example_test.go for more usage examples.

Supported Engines

Engine Repository Notes
QuickJS github.com/buke/quickjs-go CGo-based, high performance.
Goja github.com/dop251/goja Pure Go, no CGo dependency.
V8Go github.com/tommie/v8go CGo-based, V8 engine, fastest. Not supported on Windows.
Benchmark
$ go test -run=^$ -bench=. -benchmem

goos: darwin
goarch: arm64
pkg: github.com/buke/js-executor
cpu: Apple M4
BenchmarkExecutor_Goja-10                  13106             92237 ns/op           52404 B/op        761 allocs/op
BenchmarkExecutor_QuickJS-10               26239             45663 ns/op            1092 B/op         46 allocs/op
BenchmarkExecutor_V8Go-10                 134680              8719 ns/op             986 B/op         31 allocs/op
PASS
ok      github.com/buke/js-executor     5.725s

Analysis:

  • Performance (ns/op):
    • Goja is the baseline (1x).
    • QuickJS is about 2x faster than Goja.
    • V8Go is about 10x faster than Goja, and about 5x faster than QuickJS, in this high-concurrency, CPU-bound test (Fibonacci).
  • Memory (B/op, allocs/op):
    • V8Go and QuickJS have very low Go-side memory usage and allocations per operation.
    • Goja (pure Go) shows much higher memory usage and allocations, as all JS memory is tracked by Go's runtime and GC.
    • Note: For CGo engines (V8Go, QuickJS), the Go benchmark only measures Go-side allocations; native engine memory is not included.
  • Platform:
    • V8Go is not supported on Windows.

License

Apache-2.0

Documentation

Overview

Example

Example demonstrates how to use JsExecutor with QuickJS engine. It shows how to initialize the executor, start it, execute a simple JS function, and stop the executor.

package main

import (
	"fmt"

	jsexecutor "github.com/buke/js-executor"
	quickjsengine "github.com/buke/js-executor/engines/quickjs-go"
)

func main() {
	// Prepare a simple JS function as an initialization script
	jsScript := &jsexecutor.JsScript{
		FileName: "hello.js",
		Content:  `function hello(name) { return "Hello, " + name + "!"; }`,
	}

	// Create a QuickJS engine builder and a new executor with the jsScript
	executor, err := jsexecutor.NewExecutor(
		jsexecutor.WithJsEngine(quickjsengine.NewFactory(
			quickjsengine.WithEnableModuleImport(true),
			quickjsengine.WithCanBlock(true),
		)),
		jsexecutor.WithJsScripts(jsScript),
	)
	if err != nil {
		fmt.Printf("Failed to create executor: %v\n", err)
		return
	}

	// Start the executor
	if err := executor.Start(); err != nil {
		fmt.Printf("Failed to start executor: %v\n", err)
		return
	}

	// Prepare a JS request to call the hello function
	req := &jsexecutor.JsRequest{
		Id:      "1",
		Service: "hello",
		Args:    []interface{}{"World"},
	}

	// Execute the JS request
	resp, err := executor.Execute(req)
	if err != nil {
		fmt.Printf("Execution error: %v\n", err)
		return
	}
	fmt.Printf("Result: %v\n", resp.Result)

	// Stop the executor
	if err := executor.Stop(); err != nil {
		fmt.Printf("Failed to stop executor: %v\n", err)
		return
	}

}
Output:

Result: Hello, World!

Index

Examples

Constants

View Source
const ThreadIdKey = "__threadId"

ThreadIdKey is the context key for specifying thread ID in requests.

Variables

This section is empty.

Functions

func WithCreateThreshold

func WithCreateThreshold(threshold float64) func(*JsExecutor)

WithCreateThreshold sets the queue load threshold for creating new threads.

func WithExecuteTimeout

func WithExecuteTimeout(timeout time.Duration) func(*JsExecutor)

WithExecuteTimeout sets the timeout for task execution.

func WithJsEngine

func WithJsEngine(engineFactory JsEngineFactory) func(*JsExecutor)

WithJsEngine configures the JavaScript engine builder and options.

func WithJsScripts added in v0.1.11

func WithJsScripts(scripts ...*JsScript) func(*JsExecutor)

WithJsScripts configures the initialization scripts.

func WithLogger

func WithLogger(logger *slog.Logger) func(*JsExecutor)

WithLogger configures the logger for the executor.

func WithMaxExecutions

func WithMaxExecutions(max uint32) func(*JsExecutor)

WithMaxExecutions sets the maximum executions per thread before cleanup.

func WithMaxPoolSize

func WithMaxPoolSize(size uint32) func(*JsExecutor)

WithMaxPoolSize sets the maximum number of threads in the pool.

func WithMinPoolSize

func WithMinPoolSize(size uint32) func(*JsExecutor)

WithMinPoolSize sets the minimum number of threads in the pool.

func WithQueueSize

func WithQueueSize(size uint32) func(*JsExecutor)

WithQueueSize sets the size of the task queue per thread.

func WithSelectThreshold

func WithSelectThreshold(threshold float64) func(*JsExecutor)

WithSelectThreshold sets the queue load threshold for skipping busy threads.

func WithThreadTTL

func WithThreadTTL(ttl time.Duration) func(*JsExecutor)

WithThreadTTL sets the time-to-live for idle threads.

Types

type JsEngine

type JsEngine interface {
	// Load loads scripts into the engine
	Load(scripts []*JsScript) error

	// Execute executes a JavaScript request and returns the response
	Execute(req *JsRequest) (*JsResponse, error)

	// Close closes the engine and releases resources
	Close() error
}

JsEngine represents a JavaScript execution engine

type JsEngineFactory

type JsEngineFactory func() (JsEngine, error)

JsEngineFactory defines a function type for creating JavaScript engines

type JsEngineOption added in v0.1.11

type JsEngineOption func(JsEngine) error

JsEngineOption represents a configuration option for a JavaScript engine

type JsExecutor

type JsExecutor struct {
	// contains filtered or unexported fields
}

JsExecutor manages a pool of JavaScript execution threads.

func NewExecutor

func NewExecutor(opts ...func(*JsExecutor)) (*JsExecutor, error)

NewExecutor creates a new JavaScript executor with the given options.

func (*JsExecutor) AppendJsScripts added in v0.1.11

func (e *JsExecutor) AppendJsScripts(scripts ...*JsScript)

AppendJsScripts appends new scripts to the existing initialization scripts.

func (*JsExecutor) Execute

func (e *JsExecutor) Execute(request *JsRequest) (*JsResponse, error)

Execute executes a JavaScript request and returns the response.

func (*JsExecutor) GetJsScripts added in v0.1.11

func (e *JsExecutor) GetJsScripts() []*JsScript

GetJsScripts returns the current initialization scripts (no copy, read-only).

func (*JsExecutor) Reload

func (e *JsExecutor) Reload(scripts ...*JsScript) error

Reload reloads all threads with new initialization scripts.

func (*JsExecutor) SetJsScripts added in v0.1.11

func (e *JsExecutor) SetJsScripts(scripts []*JsScript)

SetJsScripts atomically sets new initialization scripts.

func (*JsExecutor) Start

func (e *JsExecutor) Start() error

Start initializes and starts the executor thread pool.

func (*JsExecutor) Stop

func (e *JsExecutor) Stop() error

Stop stops the executor and shuts down all threads.

type JsExecutorOption

type JsExecutorOption struct {
	// contains filtered or unexported fields
}

JsExecutorOption contains configuration options for the JavaScript executor.

type JsRequest

type JsRequest struct {
	Id      string                 `json:"id"`      // Unique identifier for the request
	Service string                 `json:"service"` // Service/function name to call
	Args    []interface{}          `json:"args"`    // Arguments to pass to the function
	Context map[string]interface{} `json:"context"` // Additional context data
}

JsRequest represents a JavaScript execution request

type JsResponse

type JsResponse struct {
	Id      string                 `json:"id"`      // Request ID that this response corresponds to
	Result  interface{}            `json:"result"`  // Execution result
	Context map[string]interface{} `json:"context"` // Updated context data
}

JsResponse represents the result of JavaScript execution

type JsScript added in v0.1.11

type JsScript struct {
	Content  string // Script content
	FileName string // Script file name for debugging purposes
}

JsScript represents a JavaScript script, typically used for initialization.

Directories

Path Synopsis
engines

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL