Skip to content

Add computeIntensive for running CPU-intensive operations off virtual threads - #483

Merged
adamw merged 17 commits into
masterfrom
compute-intensive
Jul 23, 2026
Merged

adamw merged 17 commits into
masterfrom
compute-intensive

Conversation

@adamw

@adamw adamw commented Jul 23, 2026

Copy link
Copy Markdown
Member

Motivation

Virtual threads are not preempted by the JVM scheduler: a long-running, CPU-bound computation monopolizes a carrier thread, and a handful of such computations can starve all other virtual threads in the process (JEP 444 explicitly excludes time-sharing). cede() (#481) covers short, instrumentable bursts; this PR adds the complement for long-running or non-instrumentable compute, following the pattern the JDK team recommends (run such work on platform threads, letting the OS preempt) and the shape other runtimes converged on (Kotlin's Dispatchers.Default, Tokio's dedicated compute pool, Reactor's Schedulers.parallel()).

Changes

  • computeIntensive(f) / computeIntensive(executor)(f): runs f on a pool of platform threads, blocking the calling (virtual) thread until completion — structured by construction (the computation never outlives the enclosing scope), composes with fork/par/race/timeout.
  • Default pool: lazily-created, global, fixed size = available processors, daemon platform threads (ox-compute-N); replaceable via setOxComputeExecutor (mirroring setOxThreadFactory) or OxApp.Settings.computeExecutor (with binary-compat overloads).
  • Cancellation: interrupting the caller interrupts the pool task and then awaits its completion (strict structure); a not-yet-started task never runs; a task-thrown InterruptedException is correctly distinguished from caller interruption; the interrupt never leaks to a subsequent task on the reused pool thread.
  • Nesting: nested calls targeting the same executor (by reference) run inline, avoiding fixed-pool deadlock; different executors submit normally.
  • Scope context is deliberately not propagated: ForkLocals read defaults, fork inside the computation fails fast.
  • Docs: new doc/utils/compute-intensive.md page + cross-links from the cede() docs.
  • 16 new tests, including interrupt-then-await semantics, cancel-before-start, nesting, either interop, and a time-limited starvation smoke test.

Notes

  • The positive setOxComputeExecutor / OxApp.Settings.computeExecutor wiring path is deliberately untested: it mutates JVM-global state and would poison other suites (same situation as the existing threadFactory setting, which is also untested). Only the "throws after first use" path is covered.
  • Full core suite: 950 passed, 0 failed.

🤖 Generated with Claude Code

adamw and others added 17 commits July 21, 2026 12:38
…rtual threads)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ry interrupt check, precise abandonOnInterrupt semantics, timeout caveat

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ad pool

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nto the caller-interrupt path

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- fail the nested-deadlock test fast via a time limit, instead of hanging
- simplify onCallerInterrupted: capture the task's exception in the catch arm,
  dropping the redundant post-loop re-fetch and the unreachable self-suppression
  guard; comment the structurally-unreachable match arm
- explain the lazy-val-forcing idiom in setOxComputeExecutor, and give it a
  proper scaladoc summary; document the lock's serialization role in completing
- fix a garden-path sentence in the computeIntensive scaladoc
- rename ComputeTaskState to ComputeIntensiveTaskState, completing's parameter c to op
- docs: compile-check the examples via mdoc, clarify the custom-executor
  lifecycle sentence, add a missing period

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d of a nullable field

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant