jsexecutor

package module
v0.1.4 Latest Latest
Warning

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

Go to latest
Published: Jul 10, 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 and Goja engines.

Overview

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

Features

  • Thread Pool Model: Efficiently handles multiple JavaScript tasks in parallel using native threads.
  • Pluggable Engine Support: Easily integrates with different JavaScript engines (e.g., QuickJS, Goja).
  • 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 engine.

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!
}

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.
Benchmark
$ go test -run=^$ -bench=. -benchmem

goos: darwin
goarch: arm64
pkg: github.com/buke/js-executor
cpu: Apple M4
BenchmarkExecutor_QuickJS-10               26292             44961 ns/op            1092 B/op         46 allocs/op
BenchmarkExecutor_Goja-10                  12428             99048 ns/op           50058 B/op        720 allocs/op
PASS
ok      github.com/buke/js-executor     4.055s

Analysis:

  • Performance (ns/op): QuickJS is approximately 2.2x faster in this high-concurrency, CPU-bound test(Fibonacci). Its low-level C implementation and minimal pressure on the Go garbage collector (GC) allow it to excel under heavy load.
  • Memory (B/op, allocs/op): The memory statistics highlight a crucial difference.
    • Goja: As a pure Go engine, its memory usage is fully tracked by Go's tools. The higher numbers reflect the total cost of JS execution within the Go runtime, which can lead to increased GC pressure.
    • QuickJS: The reported numbers only show the Go-side allocation overhead. The memory used by the C-based QuickJS engine itself is not measured by Go's benchmark tool. This results in extremely low GC pressure on the Go application, which is a key reason for its high performance.

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
	initScript := &jsexecutor.InitScript{
		FileName: "hello.js",
		Content:  `function hello(name) { return "Hello, " + name + "!"; }`,
	}

	// Create a QuickJS engine builder and a new executor with the init script
	executor, err := jsexecutor.NewExecutor(
		jsexecutor.WithJsEngine(quickjsengine.NewFactory(
			quickjsengine.WithEnableModuleImport(true),
			quickjsengine.WithCanBlock(true),
		)),
		jsexecutor.WithInitScripts(initScript),
	)
	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 WithInitScripts

func WithInitScripts(scripts ...*InitScript) func(*JsExecutor)

WithInitScripts configures the initialization scripts.

func WithJsEngine

func WithJsEngine(engineFactory JsEngineFactory) func(*JsExecutor)

WithJsEngine configures the JavaScript engine builder and options.

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 InitScript

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

InitJsScript represents a JavaScript initialization script

type JsEngine

type JsEngine interface {
	// Init initializes the engine with the given scripts
	Init(scripts []*InitScript) error

	// Reload reloads the engine with new scripts
	Reload(scripts []*InitScript) 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 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) Execute

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

Execute executes a JavaScript request and returns the response.

func (*JsExecutor) Reload

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

Reload reloads all threads with 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

Directories

Path Synopsis
engines

Jump to

Keyboard shortcuts

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