From a962c7f45b05af708a4a5574a57dcc9f0b6f0421 Mon Sep 17 00:00:00 2001 From: mimiMonads Date: Sat, 14 Dec 2024 10:55:57 +0000 Subject: [PATCH] test of threading --- experimental/main.ts | 72 +++++++++++++++++++++++++++++++++++++++++++ experimental/multi.ts | 67 ++++++++++++++++++++++++++++++++++++++++ experimental/type.ts | 50 ++++++++++++++++++++++++++++++ 3 files changed, 189 insertions(+) create mode 100644 experimental/main.ts create mode 100644 experimental/multi.ts create mode 100644 experimental/type.ts diff --git a/experimental/main.ts b/experimental/main.ts new file mode 100644 index 0000000..1a175d6 --- /dev/null +++ b/experimental/main.ts @@ -0,0 +1,72 @@ +import { wrap } from "../main.ts"; +import { bjscript } from "./multi.ts"; +import { bench, run } from "mitata"; + +const serve = await wrap({ + cyclePlugin: { + bjscript, + }, +})() + .get({ + path: "/", + f: ({ bjscript }) => { + return bjscript; + }, + }) + .get({ + path: "/hi", + f: () => "Hi", + }) + .compose(); + +const settings = { + algorithm: "argon2id", // "argon2id" | "argon2i" | "argon2d" + memoryCost: 4, // memory usage in kibibytes + timeCost: 3, // the number of iterations +}; + +const serve2 = await wrap()() + .get({ + path: "/", + f: async ({ req }) => { + return await Bun.password.hash(req.url, settings); + }, + }) + .get({ + path: "/hi", + f: () => "Hi", + }) + .get({ + path: "/hi2", + f: () => "Hi2", + }) + .compose(); + +const req = new Request("http://localhost/"); +const hi = new Request("http://localhost/hi"); +const hi2 = new Request("http://localhost/hi2"); + +// await Promise.resolve(serve(req)) +// .then(async r => await r.text()) +// .then(console.log) + +// await Promise.resolve(serve2(req)) +// .then(async r => await r.text()) +// .then(console.log) + +const select = + ((reqs: Request[]) => (n = 0) => n === 2 ? reqs[n = 0] : reqs[n = n + 1])([ + req, + hi, + hi2, + ]); + +bench("latency with thread", async () => { + await serve(select()); +}); + +bench("latency without thread", async () => { + await serve2(select()); +}); + +await run(); diff --git a/experimental/multi.ts b/experimental/multi.ts new file mode 100644 index 0000000..49f9625 --- /dev/null +++ b/experimental/multi.ts @@ -0,0 +1,67 @@ +import { plugins } from "../main.ts"; +import { isMainThread, parentPort, Worker } from "node:worker_threads"; + +const workerUrl = new URL(import.meta.url); + +// We'll store the resolver in the same scope so the on('message') handler can see it. +let globalMessageResolver: ((value: string) => void) | null = null; + +const createWorker = () => { + const worker = new Worker(workerUrl, { type: "module" }); + + // When the worker sends a message, resolve the promise if we have a resolver waiting. + worker.on("message", (msg) => { + if (globalMessageResolver) { + globalMessageResolver(msg); + globalMessageResolver = null; + } + }); + + return worker; +}; + +if (!isMainThread) { + if (parentPort === null) { + throw Error("Missing parentPort in worker thread"); + } + + const settings = { + algorithm: "argon2id", // "argon2id" | "argon2i" | "argon2d" + memoryCost: 4, // memory usage in kibibytes + timeCost: 3, // the number of iterations + }; + parentPort.on("message", async (data) => { + const hash = await Bun.password.hash(data, settings); + + parentPort.postMessage(hash); + }); +} + +const sealedwork = (w: Worker) => { + // Returns a function that sends a message to `w` and awaits the response + return async (msg: string) => { + return new Promise((resolve) => { + // Tie the "globalMessageResolver" to *this* call's `resolve`. + globalMessageResolver = resolve; + w.postMessage(msg); + }); + }; +}; + +export const bjscript = plugins.type({ + name: Symbol.for("hello"), + isFunction: false, + isAsync: true, + type: {} as string, + f: async () => { + // Create the worker once + const work = createWorker(); + // Generate an async function that sends a message + const message = sealedwork(work); + + // Return a function that, when called, sends "hi" and awaits response + return async (request) => { + return await message(request.url); + }; + }, +}); diff --git a/experimental/type.ts b/experimental/type.ts new file mode 100644 index 0000000..7f5cbf8 --- /dev/null +++ b/experimental/type.ts @@ -0,0 +1,50 @@ +// A recursive type that attempts to represent all structured-clone-friendly values. +type StructuredCloneable = + // Primitives + | null + | undefined + | boolean + | number + | string + // Objects and arrays of cloneable types + | { [key: string]: StructuredCloneable } + | StructuredCloneable[] + // Maps and Sets of cloneable values + | Map + | Set + // Date and RegExp + | Date + | RegExp + // Error objects (cloned with name/message only) + | Error + | EvalError + | RangeError + | ReferenceError + | SyntaxError + | TypeError + | URIError + // Binary / File types + | ArrayBuffer + | DataView + | Int8Array + | Uint8Array + | Uint8ClampedArray + | Int16Array + | Uint16Array + | Int32Array + | Uint32Array + | Float32Array + | Float64Array + // Potential big integer arrays if supported + // | BigInt64Array | BigUint64Array (if your environment supports them) + | Blob + | File + | FileList + // Image-related + | ImageData + | ImageBitmap + // OffscreenCanvas if supported by your environment + | OffscreenCanvas; // Streams (if supported) - this is highly environment-dependent +// | ReadableStream | WritableStream | TransformStream +// WebAssembly modules (if supported) +// | WebAssembly.Module | WebAssembly.Instance