Skip to content

feat: expose nodeWorkerArgs config #108

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/deno.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just format caught a bunch of missing semicolons, apologies. I'll call out the big changes.

// const s = new TextDecoder().decode(res.buffer);
// console.log(s);

Expand Down
2 changes: 1 addition & 1 deletion examples/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
25 changes: 10 additions & 15 deletions src/background-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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: [] });
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This worker is a fallback for Atomic.waitAsync being unavailable. Always null out the execArgv.

return (ia: any, index, value) => {
const promise = new Promise((resolve) => {
w.once('message', (data) => {
Expand Down Expand Up @@ -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 = {};
Expand Down Expand Up @@ -208,7 +203,7 @@ class BackgroundPlugin {
await this.#handleTimeout();
}
},
() => { },
() => {},
);

this.worker.postMessage({
Expand Down Expand Up @@ -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`);
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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<Worker> {
const worker = new Worker(WORKER_URL);
const worker = new Worker(WORKER_URL, opts.nodeWorkerArgs);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the actual worker. We've normalized nodeWorkerArgs by this point. (See 🍎)

onworker(worker);

await new Promise((resolve, reject) => {
Expand Down Expand Up @@ -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 {
Expand Down
76 changes: 52 additions & 24 deletions src/foreground-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -159,8 +159,19 @@ export async function createForegroundPlugin(
const seen: Map<WebAssembly.Module, WebAssembly.Instance> = 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);
}
Expand All @@ -175,7 +186,7 @@ async function instantiateModule(
names: string[],
modules: WebAssembly.Module[],
linked: Map<WebAssembly.Module, WebAssembly.Instance | null>,
mutableFlags: { suspendsOnInvoke: boolean }
mutableFlags: { suspendsOnInvoke: boolean },
) {
linked.set(module, null);

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down
9 changes: 2 additions & 7 deletions src/http-context.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down
28 changes: 28 additions & 0 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,23 @@ export interface Plugin {
reset(): Promise<boolean>;
}

/**
* Arguments to be passed to `node:worker_threads.Worker` when `runInWorker: true`.
*/
export interface NodeWorkerArgs {
name?: string;
execArgv?: string[];
argv?: string[];
env?: Record<string, string>;
resourceLimits?: {
maxOldGenerationSizeMb?: number;
maxYoungGenerationSizeMb?: number;
codeRangeSizeMb?: number;
stackSizeMb?: number;
};
[k: string]: any;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pretty much copied from Node's API, but neither Parameters<Worker> nor ConstructorParameters<Worker> worked so I've manually listed them here.

}

/**
* Options for initializing an Extism plugin.
*/
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -259,6 +286,7 @@ export interface InternalConfig extends Required<NativeManifestOptions> {
wasiEnabled: boolean;
sharedArrayBufferSize: number;
allowHttpResponseHeaders: boolean;
nodeWorkerArgs: NodeWorkerArgs;
}

export interface InternalWasi {
Expand Down
8 changes: 4 additions & 4 deletions src/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
: {}),
};
}
Expand Down
8 changes: 8 additions & 0 deletions src/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 || {},
);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🍎 We normalize nodeWorkerArgs here.


if (opts.allowedHosts.length && !opts.runInWorker) {
if (!(WebAssembly as any).Suspending) {
Expand Down Expand Up @@ -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);
Expand Down
Loading