Files
jscriptx/engine_option.go
T
what 111665d2e7 fix: HasStatic 让 Stats 越用越偏;borrowStatic 漏标 scoped
两个都是静态调用那条路上的。

HasStatic 建了 VM 却不记丢弃。Created 在 load 里加(所有建 VM 的路径都走它),
Dropped 在 staticTarget.finish 里加,而 HasStatic 拿到 VM 后直接返回,不走
finish。每调一次 Created-Dropped 就永久 +1——Stats 是导出的观测结构,这个
偏差会直接体现在监控上。改成走同一套记账。

borrowStatic 同样会注入作用域扩展,却不设 vm.scoped。release 靠这个标志拒绝
把带扩展的 VM 放回池子,漏标就是跨调用泄漏。今天不出事只是因为
staticTarget.finish 永远丢弃——同一个约束靠两套机制守,以后有人把静态 VM
接进 release 就会漏。

测试:
  - Stats 那条断言「在飞的 VM 数」调用前后不变
  - scoped 那条是**内部测试**,直接断言标志位。行为测试盖不住它——borrowStatic
    那条路今天靠 finish 兜底,从外面看不出漏标
  两条都做了破坏性验证,摘掉修复会红。

顺带把 WithTimeout 的文档补上一句:要新建 VM 的路径上是顶层和函数调用两段
各自计时,墙钟上限是 2×d。行为不改——两段各自计时是合理的,缺的只是说明。
2026-09-10 15:21:17 +08:00

91 lines
3.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package jscriptx
import (
"log/slog"
"time"
)
// 引擎的配置项都在这个文件里,一处看全 New 能配什么。
//
// 另有两组独立的选项:BundleOption(打包源码,见 bundle.go)由 WithBundleOptions
// 带进来;ScopeOption(每次调用的作用域,见 scope.go)跟着 ctx 走,不属于引擎配置。
// Option 是 New 的配置项。
type Option func(*Engine)
// WithGlobals 追加暴露给脚本的全局对象白名单。可以多次调用,同名后者覆盖前者。
//
// value 为 map[string]any 时会被注入成一个只读的 JS 对象(逐层递归),
// 脚本改不动它,多个 VM 之间也不会共享同一个可变的 Go map。
func WithGlobals(globals map[string]any) Option {
return func(e *Engine) {
for k, v := range globals {
e.globals[k] = v
}
}
}
// WithGlobal 暴露单个全局对象。
func WithGlobal(name string, value any) Option {
return func(e *Engine) { e.globals[name] = value }
}
// WithTimeout 设置单次调用的时限,默认 DefaultTimeout。超时会中断脚本执行
// goja 的 Interrupt),调用方拿到包了 ErrTimeout 的错误。
//
// 注意「单次调用」在**要新建 VM** 的路径上是两段各自计时:先给顶层代码一段,
// 再给函数调用一段,所以墙钟上限是 2×d。走 VM 池命中时没有这个问题——顶层
// 早就跑过了。要卡死总时长,用带 deadline 的 ctx。
func WithTimeout(d time.Duration) Option {
return func(e *Engine) { e.timeout = d }
}
// WithMaxVMs 设置每个脚本最多缓存多少个 VM 实例。这是缓存上限不是并发上限:
// 并发超过它时会临时新建 VM,用完直接丢弃,不会阻塞调用。
func WithMaxVMs(n int) Option {
return func(e *Engine) { e.maxVMs = n }
}
// WithMaxCallStackSize 设置脚本的最大调用栈深度,传 0 用 goja 默认值。
func WithMaxCallStackSize(n int) Option {
return func(e *Engine) { e.maxStack = n }
}
// WithLogger 设置日志器,脚本里的 console.* 会打到这里,带上 script 字段。
// 传 nil 表示不注入 console。
func WithLogger(l *slog.Logger) Option {
return func(e *Engine) { e.logger = l }
}
// WithLoader 设置脚本源码的来源,Engine.Script 会用它按名字取脚本。
// 本库只定义 Loader 接口,具体从文件、数据库还是配置中心读由调用方实现。
//
// 可以给多个,它们叠成一层层的,**后面的盖前面的**——取脚本时从最后一层往前找,
// 谁先有就用谁的。把"定制层"放最后,业务侧放一份同名脚本就能改写默认实现:
//
// base, _ := esm.NewLoader("app/src")
// custom, _ := esm.NewLoader("custom/src") // 配置可以跟 base 完全不同
//
// e, err := jscriptx.New(jscriptx.WithLoader(base, custom)) // custom 盖 base
//
// 每层是独立的 Loader,各有各的配置(入口规则、目标版本、node_modules 位置、
// 扩展模块),来源也可以不同——一层来自磁盘目录,另一层来自数据库都行。
// 多次调用 WithLoader 会继续往后叠,效果跟一次传多个一样。
//
// 叠多层时各层的 Prepared 必须一致,否则 New 报错,原因见 Overlay。
func WithLoader(loaders ...Loader) Option {
return func(e *Engine) { e.loaderLayers = append(e.loaderLayers, loaders...) }
}
// WithAutoReload 打开后,每次 Engine.Script 都会问一次 Loader 拿版本号,
// 版本变了就重新编译并换掉旧的 VM 池——这是"改脚本不重启进程"的开关。
// 代价是每次取脚本都会调一次 Loader.Load,实现方自己保证这个调用足够轻。
func WithAutoReload(on bool) Option {
return func(e *Engine) { e.autoReload = on }
}
// WithBundleOptions 配置 Compile 打包源码时的行为,比如 WithResolveDir。
func WithBundleOptions(opts ...BundleOption) Option {
return func(e *Engine) { e.bundleOpts = append(e.bundleOpts, opts...) }
}