From e110ccf84d8d9034e82a79bf7abf1fa92b2370be Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Fri, 28 Jul 2023 01:33:00 -0700 Subject: [PATCH] Fixes #3795 (#3856) Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> --- bench/snippets/webcrypto.mjs | 20 +++++++++++++++ .../bindings/ScriptExecutionContext.cpp | 11 ++++++++ src/bun.js/bindings/ScriptExecutionContext.h | 2 ++ .../bindings/webcrypto/CryptoAlgorithm.cpp | 4 ++- .../webcrypto/CryptoAlgorithmSHA1.cpp | 15 ++++++++++- .../webcrypto/CryptoAlgorithmSHA224.cpp | 14 ++++++++++- .../webcrypto/CryptoAlgorithmSHA256.cpp | 13 +++++++++- .../webcrypto/CryptoAlgorithmSHA384.cpp | 15 ++++++++++- .../webcrypto/CryptoAlgorithmSHA512.cpp | 15 ++++++++++- test/js/web/crypto/keeps-alive-fixture.js | 12 +++++++++ test/js/web/crypto/web-crypto.test.ts | 25 +++++++++++++++++++ 11 files changed, 140 insertions(+), 6 deletions(-) create mode 100644 bench/snippets/webcrypto.mjs create mode 100644 test/js/web/crypto/keeps-alive-fixture.js diff --git a/bench/snippets/webcrypto.mjs b/bench/snippets/webcrypto.mjs new file mode 100644 index 0000000000..2d1256cf8f --- /dev/null +++ b/bench/snippets/webcrypto.mjs @@ -0,0 +1,20 @@ +import { group } from "mitata"; +import { bench, run } from "./runner.mjs"; + +const sizes = [ + ["small (63 bytes)", 63], + ["medium (4096 bytes)", 4096], + ["large (64 MB)", 64 * 1024 * 1024], +]; +for (let [name, size] of sizes) { + group(name, () => { + var buf = new Uint8Array(size); + for (let algorithm of ["SHA-1", "SHA-256", "SHA-384", "SHA-512"]) { + bench(algorithm, async () => { + await crypto.subtle.digest(algorithm, buf); + }); + } + }); +} + +await run(); diff --git a/src/bun.js/bindings/ScriptExecutionContext.cpp b/src/bun.js/bindings/ScriptExecutionContext.cpp index d93478ed8c..47908b3856 100644 --- a/src/bun.js/bindings/ScriptExecutionContext.cpp +++ b/src/bun.js/bindings/ScriptExecutionContext.cpp @@ -5,6 +5,7 @@ #include "webcore/WebSocket.h" #include "libusockets.h" #include "_libusockets.h" +#include "BunClientData.h" extern "C" void Bun__startLoop(us_loop_t* loop); @@ -58,6 +59,16 @@ us_socket_context_t* ScriptExecutionContext::webSocketContextSSL() return m_ssl_client_websockets_ctx; } +extern "C" void Bun__eventLoop__incrementRefConcurrently(void* bunVM, int delta); + +void ScriptExecutionContext::refEventLoop() +{ + Bun__eventLoop__incrementRefConcurrently(WebCore::clientData(vm())->bunVM, 1); +} +void ScriptExecutionContext::unrefEventLoop() +{ + Bun__eventLoop__incrementRefConcurrently(WebCore::clientData(vm())->bunVM, -1); +} bool ScriptExecutionContext::postTaskTo(ScriptExecutionContextIdentifier identifier, Function&& task) { diff --git a/src/bun.js/bindings/ScriptExecutionContext.h b/src/bun.js/bindings/ScriptExecutionContext.h index 580c1be25d..b0e4e2096f 100644 --- a/src/bun.js/bindings/ScriptExecutionContext.h +++ b/src/bun.js/bindings/ScriptExecutionContext.h @@ -107,6 +107,8 @@ public: } static ScriptExecutionContext* getScriptExecutionContext(ScriptExecutionContextIdentifier identifier); + void refEventLoop(); + void unrefEventLoop(); const WTF::URL& url() const { diff --git a/src/bun.js/bindings/webcrypto/CryptoAlgorithm.cpp b/src/bun.js/bindings/webcrypto/CryptoAlgorithm.cpp index ca3b97bff5..3c958bdfca 100644 --- a/src/bun.js/bindings/webcrypto/CryptoAlgorithm.cpp +++ b/src/bun.js/bindings/webcrypto/CryptoAlgorithm.cpp @@ -95,10 +95,12 @@ ExceptionOr CryptoAlgorithm::getKeyLength(const CryptoAlgorithmParameter template static void dispatchAlgorithmOperation(WorkQueue& workQueue, ScriptExecutionContext& context, ResultCallbackType&& callback, CryptoAlgorithm::ExceptionCallback&& exceptionCallback, OperationType&& operation) { + context.refEventLoop(); workQueue.dispatch( [operation = WTFMove(operation), callback = WTFMove(callback), exceptionCallback = WTFMove(exceptionCallback), contextIdentifier = context.identifier()]() mutable { auto result = operation(); - ScriptExecutionContext::postTaskTo(contextIdentifier, [result = crossThreadCopy(WTFMove(result)), callback = WTFMove(callback), exceptionCallback = WTFMove(exceptionCallback)](auto&) mutable { + ScriptExecutionContext::postTaskTo(contextIdentifier, [result = crossThreadCopy(WTFMove(result)), callback = WTFMove(callback), exceptionCallback = WTFMove(exceptionCallback)](auto& context) mutable { + context.unrefEventLoop(); if (result.hasException()) { exceptionCallback(result.releaseException().code()); return; diff --git a/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA1.cpp b/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA1.cpp index f602467e02..8a2a8829fa 100644 --- a/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA1.cpp +++ b/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA1.cpp @@ -51,10 +51,23 @@ void CryptoAlgorithmSHA1::digest(Vector&& message, VectorCallback&& cal return; } + if (message.size() < 64) { + auto moved = WTFMove(message); + digest->addBytes(moved.data(), moved.size()); + auto result = digest->computeHash(); + ScriptExecutionContext::postTaskTo(context.identifier(), [callback = WTFMove(callback), result = WTFMove(result)](auto&) { + callback(result); + }); + return; + } + + context.refEventLoop(); + workQueue.dispatch([digest = WTFMove(digest), message = WTFMove(message), callback = WTFMove(callback), contextIdentifier = context.identifier()]() mutable { digest->addBytes(message.data(), message.size()); auto result = digest->computeHash(); - ScriptExecutionContext::postTaskTo(contextIdentifier, [callback = WTFMove(callback), result = WTFMove(result)](auto&) { + ScriptExecutionContext::postTaskTo(contextIdentifier, [callback = WTFMove(callback), result = WTFMove(result)](auto& context) { + context.unrefEventLoop(); callback(result); }); }); diff --git a/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA224.cpp b/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA224.cpp index dfb8204b53..3591215bf1 100644 --- a/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA224.cpp +++ b/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA224.cpp @@ -51,10 +51,22 @@ void CryptoAlgorithmSHA224::digest(Vector&& message, VectorCallback&& c return; } + if (message.size() < 64) { + auto moved = WTFMove(message); + digest->addBytes(moved.data(), moved.size()); + auto result = digest->computeHash(); + ScriptExecutionContext::postTaskTo(context.identifier(), [callback = WTFMove(callback), result = WTFMove(result)](auto&) { + callback(result); + }); + return; + } + + context.refEventLoop(); workQueue.dispatch([digest = WTFMove(digest), message = WTFMove(message), callback = WTFMove(callback), contextIdentifier = context.identifier()]() mutable { digest->addBytes(message.data(), message.size()); auto result = digest->computeHash(); - ScriptExecutionContext::postTaskTo(contextIdentifier, [callback = WTFMove(callback), result = WTFMove(result)](auto&) { + ScriptExecutionContext::postTaskTo(contextIdentifier, [callback = WTFMove(callback), result = WTFMove(result)](auto& context) { + context.unrefEventLoop(); callback(result); }); }); diff --git a/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA256.cpp b/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA256.cpp index 216eccb5d0..c9bad917bb 100644 --- a/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA256.cpp +++ b/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA256.cpp @@ -51,10 +51,21 @@ void CryptoAlgorithmSHA256::digest(Vector&& message, VectorCallback&& c return; } + if (message.size() < 64) { + auto moved = WTFMove(message); + digest->addBytes(moved.data(), moved.size()); + auto result = digest->computeHash(); + ScriptExecutionContext::postTaskTo(context.identifier(), [callback = WTFMove(callback), result = WTFMove(result)](auto&) { + callback(result); + }); + return; + } + context.refEventLoop(); workQueue.dispatch([digest = WTFMove(digest), message = WTFMove(message), callback = WTFMove(callback), contextIdentifier = context.identifier()]() mutable { digest->addBytes(message.data(), message.size()); auto result = digest->computeHash(); - ScriptExecutionContext::postTaskTo(contextIdentifier, [callback = WTFMove(callback), result = WTFMove(result)](auto&) { + ScriptExecutionContext::postTaskTo(contextIdentifier, [callback = WTFMove(callback), result = WTFMove(result)](auto& context) { + context.unrefEventLoop(); callback(result); }); }); diff --git a/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA384.cpp b/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA384.cpp index 134e89bd8a..890d317b94 100644 --- a/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA384.cpp +++ b/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA384.cpp @@ -51,10 +51,23 @@ void CryptoAlgorithmSHA384::digest(Vector&& message, VectorCallback&& c return; } + if (message.size() < 64) { + auto moved = WTFMove(message); + digest->addBytes(moved.data(), moved.size()); + auto result = digest->computeHash(); + ScriptExecutionContext::postTaskTo(context.identifier(), [callback = WTFMove(callback), result = WTFMove(result)](auto&) { + callback(result); + }); + return; + } + + context.refEventLoop(); + workQueue.dispatch([digest = WTFMove(digest), message = WTFMove(message), callback = WTFMove(callback), contextIdentifier = context.identifier()]() mutable { digest->addBytes(message.data(), message.size()); auto result = digest->computeHash(); - ScriptExecutionContext::postTaskTo(contextIdentifier, [callback = WTFMove(callback), result = WTFMove(result)](auto&) { + ScriptExecutionContext::postTaskTo(contextIdentifier, [callback = WTFMove(callback), result = WTFMove(result)](auto& context) { + context.unrefEventLoop(); callback(result); }); }); diff --git a/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA512.cpp b/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA512.cpp index 016a47bf19..38a9fbe18a 100644 --- a/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA512.cpp +++ b/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA512.cpp @@ -51,10 +51,23 @@ void CryptoAlgorithmSHA512::digest(Vector&& message, VectorCallback&& c return; } + if (message.size() < 64) { + auto moved = WTFMove(message); + digest->addBytes(moved.data(), moved.size()); + auto result = digest->computeHash(); + ScriptExecutionContext::postTaskTo(context.identifier(), [callback = WTFMove(callback), result = WTFMove(result)](auto&) { + callback(result); + }); + return; + } + + context.refEventLoop(); workQueue.dispatch([digest = WTFMove(digest), message = WTFMove(message), callback = WTFMove(callback), contextIdentifier = context.identifier()]() mutable { digest->addBytes(message.data(), message.size()); auto result = digest->computeHash(); - ScriptExecutionContext::postTaskTo(contextIdentifier, [callback = WTFMove(callback), result = WTFMove(result)](auto&) { + + ScriptExecutionContext::postTaskTo(contextIdentifier, [callback = WTFMove(callback), result = WTFMove(result)](auto& context) { + context.unrefEventLoop(); callback(result); }); }); diff --git a/test/js/web/crypto/keeps-alive-fixture.js b/test/js/web/crypto/keeps-alive-fixture.js new file mode 100644 index 0000000000..0740923a55 --- /dev/null +++ b/test/js/web/crypto/keeps-alive-fixture.js @@ -0,0 +1,12 @@ +const algorithms = ["SHA-1", "SHA-256", "SHA-384", "SHA-512"]; +const data = [ + "Hello World!", + "Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World!Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World!", +]; +for (let bytes of data) { + for (const algorithm of algorithms) { + crypto.subtle.digest(algorithm, Buffer.from(bytes)).then(data => { + console.log(Buffer.from(data).toString("hex")); + }); + } +} diff --git a/test/js/web/crypto/web-crypto.test.ts b/test/js/web/crypto/web-crypto.test.ts index b8155c3ba2..006d488513 100644 --- a/test/js/web/crypto/web-crypto.test.ts +++ b/test/js/web/crypto/web-crypto.test.ts @@ -1,6 +1,31 @@ +import { spawnSync } from "bun"; import { describe, expect, it } from "bun:test"; +import { bunEnv, bunExe } from "harness"; describe("Web Crypto", () => { + // https://github.com/oven-sh/bun/issues/3795 + it("keeps event loop alive", () => { + const { stdout, exitCode } = spawnSync({ + cmd: [bunExe(), import.meta.resolveSync("./keeps-alive-fixture.js")], + env: bunEnv, + }); + + const lines = stdout.toString().trim().split("\n").sort(); + const results = [ + "2ef7bde608ce5404e97d5f042f95f89f1c232871", + "6b3e626d70787e3dc3f0bca509a7e1e5f6802643fde54a18d4353aa9b24ccb2fb874bbc8a70ff587df2bd6ed41471f82", + "7dc2af5ef620a4b1c8871371526b664512b82193", + "7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069", + "861844d6704e8573fec34d967e20bcfef3d424cf48be04e6dc08f2bd58c729743371015ead891cc3cf1c9d34b49264b510751b1ff9e537937bc46b5d6ff4ecc8", + "bf6873609ce720ec489bb2f5ae116716058c06cda7dc9a7e1dadee90da98e71aee22519505af61adbecd5b94bbefa855c2ede623e8b383bb179b150e25861441", + "bfd76c0ebbd006fee583410547c1887b0292be76d582d96c242d2a792723e3fd6fd061f9d5cfd13b8f961358e6adba4a", + "e1061f7858d68c3818ec9967ea1f7bf8e3c65f5603af95004bdfcb64b9ea4148", + ]; + + expect(exitCode).toBe(0); + expect(lines).toStrictEqual(results); + }); + it("has globals", () => { expect(crypto.subtle !== undefined).toBe(true); expect(CryptoKey.name).toBe("CryptoKey");