diff --git a/examples/deno.ts b/examples/deno.ts index 5d0741e..142ee92 100755 --- a/examples/deno.ts +++ b/examples/deno.ts @@ -16,7 +16,7 @@ const plugin = await createPlugin(filename, { console.log('calling', { filename, funcname, input }); const res = await plugin.call(funcname, new TextEncoder().encode(input)); -console.log(res) +console.log(res); // const s = new TextDecoder().decode(res.buffer); // console.log(s); diff --git a/examples/node.js b/examples/node.js index 30119b3..b7a10c7 100755 --- a/examples/node.js +++ b/examples/node.js @@ -19,7 +19,7 @@ async function main() { console.log('calling', { filename, funcname, input }); const res = await plugin.call(funcname, new TextEncoder().encode(input)); - console.log(res) + console.log(res); // const s = new TextDecoder().decode(res.buffer); // console.log(s); diff --git a/src/background-plugin.ts b/src/background-plugin.ts index 5fb4bb7..5755707 100644 --- a/src/background-plugin.ts +++ b/src/background-plugin.ts @@ -9,17 +9,12 @@ import { SET_HOST_CONTEXT, STORE, } from './call-context.ts'; -import { - type InternalConfig, - PluginOutput, - SAB_BASE_OFFSET, - SharedArrayBufferSection, -} from './interfaces.ts'; +import { type InternalConfig, PluginOutput, SAB_BASE_OFFSET, SharedArrayBufferSection } from './interfaces.ts'; import { WORKER_URL } from './worker-url.ts'; import { Worker } from 'node:worker_threads'; import { CAPABILITIES } from './polyfills/deno-capabilities.ts'; import { EXTISM_ENV } from './foreground-plugin.ts'; -import { HttpContext } from './http-context.ts' +import { HttpContext } from './http-context.ts'; // Firefox has not yet implemented Atomics.waitAsync, but we can polyfill // it using a worker as a one-off. @@ -37,7 +32,7 @@ const AtomicsWaitAsync = const blob = new (Blob as any)([src], { type: 'text/javascript' }); const url = URL.createObjectURL(blob); - const w = new Worker(url); + const w = new Worker(url, { execArgv: [] }); return (ia: any, index, value) => { const promise = new Promise((resolve) => { w.once('message', (data) => { @@ -85,7 +80,7 @@ class BackgroundPlugin { async #handleTimeout() { // block new requests from coming in & the current request from settling const request = this.#request; - this.#request = [() => { }, () => { }]; + this.#request = [() => {}, () => {}]; const timedOut = {}; const failed = {}; @@ -208,7 +203,7 @@ class BackgroundPlugin { await this.#handleTimeout(); } }, - () => { }, + () => {}, ); this.worker.postMessage({ @@ -298,7 +293,7 @@ class BackgroundPlugin { // // - https://github.com/nodejs/node/pull/44409 // - https://github.com/denoland/deno/issues/14786 - const timer = setInterval(() => { }, 0); + const timer = setInterval(() => {}, 0); try { if (!func) { throw Error(`Plugin error: host function "${ev.namespace}" "${ev.func}" does not exist`); @@ -423,7 +418,7 @@ class RingBufferWriter { signal() { const old = Atomics.load(this.flag, 0); - while (Atomics.compareExchange(this.flag, 0, old, this.outputOffset) === old) { } + while (Atomics.compareExchange(this.flag, 0, old, this.outputOffset) === old) {} Atomics.notify(this.flag, 0, 1); } @@ -546,9 +541,9 @@ async function createWorker( names: string[], modules: WebAssembly.Module[], sharedData: SharedArrayBuffer, - onworker: (_w: Worker) => void = (_w: Worker) => { }, + onworker: (_w: Worker) => void = (_w: Worker) => {}, ): Promise { - const worker = new Worker(WORKER_URL); + const worker = new Worker(WORKER_URL, opts.nodeWorkerArgs); onworker(worker); await new Promise((resolve, reject) => { @@ -595,7 +590,7 @@ function timeout(ms: number | null, sentinel: any) { async function terminateWorker(w: Worker) { if (typeof (globalThis as any).Bun !== 'undefined') { - const timer = setTimeout(() => { }, 10); + const timer = setTimeout(() => {}, 10); await w.terminate(); clearTimeout(timer); } else { diff --git a/src/foreground-plugin.ts b/src/foreground-plugin.ts index 8834b23..585d3aa 100644 --- a/src/foreground-plugin.ts +++ b/src/foreground-plugin.ts @@ -9,10 +9,10 @@ export const EXTISM_ENV = 'extism:host/env'; type InstantiatedModule = [WebAssembly.Module, WebAssembly.Instance]; interface SuspendingCtor { - new(fn: CallableFunction): any; + new (fn: CallableFunction): any; } -const AsyncFunction = (async () => { }).constructor; +const AsyncFunction = (async () => {}).constructor; const Suspending: SuspendingCtor | undefined = (WebAssembly as any).Suspending; const promising: CallableFunction | undefined = (WebAssembly as any).promising; @@ -159,8 +159,19 @@ export async function createForegroundPlugin( const seen: Map = new Map(); const wasiList: InternalWasi[] = []; - const mutableFlags = { suspendsOnInvoke } - const instance = await instantiateModule(context, ['main'], modules[mainIndex], imports, opts, wasiList, names, modules, seen, mutableFlags); + const mutableFlags = { suspendsOnInvoke }; + const instance = await instantiateModule( + context, + ['main'], + modules[mainIndex], + imports, + opts, + wasiList, + names, + modules, + seen, + mutableFlags, + ); return new ForegroundPlugin(opts, context, [modules[mainIndex], instance], wasiList, mutableFlags.suspendsOnInvoke); } @@ -175,7 +186,7 @@ async function instantiateModule( names: string[], modules: WebAssembly.Module[], linked: Map, - mutableFlags: { suspendsOnInvoke: boolean } + mutableFlags: { suspendsOnInvoke: boolean }, ) { linked.set(module, null); @@ -241,22 +252,17 @@ async function instantiateModule( promising && imports[module][name] === context[ENV].http_request ) { - const httpContext = new HttpContext( - opts.fetch, - opts.allowedHosts, - opts.memory, - opts.allowHttpResponseHeaders - ); + const httpContext = new HttpContext(opts.fetch, opts.allowedHosts, opts.memory, opts.allowHttpResponseHeaders); - mutableFlags.suspendsOnInvoke = true + mutableFlags.suspendsOnInvoke = true; - const contributions = {} as any - httpContext.contribute(contributions) + const contributions = {} as any; + httpContext.contribute(contributions); for (const [key, entry] of Object.entries(contributions[EXTISM_ENV] as { [k: string]: CallableFunction })) { // REBIND: - imports[module][key] = (entry as any).bind(null, context) + imports[module][key] = (entry as any).bind(null, context); } - imports[module][name] = new Suspending!(imports[module][name]) + imports[module][name] = new Suspending!(imports[module][name]); } switch (kind) { @@ -289,11 +295,33 @@ async function instantiateModule( // If the dependency provides "_start", treat it as a WASI Command module; instantiate it (and its subtree) directly. const instance = providerExports.find((xs) => xs.name === '_start') - ? await instantiateModule(context, [...current, module], provider, imports, opts, wasiList, names, modules, new Map(), mutableFlags) + ? await instantiateModule( + context, + [...current, module], + provider, + imports, + opts, + wasiList, + names, + modules, + new Map(), + mutableFlags, + ) : !linked.has(provider) - ? (await instantiateModule(context, [...current, module], provider, imports, opts, wasiList, names, modules, linked, mutableFlags), - linked.get(provider)) - : linked.get(provider); + ? (await instantiateModule( + context, + [...current, module], + provider, + imports, + opts, + wasiList, + names, + modules, + linked, + mutableFlags, + ), + linked.get(provider)) + : linked.get(provider); if (!instance) { // circular import, either make a trampoline or bail @@ -334,10 +362,10 @@ async function instantiateModule( const guestType = instance.exports.hs_init ? 'haskell' : instance.exports._initialize - ? 'reactor' - : instance.exports._start - ? 'command' - : 'none'; + ? 'reactor' + : instance.exports._start + ? 'command' + : 'none'; if (wasi) { await wasi?.initialize(instance); diff --git a/src/http-context.ts b/src/http-context.ts index b7ae682..718475c 100644 --- a/src/http-context.ts +++ b/src/http-context.ts @@ -1,10 +1,5 @@ -import { - CallContext, - ENV, -} from './call-context.ts'; -import { - MemoryOptions, -} from './interfaces.ts'; +import { CallContext, ENV } from './call-context.ts'; +import { MemoryOptions } from './interfaces.ts'; import { EXTISM_ENV } from './foreground-plugin.ts'; import { matches } from './polyfills/deno-minimatch.ts'; diff --git a/src/interfaces.ts b/src/interfaces.ts index 8f18963..d852b33 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -124,6 +124,23 @@ export interface Plugin { reset(): Promise; } +/** + * Arguments to be passed to `node:worker_threads.Worker` when `runInWorker: true`. + */ +export interface NodeWorkerArgs { + name?: string; + execArgv?: string[]; + argv?: string[]; + env?: Record; + resourceLimits?: { + maxOldGenerationSizeMb?: number; + maxYoungGenerationSizeMb?: number; + codeRangeSizeMb?: number; + stackSizeMb?: number; + }; + [k: string]: any; +} + /** * Options for initializing an Extism plugin. */ @@ -214,6 +231,16 @@ export interface ExtismPluginOptions { * headers for HTTP requests made using `extism:host/env::http_request` */ allowHttpResponseHeaders?: boolean; + + /** + * Arguments to pass to the `node:worker_threads.Worker` instance when `runInWorker: true`. + * + * This is particularly useful for changing `process.execArgv`, which controls certain startup + * behaviors in node (`--import`, `--require`, warnings.) + * + * If not set, defaults to removing the current `execArgv` and disabling node warnings. + */ + nodeWorkerArgs?: NodeWorkerArgs; } export type MemoryOptions = { @@ -259,6 +286,7 @@ export interface InternalConfig extends Required { wasiEnabled: boolean; sharedArrayBufferSize: number; allowHttpResponseHeaders: boolean; + nodeWorkerArgs: NodeWorkerArgs; } export interface InternalWasi { diff --git a/src/manifest.ts b/src/manifest.ts index 1f3ea00..7d1039e 100644 --- a/src/manifest.ts +++ b/src/manifest.ts @@ -86,10 +86,10 @@ function parseManifestFromJson(json: string): Manifest { config: parsed.config, ...(parsed.memory ? { - maxHttpResponseBytes: parsed.memory.maxHttpResponseBytes ?? parsed.memory.max_http_response_bytes, - maxPages: parsed.memory.maxPages ?? parsed.memory.max_pages, - maxVarBytes: parsed.memory.maxVarBytes ?? parsed.memory.max_var_bytes, - } + maxHttpResponseBytes: parsed.memory.maxHttpResponseBytes ?? parsed.memory.max_http_response_bytes, + maxPages: parsed.memory.maxPages ?? parsed.memory.max_pages, + maxVarBytes: parsed.memory.maxVarBytes ?? parsed.memory.max_var_bytes, + } : {}), }; } diff --git a/src/mod.ts b/src/mod.ts index 6db9d23..6fd91f7 100644 --- a/src/mod.ts +++ b/src/mod.ts @@ -99,6 +99,13 @@ export async function createPlugin( opts.config = opts.config || manifestOpts.config || {}; opts.memory = opts.memory || manifestOpts.memory || {}; opts.timeoutMs = opts.timeoutMs || manifestOpts.timeoutMs || null; + opts.nodeWorkerArgs = Object.assign( + { + name: 'extism plugin', + execArgv: ['--disable-warning=ExperimentalWarning'], + }, + opts.nodeWorkerArgs || {}, + ); if (opts.allowedHosts.length && !opts.runInWorker) { if (!(WebAssembly as any).Suspending) { @@ -142,6 +149,7 @@ export async function createPlugin( timeoutMs: opts.timeoutMs, memory: opts.memory, allowHttpResponseHeaders: !!opts.allowHttpResponseHeaders, + nodeWorkerArgs: opts.nodeWorkerArgs || {}, }; return (opts.runInWorker ? _createBackgroundPlugin : _createForegroundPlugin)(ic, names, moduleData);