Skip to content

Auto-enroll pollers into autoscaling on PollerAutoscalingAutoEnroll (Java SDK)#2953

Merged
veeral-patel merged 4 commits into
mainfrom
veeral/poller-autoscaling-auto-enroll
Jul 21, 2026
Merged

Auto-enroll pollers into autoscaling on PollerAutoscalingAutoEnroll (Java SDK)#2953
veeral-patel merged 4 commits into
mainfrom
veeral/poller-autoscaling-auto-enroll

Conversation

@veeral-patel

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

Copy link
Copy Markdown
Contributor

Mirrors the Go SDK (temporalio/sdk-go#2442).

What

When a namespace advertises the PollerAutoscalingAutoEnroll capability, workers automatically use poller autoscaling for any poller type left at its default — i.e. where the user set neither MaxConcurrent<Type>TaskPollers nor a <Type>TaskPollerBehavior. Explicitly configured pollers are left alone. Applies to workflow, activity, and nexus pollers.

How

  • Worker records, per poller type, whether it was left at its default (read from the raw WorkerOptions, before defaults are applied).
  • At start() — once namespace capabilities are known — each eligible poller's behavior is swapped to autoscaling before its poller is created.
  • Auto-enroll also implies scale-down support, so NamespaceCapabilities treats it as enabling pollerAutoscaling too.

Proto

Bumps the temporal/api submodule to pick up the poller_autoscaling_auto_enroll capability (api #803).

Testing

  • PollerAutoscalingAutoEnrollTest — the enroll decision (default + capability → autoscaling; capability off, explicit count, or explicit behavior → unchanged).
  • WorkerPollerAutoEnrollEligibilityTestWorker marks defaults eligible and explicit config ineligible, per type.
  • Existing poller/worker tests still pass.

When a namespace advertises the PollerAutoscalingAutoEnroll capability,
automatically switch a poller type to poller autoscaling if the user left
it at its default (set neither MaxConcurrent<Type>TaskPollers nor a
<Type>TaskPollerBehavior). Explicitly configured pollers are untouched.

Auto-enroll implies full autoscaling support, so it also enables
serverSupportsAutoscaling (scale-down). Applies to workflow, activity, and
nexus pollers.

The decision is made at worker start(), after namespace capabilities are
loaded, by rebuilding each dormant worker's PollerOptions before its poller
is created. Per-poller-type eligibility is captured at Worker construction
from the raw (pre-defaulting) WorkerOptions and threaded through
PollerOptions.

Also bumps the temporal/api proto submodule to pick up the
poller_autoscaling_auto_enroll namespace capability (api #803).
@veeral-patel
veeral-patel requested a review from a team as a code owner July 15, 2026 21:17
@veeral-patel
veeral-patel marked this pull request as draft July 15, 2026 21:19
@veeral-patel veeral-patel changed the title Auto-enroll pollers into autoscaling on PollerAutoscalingAutoEnroll Auto-enroll pollers into autoscaling on PollerAutoscalingAutoEnroll (Java SDK) Jul 16, 2026
@veeral-patel
veeral-patel marked this pull request as ready for review July 17, 2026 17:45

@Sushisource Sushisource left a comment

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.

Looking good to me!

pollerAutoscalingAutoEnroll.set(true);
}
// Auto-enroll implies full poller autoscaling support, including scaling down, so it also
// enables pollerAutoscaling (which drives serverSupportsAutoscaling in PollScaleReportHandle).

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.

not entirely sure the purpose for this comment, why is "scaling down" relevant here?

@veeral-patel veeral-patel Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed the comment, thank you

// at start() when the namespace advertises the PollerAutoscalingAutoEnroll capability. This
// must
// be read from the raw (pre-defaulting) options, since validateAndBuildWithDefaults() fills in
// a

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.

spacing is weird on this comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed this

Comment on lines +143 to +145
boolean workflowTaskAutoEnrollEligible =
options == null
|| (options.getWorkflowTaskPollersBehavior() == null

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.

I think we need to preserve a "setter was explicitly called" as private/internal metadata through build, validation, and copy-building, then transfer that into the internal PollerOptions. Only WorkerOptions.Builder knows whether the user actually called either poller setter.

@veeral-patel veeral-patel Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good idea, this is done.

I added a private {workflow,activity,nexus}TaskPollersConfigured flag, set to true inside both setters for each type (setMaxConcurrentTaskPollers and setTaskPollersBehavior).

@@ -0,0 +1,138 @@
package io.temporal.worker;

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.

Can we add tests showing that pollers remain eligible when default options come from WorkerOptions.getDefaultInstance(), validateAndBuildWithDefaults(), or a copy of either? And a test that explicitly sets the count to the numerical default of 5 proving its ineligible.

It would also be useful to have one startup-path test that enables the namespace capability, starts workflow/activity/Nexus workers, and verifies their effective poller behavior becomes autoscaling. I think we're missing a full E2E wiring test here.

@veeral-patel veeral-patel Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Can we add tests showing that pollers remain eligible when default options come from WorkerOptions.getDefaultInstance(), validateAndBuildWithDefaults(), or a copy of either? And a test that explicitly sets the count to the numerical default of 5 proving its ineligible.

Done, added the following test cases under WorkerPollerAutoEnrollEligibilityTest:

Test case Summary Expected
defaultInstanceMakesEveryPollerTypeEligible getDefaultInstance() carries the numeric default count (5), but no setter was called — so intent-tracking keeps it eligible Eligible (all types)
validateAndBuildWithDefaultsMakesEveryPollerTypeEligible Same for newBuilder().validateAndBuildWithDefaults(): defaulting fills in 5 without marking the pollers configured Eligible (all types)
copyOfDefaultInstanceIsEligible Copying a default-options object (via both build() and validateAndBuildWithDefaults()) preserves the "no setter called" state, so the copy stays eligible too Eligible (all types)
explicitCountEqualToNumericDefaultIsIneligible User explicitly calls setMaxConcurrentWorkflowTaskPollers(5) — same value as the default, but now the setter was invoked, so it must count as configured Ineligible (workflow); others eligible

It would also be useful to have one startup-path test that enables the namespace capability, starts workflow/activity/Nexus workers, and verifies their effective poller behavior becomes autoscaling. I think we're missing a full E2E wiring test here.

Done, added this test: WorkerPollerAutoEnrollStartupTest.autoEnrollAtStartupSwitchesPollersToAutoscaling.

It starts a Worker (with a NamespaceCapabilities advertising the capability) that has workflow, activity, and nexus implementations registered, then asserts all three pollers' effective behavior is PollerBehaviorAutoscaling after start(). This covers the full path: WorkerOptions → eligibility → start-time enrollment → AsyncPoller.

- Determine auto-enroll eligibility from whether the user actually called a
  poller setter (MaxConcurrent<Type>TaskPollers or <Type>TaskPollerBehavior),
  tracked on WorkerOptions.Builder and carried through build/validate/copy,
  instead of inferring from the resolved option values. This fixes the case
  where options from getDefaultInstance()/validateAndBuildWithDefaults() carry
  the numeric default (5) and would wrongly look explicitly configured.
- Worker reads the new WorkerOptions eligibility getters instead of the raw
  pre-defaulting options.
- Reword the NamespaceCapabilities comment to explain why auto-enroll also
  enables pollerAutoscaling (serverSupportsAutoscaling in PollScaleReportHandle),
  and fix the mangled comment in Worker.
- Expand tests: eligibility from getDefaultInstance()/validateAndBuildWithDefaults()
  and copies stays eligible; an explicit count equal to the numeric default is
  ineligible. Add a full start-path E2E test asserting workflow/activity/nexus
  pollers become autoscaling when the namespace advertises the capability.
The auto-enroll capability no longer forces NamespaceCapabilities.pollerAutoscaling
on. The server advertises pollerAutoscaling independently (and unconditionally), so
the pre-existing getPollerAutoscaling() read already enables scale-down for
auto-enrolled pollers; the extra coupling was redundant and conflated the
scale-down flag with the enrollment decision.

setFromCapabilities reverts to reading each capability separately. The auto-enroll
field/getter stay, since the enrollment decision still needs them and is independent
of isPollerAutoscaling(). Replaced the obsolete implication test with one asserting
the two capabilities are independent.
@veeral-patel

Copy link
Copy Markdown
Contributor Author

Tested this change manually successfully.

Setup: Built temporal server from my branch, 1 worker, 2 namespaces. Examined the logs in order to check.

default namespace:
=== server capability PollerAutoscalingAutoEnroll=false
=== worker started …
=== done for namespace=default
No async-poller lines → fixed pollers (MultiThreadedPoller, PollerBehaviorSimpleMaximum).

autoscale-ns namespace:
=== server capability PollerAutoscalingAutoEnroll=true
INFO io.temporal.internal.worker.AsyncPoller - Starting async poller: StickyWorkflowPollTask
INFO io.temporal.internal.worker.AsyncPoller - Starting async poller: NormalWorkflowPollTask
INFO io.temporal.internal.worker.AsyncPoller - Starting async poller: AsyncActivityPollTask
Three async pollers → auto-enrolled into autoscaling for workflow (sticky + normal) and activity.

@veeral-patel
veeral-patel merged commit 6d09a34 into main Jul 21, 2026
18 checks passed
@veeral-patel
veeral-patel deleted the veeral/poller-autoscaling-auto-enroll branch July 21, 2026 21:32
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.

3 participants