Auto-enroll pollers into autoscaling on PollerAutoscalingAutoEnroll (Rust SDK) #1406
Auto-enroll pollers into autoscaling on PollerAutoscalingAutoEnroll (Rust SDK) #1406veeral-patel wants to merge 1 commit into
Conversation
Automatically switch workflow, activity, and nexus pollers to autoscaling when the namespace advertises the `poller_autoscaling_auto_enroll` capability. This only applies to poller types left at their default (the worker set neither a fixed poller count nor a poller behavior); explicitly configured pollers are left unchanged. Ports temporalio/sdk-go#2442 to sdk-core so it is shared by every SDK built on top of it. - Parse the capability in `Worker::validate` and force-enable `poller_autoscaling` (so auto-enrolled pollers may scale down), tracked on `NamespaceCapabilities`. - Add per-type auto-enroll eligibility to `WorkerConfig`. - `PollScaler` now resolves its effective behavior lazily, when polling starts (guaranteed after `validate`), deferring the report handle, ingestor task, and `no_retry` so a default poller can be enrolled into autoscaling without changing construction ordering. - Surface eligibility through the high-level SDK `WorkerOptions` (poller fields are now `Option`) and the c-bridge (unset == both FFI pointers null). Note: unlike the Go PR there is no session-worker case and no heartbeat change, as neither concept exists in sdk-core.
| /// Converts an FFI poller behavior into an optional core poller behavior. Both fields null means | ||
| /// the poller was left unset by lang: the caller should apply the default behavior and treat the | ||
| /// poller as eligible for automatic enrollment into poller autoscaling. | ||
| fn ffi_poller_behavior_opt( |
There was a problem hiding this comment.
This should probably just be an Into impl
| // The watch channel is created eagerly because `WFTPollerShared` needs `active_rx` before the | ||
| // poller task starts. | ||
| active_tx: watch::Sender<usize>, | ||
| active_rx: watch::Receiver<usize>, | ||
| num_pollers_handler: Option<Arc<F>>, | ||
| // Inputs captured so the effective behavior can be resolved lazily in `activate`, once the | ||
| // namespace capabilities discovered by `Worker::validate` are known (which is guaranteed to be | ||
| // before polling begins). | ||
| configured_behavior: PollerBehavior, | ||
| auto_enroll_eligible: bool, | ||
| capabilities: Arc<NamespaceCapabilities>, | ||
| last_successful_poll_time: Arc<AtomicCell<Option<SystemTime>>>, | ||
| shutdown: CancellationToken, | ||
| /// Shared with the poll function so `no_retry` tracks the resolved (post-`activate`) behavior. | ||
| autoscaling_active: Arc<AtomicBool>, | ||
| // Resolved by `activate`, before the poll loop issues any poll. | ||
| report_handle: Option<Arc<PollScalerReportHandle>>, |
There was a problem hiding this comment.
This is a lot of additional complexity. I think things could overall become much, much simpler, if we just pass in the right PollerBehavior based on namespace capabilities rather than switching it dynamically.
We aren't expecting to dynamically flip between enabled and not enabled while the worker is running, right? I don't think the Go PR did that.
So IMO we should just delay spinning up pollers until after the validate call has happened (which might already be true)? And then this file doesn't need any changes at all, everything should just work as is.
| self.capabilities | ||
| .poller_autoscaling_auto_enroll | ||
| .store(true, Ordering::Relaxed); | ||
| // Auto-enroll implies full autoscaling support (including scaling down on |
There was a problem hiding this comment.
We don't need this. We can decouple these two capabilities.
| if auto_enroll_eligible && capabilities.poller_autoscaling_auto_enroll() { | ||
| // Mirrors the Go SDK's auto-enroll autoscaling defaults. | ||
| PollerBehavior::Autoscaling { | ||
| minimum: 1, |
There was a problem hiding this comment.
See if we can not pass in the min, max, initial so the existing default values are used. If we have to, see if we can reuse a shared constant instead of hard-coding
|
Closing in favor of this PR: #1425 |
Ports temporalio/sdk-go#2442 to
sdk-core, so it's shared by every SDK on top of it (Rust directly; TS/Python/.NET/Ruby via the c-bridge).What
When a namespace advertises the
poller_autoscaling_auto_enrollcapability, workers automatically switch their workflow, activity, and nexus pollers to autoscaling — but only for poller types the user left at their default. Pollers with an explicitly configured count or behavior are left alone.Why it's implemented this way
The capability is only known after
Worker::validate()(an async namespace lookup), but pollers are built earlier, synchronously. Instead of reordering that, thePollScalerresolves its behavior lazily, when polling starts — which always happens aftervalidate(). Alternatives (blocking network I/O during construction, or rebuilding pollers afterward) were more invasive.Key changes
Worker::validate()reads the new capability and also enablespoller_autoscalingso enrolled pollers can scale down.WorkerConfiggains three per-type "eligibility" flags (defaultfalse— nothing auto-enrolls unless a layer above opts in).PollScalerdefers its setup intoactivate()and, if the poller is eligible and the capability is present, usesAutoscaling { min: 1, max: 100, initial: 5 }(matching Go).Option::None; c-bridge: both FFI pointers null).Notes
sdk-core.WorkerOptionspoller fields becomeOption<PollerBehavior>(minor pre-1.0 break; existing.foo(x)calls still compile).Testing
New unit tests for behavior resolution,
validate()plumbing, and the c-bridge conversion. Fullsdk-corelib suite passes (368 tests); fmt + clippy clean.