@@ -252,21 +252,99 @@ If the `CryptoJob` is processed as a Web Crypto API job, then
252252` run() ` returns a Promise. ` kCryptoJobWebCrypto ` dispatches the
253253work to the libuv threadpool. ` kCryptoJobSyncWebCrypto ` performs
254254the work synchronously on the current thread but still resolves or
255- rejects the Promise using Web Crypto API semantics. This sync
256- Web Crypto mode is used for selected small-input operations and
257- selected low-cost public-key operations, plus Web Crypto symmetric
258- and non-RSA asymmetric key generation. Sync Web Crypto selection is
259- also limited by a small per-turn JavaScript budget so that sequential
260- ` await ` usage can take the fast path while same-turn fanout falls back
261- to asynchronous jobs.
262- Operation-specific failures are rejected with an ` OperationError ` ,
263- and successful jobs resolve with the Web Crypto API result shape
264- expected by the JavaScript implementation.
265-
266- The JavaScript Web Crypto layer keeps the thresholds close to the
267- operation-specific call sites. The default small-input threshold is
268- 64 KiB. RSA public-key operations are only selected for sync Web Crypto
269- mode with moduli up to 4096 bits.
255+ rejects the Promise using Web Crypto API semantics. Operation-specific
256+ failures are rejected with an ` OperationError ` , and successful jobs
257+ resolve with the Web Crypto API result shape expected by the
258+ JavaScript implementation.
259+
260+ The JavaScript Web Crypto layer decides between ` kCryptoJobWebCrypto `
261+ and ` kCryptoJobSyncWebCrypto ` before the native job is constructed.
262+ The decision must be made at that point because async jobs need
263+ threadsafe copies of BufferSource input, while sync Web Crypto jobs can
264+ borrow the caller's input for the duration of the immediate operation.
265+ A native job cannot safely start as sync and later fall back to async
266+ after it has been configured with sync-only input references.
267+
268+ No Web Crypto operation is unconditionally sync. The sync path is only
269+ a fast-path candidate. A candidate still becomes async when its input
270+ is too large, when an operation-specific public-key limit rejects it,
271+ or when the same JavaScript turn has already consumed the sync budget.
272+
273+ The default small-input threshold is 64 KiB. Call sites may use lower
274+ operation-specific thresholds when benchmarks show that parallel
275+ throughput is more sensitive to the synchronous first job. Current
276+ thresholds are:
277+
278+ * 64 KiB by default.
279+ * 32 KiB for AES-CBC and AES-CTR encrypt.
280+ * 16 KiB for HKDF deriveBits.
281+ * 16 KiB for KMAC sign and verify.
282+
283+ The byte length being compared is the amount of operation input that
284+ best predicts the native cost, not always a single Web Crypto argument:
285+
286+ * Digest uses input data length plus output length.
287+ * AES-CBC, AES-CTR, and AES-KW use input data length.
288+ * AES-GCM, AES-OCB, and ChaCha20-Poly1305 use input data length plus
289+ additional authenticated data length. For decrypt, input data already
290+ includes the authentication tag.
291+ * HMAC uses input data length plus signature length for verify.
292+ * KMAC uses input data length plus signature length plus output length.
293+ * HKDF uses salt length plus info length plus derived output length.
294+ * ECDSA, EdDSA, and RSA verify use input data length.
295+
296+ RSA public-key candidates are additionally limited to moduli up to
297+ 4096 bits. RSA-OAEP encrypt can be a sync candidate under that modulus
298+ limit. RSA-OAEP decrypt, RSA sign, and RSA key generation remain
299+ async.
300+
301+ Some candidates do not have a byte-size threshold because their input
302+ size is fixed or otherwise bounded by the operation. These include
303+ Web Crypto symmetric key generation, non-RSA asymmetric key generation,
304+ ML-KEM encapsulation and decapsulation, ECDH P-256 deriveBits, X25519
305+ deriveBits, X448 deriveBits, and RSA-OAEP encrypt with an allowed
306+ modulus size. These are still only sync candidates; same-turn fanout
307+ can force them async.
308+
309+ The sync budget is intentionally small: at most one sync Web Crypto
310+ candidate is allowed in a JavaScript turn. Here "turn" means the
311+ continuous JavaScript execution slice before the next microtask
312+ checkpoint. When the first candidate reserves the sync slot, Node.js
313+ queues a microtask that resets the per-turn counter. Sequential
314+ ` await ` usage yields to the microtask queue between operations, so it
315+ can keep taking the sync fast path:
316+
317+ ``` js
318+ await subtle .digest (' SHA-256' , data);
319+ await subtle .digest (' SHA-256' , data);
320+ ```
321+
322+ Same-turn fanout does not give the reset microtask a chance to run
323+ between job creations:
324+
325+ ``` js
326+ const a = subtle .digest (' SHA-256' , data);
327+ const b = subtle .digest (' SHA-256' , data);
328+ await Promise .all ([a, b]);
329+ ```
330+
331+ In that case the first eligible job may run sync, the second eligible
332+ job runs async, and a short async pressure window is enabled. The
333+ pressure window forces the next 1024 eligible candidates to async. If a
334+ single synchronous burst creates more jobs than that, the per-turn sync
335+ counter is still used, so the pressure window is re-armed instead of
336+ letting another sync job through.
337+
338+ The pressure window is a heuristic, not a Web Crypto API requirement.
339+ It exists because a replenished Promise pipeline can otherwise run one
340+ sync job from each Promise reaction while still trying to measure or
341+ use parallel throughput. Once same-turn fanout has been observed, the
342+ workload is treated as throughput-oriented for a while and candidates
343+ are sent to the threadpool. This protects parallel throughput without
344+ requiring precise active-job accounting in the native layer, but it
345+ also means that a small fanout can make later otherwise-eligible jobs
346+ async until the pressure counter drains. Changes to this policy should
347+ be justified with serial and parallel Web Crypto benchmarks.
270348
271349For ` CipherJob ` types, the output is always an ` ArrayBuffer ` .
272350
0 commit comments