Skip to content

Commit

Permalink
Cross-platform error stack trace injection
Browse files Browse the repository at this point in the history
  • Loading branch information
W4G1 committed Jan 7, 2024
1 parent 9c12e51 commit 04f2b91
Show file tree
Hide file tree
Showing 24 changed files with 605 additions and 203 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ await Promise.all([

console.log(user.balance); // 15
```
In this example, the `add` function is used within the multithreaded `addBalance` function. The `yield` statement is used to declare external dependencies, ensuring that the required functions and data are available to the threaded function.
In this example, the `add` function is used within the multithreaded `addBalance` function. The `yield` statement is used to declare external dependencies. You can think of it as a way to import external data, functions or even packages.

As with previous examples, the shared state is managed using `$claim` and `$unclaim` to guarantee proper synchronization and prevent data conflicts.

Expand Down
38 changes: 24 additions & 14 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 6 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "multithreading",
"version": "0.2.0",
"version": "0.2.1",
"description": "⚡ Multithreading functions in JavaScript to speedup heavy workloads, designed to feel like writing vanilla functions.",
"author": "Walter van der Giessen <waltervdgiessen@gmail.com>",
"homepage": "https://multithreading.io",
Expand All @@ -16,11 +16,11 @@
"worker-threads"
],
"types": "./dist/index.d.ts",
"module": "./dist/index.mjs",
"module": "./dist/index.js",
"main": "./dist/index.js",
"exports": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"import": "./dist/index.js",
"require": "./dist/index.js"
},
"files": [
Expand All @@ -44,6 +44,7 @@
"@rollup/plugin-swc": "^0.3.0",
"@rollup/plugin-terser": "^0.4.4",
"@types/node": "^20.10.4",
"cross-env": "^7.0.3",
"jest": "^29.7.0",
"nodemon": "^3.0.2",
"rimraf": "^5.0.5",
Expand All @@ -58,16 +59,12 @@
"build": "npm run build:worker && rollup -c rollup.config.js --configPlugin @rollup/plugin-swc && rimraf .temp",
"dev": "nodemon",
"prepublishOnly": "npm run build",
"test": "jest"
"test": "cross-env NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest"
},
"bugs": {
"url": "https://github.com/W4G1/multithreading/issues"
},
"peerDependencies": {
"web-worker": "^1.2.0"
},
"optionalDependencies": {
"@rollup/rollup-linux-x64-gnu": "^4.9.1"
},
"dependencies": {}
}
}
1 change: 0 additions & 1 deletion rollup.config.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ export default ["cjs"].flatMap((type) => {
plugins: [...(version === ".min" ? [terser()] : [])],
},
],
external: ["web-worker"],
})
);
});
7 changes: 4 additions & 3 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,14 @@ export default ["esm", "cjs"].flatMap((type) => {
},
},
],

output: [
{
file: `dist/index${version}.${ext}`,
format: type,
sourcemap: false,
name: "multithreading",
dynamicImportInCjs: false,
dynamicImportInCjs: true,
globals: {
"web-worker": "Worker",
},
Expand All @@ -66,15 +67,15 @@ export default ["esm", "cjs"].flatMap((type) => {
terser({
compress: {
toplevel: true,
passes: 3,
passes: 2,
},
mangle: {},
}),
]
: []),
],
},
],
external: ["web-worker"],
})
);
});
3 changes: 2 additions & 1 deletion rollup.config.worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,9 @@ export default [
terser({
compress: {
toplevel: true,
passes: 3,
passes: 2,
},
mangle: {},
}),
],
name: "multithreading",
Expand Down
1 change: 0 additions & 1 deletion src/constants.ts

This file was deleted.

42 changes: 14 additions & 28 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,21 @@
import "./lib/polyfills/Promise.withResolvers.ts";
import "./lib/polyfills/import.meta.resolve.ts";
import "./lib/polyfills/web-worker.ts";

import { serialize } from "./lib/serialize.ts";
import { GLOBAL_FUNCTION_NAME } from "./constants.ts";
import * as $ from "./lib/keys.ts";
import { MainEvent } from "./lib/types";
import { MainEvent, UserFunction } from "./lib/types";
import { setupWorkerListeners } from "./lib/setupWorkerListeners.ts";
import { parseTopLevelYieldStatements } from "./lib/parseTopLevelYieldStatements.ts";

const INLINE_WORKER = `__INLINE_WORKER__`;
const inlineWorker = `__INLINE_WORKER__`;

export async function $claim(value: Object) {}
export function $unclaim(value: Object) {}

const workerPools = new WeakMap<Function, Worker[]>();
const workerPools = new WeakMap<UserFunction, Worker[]>();
const valueOwnershipQueue = new WeakMap<Object, Worker[]>();

// Either AsyncGenerator or Generator
type CommonGenerator<T, TReturn, TNext> =
| AsyncGenerator<T, TReturn, TNext>
| Generator<T, TReturn, TNext>;

type UserFunction<T extends Array<unknown> = [], TReturn = void> = (
...args: T
) => CommonGenerator<
any,
TReturn,
{
$claim: typeof $claim;
$unclaim: typeof $unclaim;
}
>;

interface ThreadedConfig {
debug: boolean;
maxThreads: number;
Expand Down Expand Up @@ -73,7 +59,7 @@ export function threaded<T extends Array<unknown>, TReturn>(
const init = (async () => {
const fnStr = fn.toString();

const yieldList = parseTopLevelYieldStatements(fnStr);
const yieldList = await parseTopLevelYieldStatements(fnStr);

// @ts-ignore - Call function without arguments
const gen = fn();
Expand All @@ -93,26 +79,24 @@ export function threaded<T extends Array<unknown>, TReturn>(
}

const workerCode = [
`globalThis.${GLOBAL_FUNCTION_NAME} = ${fnStr};`,
INLINE_WORKER + "",
inlineWorker,
`__internal.${$.UserFunction} = ${fnStr};`,
];

const serializedVariables = serialize(context);

for (const [key, value] of Object.entries(serializedVariables)) {
if (value[$.WasType] !== $.Function) continue;
// globalthis. is necessary to prevent duplicate variable names when the function is named
workerCode.unshift(`globalThis.${key} = ${value.value};`);

delete serializedVariables[key];
}

// Polyfill for Node.js
globalThis.Worker ??= (await import("web-worker")).default;

const workerCodeString = workerCode.join("\r\n");

for (let i = 0; i < config.maxThreads; i++) {
const worker = new Worker(
const worker = new (await Worker)(
encodeURI(
"data:application/javascript;base64," + btoa(workerCodeString)
),
Expand All @@ -126,7 +110,9 @@ export function threaded<T extends Array<unknown>, TReturn>(
context,
valueOwnershipQueue,
invocationQueue,
workerPool
workerPool,
workerCodeString,
i
);

workerPool.push(worker);
Expand Down
4 changes: 4 additions & 0 deletions src/lib/colors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const red = "\x1b[31m";
export const cyan = "\x1b[36m";
export const gray = "\x1b[90m";
export const reset = "\x1b[39m";
11 changes: 8 additions & 3 deletions src/lib/getErrorPreview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const colorRed = "\x1b[31m";
const colorCyan = "\x1b[36m";
const colorReset = "\x1b[39m";

export function getErrorPreview(error: Error, code: string) {
export function getErrorPreview(error: Error, code: string, pid: number) {
const [message, ...serializedStackFrames] = error.stack!.split("\n");

// Check if error originates from inside the user function
Expand Down Expand Up @@ -51,11 +51,16 @@ export function getErrorPreview(error: Error, code: string) {

const index = serializedStackFrames.indexOf(stackFrame);
serializedStackFrames[index] =
` at ${colorCyan}<user function>${colorReset}\n` +
` at ${colorCyan}<Thread_${pid}>${colorReset}\n` +
colorGray +
" " +
previewLines.join("\n ") +
colorReset;

return message + "\n" + serializedStackFrames.slice(0, index + 1).join("\n");
// return message + "\n" + serializedStackFrames.slice(0, index + 1).join("\n");
return (
message.split(":").slice(1).join(":").trim() +
"\n" +
serializedStackFrames.slice(0, index + 1).join("\n")
);
}
14 changes: 14 additions & 0 deletions src/lib/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const ClaimAcceptance = 8;
export const ClaimRejection = 9;
export const Invocation = 12;
export const Synchronization = 13;
export const Error = 14;

export const Variables = "a";
export const Args = "b";
Expand All @@ -23,6 +24,12 @@ export const DebugEnabled = "k";
export const Type = "l";
export const AbsolutePath = "m";
export const Code = "n";
export const Pid = "o";
export const UserFunction = "p";
export const Internal = "q";
export const ShareableNameMap = "r";
export const ValueClaimMap = "s";
export const ValueInUseCount = "t";

export declare type Function = typeof Function;
export declare type Other = typeof Other;
Expand All @@ -34,6 +41,7 @@ export declare type ClaimAcceptance = typeof ClaimAcceptance;
export declare type ClaimRejection = typeof ClaimRejection;
export declare type Invocation = typeof Invocation;
export declare type Synchronization = typeof Synchronization;
export declare type Error = typeof Error;

export declare type Variables = typeof Variables;
export declare type Args = typeof Args;
Expand All @@ -49,3 +57,9 @@ export declare type DebugEnabled = typeof DebugEnabled;
export declare type Type = typeof Type;
export declare type AbsolutePath = typeof AbsolutePath;
export declare type Code = typeof Code;
export declare type Pid = typeof Pid;
export declare type UserFunction = typeof UserFunction;
export declare type Internal = typeof Internal;
export declare type ShareableNameMap = typeof ShareableNameMap;
export declare type ValueClaimMap = typeof ValueClaimMap;
export declare type ValueInUseCount = typeof ValueInUseCount;
Loading

0 comments on commit 04f2b91

Please sign in to comment.