运行时能力

Node 模块与 Worker 任务

为独立 Node ESM 和 CPU 密集任务选择合适的构建与生命周期模型。

Pluxel 可以把一段代码构建成独立 Node ESM,也可以把 CPU 密集任务放进共享线程池。两者解决的问题不同:

需求选择
把另一份源码构建成独立 Node ESM,并在当前线程初始化defineNodeModule()
把可结构化克隆的 CPU 密集工作放入共享线程池defineWorkerTask()
普通异步 I/O、数据库或短小调用直接在 Plugin 中执行

快速开始 创建的插件包内添加下列文件;保留 Pluxel 的 Vite/构建配置,普通 TypeScript 编译不会生成这些独立产物。声明必须位于模块顶层,入口使用静态相对路径。

独立 Node module

import { , ,  } from '@pluxel/runtime'

const  = (import.meta., './rules-entry.ts')

@({ : 'Rules' })
export class  extends  {
	protected override async () {
		await this...(, async () => {
			const  = await import(.)
			return .setup({ : this.. })
		})
	}
}

rules-entry.ts 默认没有 Pluxel Context;它导出上面调用的 setup(),返回释放资源的函数:

export function setup({ logger }: { logger: { info(message: string): void } }) {
	logger.info('rules module ready')
	return () => logger.info('rules module released')
}

启动插件后应看到 rules module ready;停用插件后应看到释放日志。

defineNodeModule() 只声明 entry,不创建线程。ctx.nodeModules.use()

  • 等待 artifact 首次可用和 setup 完成;
  • setup/import 失败会让 Plugin init() 失败;
  • callback 返回的 cleanup/disposable 绑定当前 owner generation;
  • 开发期 replacement 先 setup 新 URL,成功后再清理上一成功消费者;
  • 新 artifact 失败时保留旧成功 consumer,不提交半成品。

artifact 是自包含单文件 Node ESM。它可以使用 Node builtin 和可安全 bundle 的普通 library,但不能 value-import Pluxel runtime/core、Plugin、Context 或 Workbench server API,也不能嵌套声明 Plugin/Workbench/Node module。

需要把 Context 能力交给 module 时,传入窄的、明确 owner 的 facade,不要传整个 Context 或 Plugin instance。

共享 worker task

持续占用 JavaScript event loop、thread-safe 且能用纯数据描述的 CPU/native 工作使用 worker task:

import { , ,  } from '@pluxel/runtime'

type  = { : number[] }
type  = { : number }

const  = <, >(import.meta., './sum-worker.ts')

@({ : 'Reports' })
export class  extends  {
	(: number[], ?: AbortSignal) {
		return this...(, {  }, {  })
	}
}

把下一段保存为与插件文件同目录的 sum-worker.ts。它默认导出 handler,不接收 Pluxel Context:

import type {  } from '@pluxel/runtime'

type  = { : number[] }
type  = { : number }

const : <, > = ({  }) => ({
	: .((, ) =>  + , 0),
})

export default 

调用 calculate([1, 2, 3]) 应得到 { total: 6 }。用 测试宿主 或现有应用的 开发控制台 调用插件方法,才能同时验证产物提取与执行路径。

所有插件共享宿主的线程池,线程按需创建,队列有上限,并在插件之间公平调度。host 统一配置 concurrent execution slots、全局和每 Plugin queue limit、idle timeout;Plugin 不创建私有线程池,也不自行扩大进程预算。取消 running task 会终止对应 worker; caller Promise 可以立即结束,但 runtime 会等 worker 真正退出后才归还 active slot,避免 replacement task 穿透执行上限。

什么可以跨线程

输入输出必须满足 structured clone:

  • 可以:plain object、array、string、number、boolean、ArrayBuffer、typed array;
  • 不可以:function、closure、Context、Plugin instance、数据库连接、native Canvas/Image;
  • native binding 必须明确 thread-safe,并由拥有它的 package 声明构建 metadata。

网络/数据库 I/O、短调用、不可重试 side effect 通常不应为了“统一”而进入 worker。worker cancellation 可能终止线程,任务必须能安全重做或丢弃。

