Skip to content

feat(sdk): auto-enroll default pollers into autoscaling#1425

Open
veeral-patel wants to merge 1 commit into
mainfrom
feat/poller-autoscaling-auto-enroll-v2
Open

feat(sdk): auto-enroll default pollers into autoscaling#1425
veeral-patel wants to merge 1 commit into
mainfrom
feat/poller-autoscaling-auto-enroll-v2

Conversation

@veeral-patel

@veeral-patel veeral-patel commented Jul 24, 2026

Copy link
Copy Markdown

Ports temporalio/sdk-go#2442 to sdk-core, so every SDK built on it (Rust directly; TS/Python/.NET/Ruby via the c-bridge) gets it.

Supersedes #1406 — same feature, reworked per review feedback (see "How" below).

What it does

A worker's pollers can run in one of two modes: a fixed number, or autoscaling (the server tells the worker to add/remove pollers based on load).

This makes a worker automatically use autoscaling when the server says the namespace supports it (poller_autoscaling_auto_enroll) — but only for pollers the user didn't configure. If you set a poller count or behavior yourself, it's left exactly as you asked.

How it works

The worker figures out the right mode once, right after it checks in with the server (validate()), and builds its pollers then. Previously the pollers were built before the check-in, so #1406 had to flip the mode later at runtime; this version avoids that entirely (poll_buffer.rs is unchanged here).

  • worker/mod.rs: poller setup is deferred until after the server check-in, so the mode is known up front.
  • The Rust SDK now calls validate() on startup (it never did before, so it was missing all namespace capabilities).

Testing

Unit tests for the mode decision and the capability parsing; c-bridge conversion tests. cargo check/fmt/clippy clean; sdk-core lib 368 pass, c-bridge 14 pass.

@veeral-patel
veeral-patel requested a review from a team as a code owner July 24, 2026 20:19
@veeral-patel
veeral-patel force-pushed the feat/poller-autoscaling-auto-enroll-v2 branch 2 times, most recently from 5954b11 to d600242 Compare July 24, 2026 21:22
Comment thread crates/sdk-core/src/worker/mod.rs Outdated
/// `poller_autoscaling_auto_enroll` capability. Layers above core (the Rust SDK, the c-bridge)
/// set this; direct core users leave it `false`.
#[builder(default = false)]
pub workflow_task_poller_behavior_auto_enroll: bool,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't think we need these. They can just explicitly set the option for whatever they do want, rather than disabling the enrollment explicitly.

Comment thread crates/sdk-core-c-bridge/src/worker.rs Outdated
Comment on lines +100 to +102
impl TryFrom<&PollerBehavior> for Option<temporalio_sdk_core::PollerBehavior> {
type Error = anyhow::Error;
fn try_from(value: &PollerBehavior) -> Result<Self, Self::Error> {
fn try_from(value: &PollerBehavior) -> anyhow::Result<Self> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This won't compile (and doesn't, in CI).

Comment thread crates/sdk-core/src/worker/mod.rs Outdated
at_task_mgr: Option<WorkerActivityTasks>,
/// The poller-dependent subsystems (workflows, activity task manager, nexus manager). Built
/// lazily once namespace capabilities are known so effective poller behavior can be resolved.
pollers: std::sync::OnceLock<PollerSubsystems>,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

These really aren't (just) pollers. TaskSubsystems would be better I think.

Comment thread crates/sdk-core/src/worker/mod.rs Outdated
Comment on lines +1155 to +1160
// If shutdown was already initiated before the pollers were built (e.g. a worker that
// never ran validate() and only starts polling as part of draining during shutdown),
// propagate the shutdown signal to the freshly-built subsystems so they don't hang waiting
// for work that will never arrive. `initiate_shutdown` deliberately does not force a build,
// so it can't have done this for us.
if shutdown_token.is_cancelled() {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If the task subsystems were never built, we shouldn't need to do anything at all I think?

Comment thread crates/sdk-core/src/worker/mod.rs Outdated
pollers: std::sync::OnceLock<PollerSubsystems>,
/// Builds [PollerSubsystems] on first access. Taken exactly once.
#[allow(clippy::type_complexity)]
poller_builder: parking_lot::Mutex<Option<Box<dyn FnOnce() -> PollerSubsystems + Send>>>,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we could avoid needing this, and I think we can kill two birds with one stone and get rid of PollerInitInputs at the same time.

The pollers (now task_subsystems) field, should instead be a https://doc.rust-lang.org/beta/std/sync/struct.LazyLock.html

The closure given to the LazyLock constructor can be the same one you give now, and build_poller_subsystems can be inlined to eliminate the need for PollerInitInputs

Comment thread crates/sdk-core/src/worker/mod.rs Outdated
Comment on lines +2221 to +2222
/// Whether this worker polls for (non-local) activities at all.
activities_enabled: bool,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We don't need a separate bool here. Simply not initializing the OnceLock is sufficient.

When a namespace advertises the `poller_autoscaling_auto_enroll`
capability, workers automatically switch workflow, activity, and nexus
pollers to autoscaling — but only for poller types the user left at their
default (neither a fixed poller count nor an explicit behavior).
Explicitly configured pollers are left unchanged.

The effective `PollerBehavior` is resolved once, after the namespace
capability is known. Poller construction is deferred out of the
synchronous `Worker::new_with_pollers` into a lazily-built step that runs
after `Worker::validate()` populates capabilities (and on first poll for
paths that skip validate, e.g. replay). This keeps `poll_buffer.rs` /
`PollScaler` free of any runtime mode-switching — the behavior is simply
passed in already resolved.

Eligibility (user left the poller at its default) is signalled from the
layers above core: the Rust SDK exposes the poller options as `Option`,
and the c-bridge treats unset (both FFI pointers null) as eligible.

Ports temporalio/sdk-go#2442.
@veeral-patel
veeral-patel force-pushed the feat/poller-autoscaling-auto-enroll-v2 branch from d600242 to e32bf5f Compare July 25, 2026 00:46
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.

2 participants