Skip to content

[parallel] add Strategy::run_async for offloading CPU work from worker thread#4005

Draft
roberto-bayardo wants to merge 1 commit into
mainfrom
roberto/parallel-run-async
Draft

[parallel] add Strategy::run_async for offloading CPU work from worker thread#4005
roberto-bayardo wants to merge 1 commit into
mainfrom
roberto/parallel-run-async

Conversation

@roberto-bayardo

@roberto-bayardo roberto-bayardo commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

Offload batch leaf hashing onto the strategy

The authenticated journal's add_many hashes every appended item into a leaf digest (roughly half of merkleization CPU for a qmdb batch). It was already parallelized, but
Rayon::install blocks the calling thread, pinning a tokio worker for tens of milliseconds on large batches. It now runs through Strategy::run_async: still parallel,
but the calling task suspends instead of blocking its thread. First production use of run_async, to evaluate on a busy validator before converting parent-node hashing
(blocked on off-lock merkleization).

API changes (ALPHA)

  • add_many and the keyless/immutable batch merkleize fns are now async.
  • ValueEncoding/FixedValue/VariableValue gain 'static bounds so owned items can cross the offload boundary (Key already had one).
  • A test pairing Rayon with the deterministic runner moved to the tokio runner: the deterministic runtime cannot observe wakeups from external pool threads.

Validation

Full storage/glue/sync suites pass; conformance roots are unchanged, confirming the relocation is behavior-preserving. The caller still awaits the root, so merkleize
benches won't move — the win is co-scheduled tasks no longer stalling behind hashing on executor threads, visible in validator tail latency rather than throughput.

… tasks

Runs a closure to completion and returns its result through a future. The
default implementation (used by Sequential) runs the work inline at first
poll; Rayon spawns it onto its thread pool and suspends the caller via a
oneshot channel, keeping CPU-intensive work off async executor threads.
Strategy methods invoked within the closure execute with the strategy's
parallelism.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
commonware-mcp a7268e1 Jun 09 2026, 11:24 PM

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown

Benchmark results

Tip

PASSED: No benchmark exceeded the regression threshold.

Benchmark comparison table
Benchmark Baseline (main) Current Delta Threshold Status
qmdb::merkleize/variant=any::unordered::fixed::mmr keys=10000 ch=false sync=false 1.538 ms 1.643 ms +6.82% 10.00% ✅ PASS
qmdb::merkleize/variant=current::ordered::fixed::mmb chunk=256 keys=10000 ch=true sync=false 3.238 ms 3.337 ms +3.05% 10.00% ✅ PASS

Baseline commit(s): 6280bf61991d

Comment thread parallel/src/lib.rs
R: Send + 'static,
{
let (tx, rx) = futures::channel::oneshot::channel();
self.thread_pool.spawn(move || {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

does this take away from rayon's throughput?

@roberto-bayardo roberto-bayardo Jun 9, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

not according to Fable

for the parallel work itself, throughput is identical. The closure's host thread isn't an overhead thread sitting on top of the workers; it is one of the N
  workers, and when the inner map_collect_vec runs, it's hashing alongside the rest of the pool. The pool delivers the same N-way parallelism it did under the old blocking
  install — rayon's install-from-within-the-pool path exists precisely so nested parallelism doesn't lose a thread.

  The only costs that exist at all:

  - A one-time handoff per run_async call (oneshot send + waker) — microseconds, amortized over milliseconds of hashing.
  - Sequential code inside a closure holds one worker while it runs. But that work was going to consume one thread somewhere regardless — it's just a rayon thread now
  instead of a tokio thread, which is the whole point.

  And under concurrency it's arguably a throughput improvement in aggregate: previously, several tasks merkleizing at once meant several tokio workers all blocked in
  install, contending for the same pool while contributing nothing. Now those tasks suspend, the closures queue in rayon's injector, and the pool chews through them at full
  utilization with no parked threads anywhere.

@codecov

codecov Bot commented Jun 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.08%. Comparing base (47efdba) to head (a7268e1).
⚠️ Report is 1 commits behind head on main.

@@           Coverage Diff           @@
##             main    #4005   +/-   ##
=======================================
  Coverage   95.08%   95.08%           
=======================================
  Files         531      530    -1     
  Lines      218260   218264    +4     
  Branches     5302     5302           
=======================================
+ Hits       207524   207537   +13     
+ Misses       8936     8925   -11     
- Partials     1800     1802    +2     
Files with missing lines Coverage Δ
parallel/src/lib.rs 95.19% <100.00%> (+0.81%) ⬆️

... and 34 files with indirect coverage changes


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 47efdba...a7268e1. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Staged

Development

Successfully merging this pull request may close these issues.

2 participants