Skip to content

docs: design proposal for run-anywhere compute tasks - #300

Draft
joe-redpanda wants to merge 9 commits into
v26.2.xfrom
run-anywhere-compute-tasks-design
Draft

docs: design proposal for run-anywhere compute tasks#300
joe-redpanda wants to merge 9 commits into
v26.2.xfrom
run-anywhere-compute-tasks-design

Conversation

@joe-redpanda

Copy link
Copy Markdown

Summary

Design document for a new run-anywhere compute task type: CPU-heavy work with the opposite affinity contract to normal seastar tasks.

  • Expressed as a dedicated C++20 coroutine type (compute::task<T>) that is deliberately not a seastar::task — the closed awaitable set makes shard-affine machinery (futures, engine(), timers, I/O) unreachable at compile time, which is what makes migration safe.
  • Strictly idle-priority: work is pulled from a single global queue by whichever shard hits the reactor's idle branch, so it only consumes cycles a shard would otherwise waste. Scheduling-group shares cannot express this (floored at 1.0, CFS guarantees a slice), but the existing unused idle-handler seam in the reactor loop can.
  • co_await compute::checkpoint() cooperatively yields the CPU back the moment the running shard has foreground work, returning the task to the global queue for another idle shard to pick up.
  • Shard-affine edges: submit() returns an ordinary future that resolves on the submitting shard; only the compute task itself migrates.
  • Near-zero cost when unused: nothing on the busy path, one relaxed load per idle iteration when enabled.

The doc is structured with the human-readable design up top (problem, high-level design, API sketch, lifecycle), followed by checkpoint semantics, the wake protocol, perf-impact analysis, open questions, and a fully worked end-to-end trace as an addendum.

Design only — no implementation in this PR. Opening as a draft to gather feedback on the approach.

🤖 Generated with Claude Code

joe-redpanda and others added 9 commits July 21, 2026 13:01
Design proposal for a core-unaffine, idle-priority task type: CPU-heavy
work expressed as a dedicated coroutine type, pulled from a global queue
by whichever shard has spare cycles, yielding back cooperatively when
foreground work arrives, and completing to the submitting shard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Lock-free MPMC queue of type-erased coroutine handles with a relaxed
size counter for a cheap empty check, per the run-anywhere compute
tasks design doc (v0 scope).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A second idle-handler seam consulted from the idle branch, gated on a
bool so the unused feature costs one branch on the idle path and
nothing on the busy path. Keeps the public set_idle_cpu_handler()
available to applications.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
compute::task<T> is a coroutine with no core affinity, executed
checkpoint-to-checkpoint by whichever shard has spare CPU via the
reactor's compute idle handler, strictly below normal seastar work.
The closed awaitable set (checkpoint() only) makes shard-affine
seastar machinery unreachable at compile time. submit()/completion
are shard-affine: the returned future resolves on the submitting
shard, thread_pool-style, via smp::submit_to.

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>
With 2 of 4 shards saturated by foreground tasks, all compute
iterations execute on the idle shards: the participant only runs from
the idle branch, which a shard with a non-empty task queue never
reaches. Validated in dev and debug modes (debug forces a yield at
every checkpoint): 0 of 4000 iterations on the busy shards.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread src/core/compute_task.cc
};

compute_queue& the_queue() {
static compute_queue instance;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

iirc singletons are sly mutex operations. This probably should be a static registration slot provided by the main method / on startup

Comment thread src/core/compute_task.cc
auto& cq = the_queue();
// push() only fails on node allocation failure, which is not
// recoverable here.
auto ok = cq.q.push(h.address());

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

these have to be some of the worst variable names I have ever seen

Comment thread src/core/compute_task.cc

size_t queue_size() noexcept {
auto s = the_queue().size.load(std::memory_order_relaxed);
return s < 0 ? 0 : static_cast<size_t>(s);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

well that shouldn't happen

/// Install the run-anywhere compute participant (see seastar::compute).
/// Same contract as set_idle_cpu_handler; a separate slot so the public
/// idle handler remains available to applications.
void set_compute_idle_handler(idle_cpu_handler&& handler) {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

and why did we not simply use the set_idle_cpu_handler?

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