大型二进制 transfer

默认输入在 run() 接纳时 snapshot。大型 ArrayBuffer 可以明确移交 ownership:

const bytes = new Uint8Array(await response.arrayBuffer())
const result = this.ctx.workers.run(
	decodeTask,
	{ bytes },
	{
		signal,
		transfer: [bytes.buffer],
	},
)

// 接纳成功后 bytes.buffer 已同步 detached。
return result

transfer 规则:

  • 只接受 ArrayBuffer
  • 同一个 buffer 不能重复列出;
  • accepted 后即使任务最终失败也不会恢复 ownership;
  • queue 已满、任务未接纳时 runtime 不 detach;
  • SharedArrayBuffer 本来就是共享内存,不进入 transfer,调用方自行负责同步协议。

借用输入,避免排队时复制

默认 snapshot 适合调用后立即复用或修改 input。若领域 API 已经要求 input 在 Promise settle 前保持不变,可以省略 排队 snapshot,只保留真正 dispatch 的 transport clone:

return this.ctx.workers.run(renderTask, input, {
	signal,
	inputOwnership: 'borrowed',
})

borrowed 模式在 queue 中保留 caller graph,因此 mutation 会改变尚未 dispatch 的任务;它不能与 transfer 同时使用。 该模式减少一次 clone,不会让 postMessage serialization 离开主线程,领域仍必须对超大 object graph 设置 bytes/count/depth 预算。

任务获准执行后再准备输入

如果 domain budget walk 或 snapshot 本身较重,先用共享 queue admission,再准备输入:

return this.ctx.workers.runPrepared(
	renderTask,
	async (signal) => {
		await assertBoundedDeclarativeGraph(option, signal)
		return { option, policy }
	},
	{ signal, inputOwnership: 'borrowed' },
)

prepare(signal) 只在 artifact route 可用、任务轮到 fair execution slot 后执行;queue full 不调用它。准备期间该 slot 保持 active,因此 host-side preflight 数也受 maxThreads 限制。callback 应分段让出 event loop 并观察 signal;runtime 不能抢占一个同步 callback。callback error 原样返回,prepared value 的 structured-clone error 仍归类为 WorkerTaskError('INVALID_INPUT')

默认会 snapshot prepared value;只有领域 API 已要求 caller 到最终 Promise settle 前保持 captured/returned graph 不变时 才使用 borrowed。runPrepared() 不支持 transfer,因为 admission 时 buffer 尚未产生,无法提供“接纳成功即同步 detach” 的 ownership contract。

取消与 Plugin stop

run(task, input, { signal }) 的 signal 取消 admission 或运行中任务。Plugin stop/replacement 也会取消并等待该 owner 已接纳任务,不让旧 generation 在后台继续写结果。

不要只 fire-and-forget:

// 错误:调用方无法观察失败,业务也不知道结果是否提交。
void this.ctx.workers.run(task, input)

后台调用需要明确捕获 error、更新有界状态,并决定重试/丢弃。请求级任务把 Promise 返回给 HTTP/command 边界。

Canvas 与 ECharts

native Canvas/Image 不能 structured clone。官方 Canvas Plugin 提供纯数据 workerSnapshot@pluxel/canvas/worker adapter,让 worker 在自己的线程内创建 native surface;业务插件不直接传 native handle。

@pluxel/echarts 始终使用 runtime shared worker pool。调用方只需调用 render(),不要再套一层自建 worker。

构建与验证

Plugin build 会提取 literal declaration,并输出独立 artifact。验证至少覆盖:

  • declaration 使用 literal relative entry;
  • artifact 中没有 Pluxel runtime/Context import;
  • input/output 可 clone,transfer 后调用方不再访问 buffer;
  • queue full、abort、worker error 和 Plugin stop;
  • native dependency metadata 与真正 package owner 一致。

构建产物与缓存位置见 CLI 与工具链,测试 host 选择见 测试 Pluxel 插件

最后更新于

本页目录