Skip to content

Commit 654dd70

Browse files
authored
Merge branch 'main' into workeroptions-builder-isUsingVirtualThreadsOnWorkflowWorker-fix
2 parents 4d68e2e + 6d09a34 commit 654dd70

11 files changed

Lines changed: 647 additions & 17 deletions

File tree

temporal-sdk/src/main/java/io/temporal/internal/worker/ActivityWorker.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ final class ActivityWorker implements SuspendableWorker {
4343
private final String taskQueue;
4444
private final SingleWorkerOptions options;
4545
private final double taskQueueActivitiesPerSecond;
46-
private final PollerOptions pollerOptions;
46+
private PollerOptions pollerOptions;
4747
private final Scope workerMetricsScope;
4848
private final GrpcRetryer grpcRetryer;
4949
private final GrpcRetryer.GrpcRetryerOptions replyGrpcRetryerOptions;
@@ -83,6 +83,11 @@ public ActivityWorker(
8383
@Override
8484
public boolean start() {
8585
if (handler.isAnyTypeSupported()) {
86+
// Auto-enroll into poller autoscaling if the namespace advertises the capability and this
87+
// poller type was left at its default. Resolved here (after namespace capabilities are known)
88+
// so the poller built below reflects the effective behavior.
89+
this.pollerOptions =
90+
PollerOptions.maybeEnrollInPollerAutoscaling(pollerOptions, namespaceCapabilities);
8691
this.pollTaskExecutor =
8792
new PollTaskExecutor<>(
8893
namespace,

temporal-sdk/src/main/java/io/temporal/internal/worker/NamespaceCapabilities.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,15 @@
1010
*/
1111
public final class NamespaceCapabilities {
1212
private final AtomicBoolean pollerAutoscaling = new AtomicBoolean(false);
13+
private final AtomicBoolean pollerAutoscalingAutoEnroll = new AtomicBoolean(false);
1314
private final AtomicBoolean gracefulPollShutdown = new AtomicBoolean(false);
1415
private final AtomicBoolean workerHeartbeats = new AtomicBoolean(false);
1516
private final AtomicBoolean workerCommands = new AtomicBoolean(false);
1617

1718
public void setFromCapabilities(Capabilities capabilities) {
19+
if (capabilities.getPollerAutoscalingAutoEnroll()) {
20+
pollerAutoscalingAutoEnroll.set(true);
21+
}
1822
if (capabilities.getPollerAutoscaling()) {
1923
pollerAutoscaling.set(true);
2024
}
@@ -33,6 +37,10 @@ public boolean isPollerAutoscaling() {
3337
return pollerAutoscaling.get();
3438
}
3539

40+
public boolean isPollerAutoscalingAutoEnroll() {
41+
return pollerAutoscalingAutoEnroll.get();
42+
}
43+
3644
public boolean isGracefulPollShutdown() {
3745
return gracefulPollShutdown.get();
3846
}

temporal-sdk/src/main/java/io/temporal/internal/worker/NexusWorker.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ final class NexusWorker implements SuspendableWorker {
4646
private final String namespace;
4747
private final String taskQueue;
4848
private final SingleWorkerOptions options;
49-
private final PollerOptions pollerOptions;
49+
private PollerOptions pollerOptions;
5050
private final Scope workerMetricsScope;
5151
private final DataConverter dataConverter;
5252
private final GrpcRetryer grpcRetryer;
@@ -114,6 +114,11 @@ public NexusWorker(
114114
@Override
115115
public boolean start() {
116116
if (handler.start()) {
117+
// Auto-enroll into poller autoscaling if the namespace advertises the capability and this
118+
// poller type was left at its default. Resolved here (after namespace capabilities are known)
119+
// so the poller built below reflects the effective behavior.
120+
this.pollerOptions =
121+
PollerOptions.maybeEnrollInPollerAutoscaling(pollerOptions, namespaceCapabilities);
117122
this.pollTaskExecutor =
118123
new PollTaskExecutor<>(
119124
namespace,

temporal-sdk/src/main/java/io/temporal/internal/worker/PollerOptions.java

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import io.grpc.Status;
44
import io.grpc.StatusRuntimeException;
55
import io.temporal.worker.tuning.PollerBehavior;
6+
import io.temporal.worker.tuning.PollerBehaviorAutoscaling;
67
import java.time.Duration;
78
import java.util.concurrent.ExecutorService;
89
import org.slf4j.Logger;
@@ -26,6 +27,27 @@ public static PollerOptions getDefaultInstance() {
2627
return DEFAULT_INSTANCE;
2728
}
2829

30+
/**
31+
* If the given options are eligible for poller-autoscaling auto-enrollment (the user left this
32+
* poller type at its default) and the namespace advertises the auto-enroll capability, returns a
33+
* copy of the options with a default {@link PollerBehaviorAutoscaling} behavior. Otherwise
34+
* returns the options unchanged.
35+
*
36+
* <p>Must only be called before the worker's poller is created (i.e. at worker start), so the
37+
* resolved behavior is picked up when the poller is built.
38+
*/
39+
public static PollerOptions maybeEnrollInPollerAutoscaling(
40+
PollerOptions options, NamespaceCapabilities namespaceCapabilities) {
41+
if (options.isAutoscalingAutoEnrollEligible()
42+
&& namespaceCapabilities.isPollerAutoscalingAutoEnroll()
43+
&& !(options.getPollerBehavior() instanceof PollerBehaviorAutoscaling)) {
44+
return PollerOptions.newBuilder(options)
45+
.setPollerBehavior(new PollerBehaviorAutoscaling())
46+
.build();
47+
}
48+
return options;
49+
}
50+
2951
private static final PollerOptions DEFAULT_INSTANCE;
3052

3153
static {
@@ -46,6 +68,7 @@ public static final class Builder {
4668
private Thread.UncaughtExceptionHandler uncaughtExceptionHandler;
4769
private boolean usingVirtualThreads;
4870
private ExecutorService pollerTaskExecutorOverride;
71+
private boolean autoscalingAutoEnrollEligible;
4972

5073
private Builder() {}
5174

@@ -65,6 +88,7 @@ private Builder(PollerOptions options) {
6588
this.uncaughtExceptionHandler = options.getUncaughtExceptionHandler();
6689
this.usingVirtualThreads = options.isUsingVirtualThreads();
6790
this.pollerTaskExecutorOverride = options.getPollerTaskExecutorOverride();
91+
this.autoscalingAutoEnrollEligible = options.isAutoscalingAutoEnrollEligible();
6892
}
6993

7094
/** Defines interval for measuring poll rate. Larger the interval more spiky can be the load. */
@@ -152,6 +176,16 @@ public Builder setPollerTaskExecutorOverride(ExecutorService overrideTaskExecuto
152176
return this;
153177
}
154178

179+
/**
180+
* Marks whether this poller type was left at its default (the user set neither a fixed poller
181+
* count nor a poller behavior) and is therefore eligible for poller-autoscaling auto-enrollment
182+
* when the namespace advertises the capability.
183+
*/
184+
public Builder setAutoscalingAutoEnrollEligible(boolean autoscalingAutoEnrollEligible) {
185+
this.autoscalingAutoEnrollEligible = autoscalingAutoEnrollEligible;
186+
return this;
187+
}
188+
155189
public PollerOptions build() {
156190
if (uncaughtExceptionHandler == null) {
157191
uncaughtExceptionHandler =
@@ -180,7 +214,8 @@ public PollerOptions build() {
180214
uncaughtExceptionHandler,
181215
pollThreadNamePrefix,
182216
usingVirtualThreads,
183-
pollerTaskExecutorOverride);
217+
pollerTaskExecutorOverride,
218+
autoscalingAutoEnrollEligible);
184219
}
185220
}
186221

@@ -198,6 +233,7 @@ public PollerOptions build() {
198233
private final boolean usingVirtualThreads;
199234
private final ExecutorService pollerTaskExecutorOverride;
200235
private final PollerBehavior pollerBehavior;
236+
private final boolean autoscalingAutoEnrollEligible;
201237

202238
private PollerOptions(
203239
int maximumPollRateIntervalMilliseconds,
@@ -211,7 +247,8 @@ private PollerOptions(
211247
Thread.UncaughtExceptionHandler uncaughtExceptionHandler,
212248
String pollThreadNamePrefix,
213249
boolean usingVirtualThreads,
214-
ExecutorService pollerTaskExecutorOverride) {
250+
ExecutorService pollerTaskExecutorOverride,
251+
boolean autoscalingAutoEnrollEligible) {
215252
this.maximumPollRateIntervalMilliseconds = maximumPollRateIntervalMilliseconds;
216253
this.maximumPollRatePerSecond = maximumPollRatePerSecond;
217254
this.backoffCoefficient = backoffCoefficient;
@@ -224,6 +261,7 @@ private PollerOptions(
224261
this.pollThreadNamePrefix = pollThreadNamePrefix;
225262
this.usingVirtualThreads = usingVirtualThreads;
226263
this.pollerTaskExecutorOverride = pollerTaskExecutorOverride;
264+
this.autoscalingAutoEnrollEligible = autoscalingAutoEnrollEligible;
227265
}
228266

229267
public int getMaximumPollRateIntervalMilliseconds() {
@@ -274,6 +312,10 @@ public ExecutorService getPollerTaskExecutorOverride() {
274312
return pollerTaskExecutorOverride;
275313
}
276314

315+
public boolean isAutoscalingAutoEnrollEligible() {
316+
return autoscalingAutoEnrollEligible;
317+
}
318+
277319
@Override
278320
public String toString() {
279321
return "PollerOptions{"

temporal-sdk/src/main/java/io/temporal/internal/worker/WorkflowWorker.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ final class WorkflowWorker implements SuspendableWorker {
4848
private final WorkflowExecutorCache cache;
4949
private final WorkflowTaskHandler handler;
5050
private final String stickyTaskQueueName;
51-
private final PollerOptions pollerOptions;
51+
private PollerOptions pollerOptions;
5252
private final Scope workerMetricsScope;
5353
private final GrpcRetryer grpcRetryer;
5454
private final EagerActivityDispatcher eagerActivityDispatcher;
@@ -99,6 +99,11 @@ public WorkflowWorker(
9999
@Override
100100
public boolean start() {
101101
if (handler.isAnyTypeSupported()) {
102+
// Auto-enroll into poller autoscaling if the namespace advertises the capability and this
103+
// poller type was left at its default. Resolved here (after namespace capabilities are known)
104+
// so the poller built below reflects the effective behavior.
105+
this.pollerOptions =
106+
PollerOptions.maybeEnrollInPollerAutoscaling(pollerOptions, namespaceCapabilities);
102107
pollTaskExecutor =
103108
new PollTaskExecutor<>(
104109
namespace,

temporal-sdk/src/main/java/io/temporal/worker/Worker.java

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,15 @@ private static final class TaskSnapshot {
132132
Map<String, String> tags =
133133
new ImmutableMap.Builder<String, String>(1).put(MetricsTag.TASK_QUEUE, taskQueue).build();
134134
Scope taggedScope = metricsScope.tagged(tags);
135+
136+
// Poller types the user left at their default are auto-enrolled into poller autoscaling at
137+
// start() when the namespace advertises the PollerAutoscalingAutoEnroll capability. Eligibility
138+
// tracks whether the user called a poller setter (recorded on WorkerOptions.Builder), not the
139+
// resolved value, so a defaulted count of 5 is not mistaken for an explicit choice.
140+
boolean workflowTaskAutoEnrollEligible = this.options.isWorkflowTaskPollerAutoEnrollEligible();
141+
boolean activityTaskAutoEnrollEligible = this.options.isActivityTaskPollerAutoEnrollEligible();
142+
boolean nexusTaskAutoEnrollEligible = this.options.isNexusTaskPollerAutoEnrollEligible();
143+
135144
SingleWorkerOptions activityOptions =
136145
toActivityOptions(
137146
factoryOptions,
@@ -140,7 +149,8 @@ private static final class TaskSnapshot {
140149
contextPropagators,
141150
taggedScope,
142151
workerInstanceKey,
143-
workerControlTaskQueue);
152+
workerControlTaskQueue,
153+
activityTaskAutoEnrollEligible);
144154
if (this.options.isLocalActivityWorkerOnly()) {
145155
activityWorker = null;
146156
} else {
@@ -174,7 +184,8 @@ private static final class TaskSnapshot {
174184
contextPropagators,
175185
taggedScope,
176186
workerInstanceKey,
177-
workerControlTaskQueue);
187+
workerControlTaskQueue,
188+
nexusTaskAutoEnrollEligible);
178189
SlotSupplier<NexusSlotInfo> nexusSlotSupplier =
179190
this.options.getWorkerTuner() == null
180191
? new FixedSizeSlotSupplier<>(this.options.getMaxConcurrentNexusExecutionSize())
@@ -194,7 +205,8 @@ private static final class TaskSnapshot {
194205
contextPropagators,
195206
taggedScope,
196207
workerInstanceKey,
197-
workerControlTaskQueue);
208+
workerControlTaskQueue,
209+
workflowTaskAutoEnrollEligible);
198210
SingleWorkerOptions localActivityOptions =
199211
toLocalActivityOptions(
200212
factoryOptions,
@@ -901,7 +913,8 @@ private static SingleWorkerOptions toActivityOptions(
901913
List<ContextPropagator> contextPropagators,
902914
Scope metricsScope,
903915
String workerInstanceKey,
904-
String workerControlTaskQueue) {
916+
String workerControlTaskQueue,
917+
boolean autoEnrollEligible) {
905918
return toSingleWorkerOptions(
906919
factoryOptions,
907920
options,
@@ -920,6 +933,7 @@ private static SingleWorkerOptions toActivityOptions(
920933
: new PollerBehaviorSimpleMaximum(
921934
options.getMaxConcurrentActivityTaskPollers()))
922935
.setUsingVirtualThreads(options.isUsingVirtualThreadsOnActivityWorker())
936+
.setAutoscalingAutoEnrollEligible(autoEnrollEligible)
923937
.build())
924938
.setMetricsScope(metricsScope)
925939
.build();
@@ -932,7 +946,8 @@ private static SingleWorkerOptions toNexusOptions(
932946
List<ContextPropagator> contextPropagators,
933947
Scope metricsScope,
934948
String workerInstanceKey,
935-
String workerControlTaskQueue) {
949+
String workerControlTaskQueue,
950+
boolean autoEnrollEligible) {
936951
return toSingleWorkerOptions(
937952
factoryOptions,
938953
options,
@@ -948,6 +963,7 @@ private static SingleWorkerOptions toNexusOptions(
948963
: new PollerBehaviorSimpleMaximum(
949964
options.getMaxConcurrentNexusTaskPollers()))
950965
.setUsingVirtualThreads(options.isUsingVirtualThreadsOnNexusWorker())
966+
.setAutoscalingAutoEnrollEligible(autoEnrollEligible)
951967
.build())
952968
.setMetricsScope(metricsScope)
953969
.setUsingVirtualThreads(options.isUsingVirtualThreadsOnNexusWorker())
@@ -962,7 +978,8 @@ private static SingleWorkerOptions toWorkflowWorkerOptions(
962978
List<ContextPropagator> contextPropagators,
963979
Scope metricsScope,
964980
String workerInstanceKey,
965-
String workerControlTaskQueue) {
981+
String workerControlTaskQueue,
982+
boolean autoEnrollEligible) {
966983
Map<String, String> tags =
967984
new ImmutableMap.Builder<String, String>(1).put(MetricsTag.TASK_QUEUE, taskQueue).build();
968985

@@ -1005,6 +1022,7 @@ private static SingleWorkerOptions toWorkflowWorkerOptions(
10051022
? pollerBehavior
10061023
: new PollerBehaviorSimpleMaximum(maxConcurrentWorkflowTaskPollers))
10071024
.setUsingVirtualThreads(options.isUsingVirtualThreadsOnWorkflowWorker())
1025+
.setAutoscalingAutoEnrollEligible(autoEnrollEligible)
10081026
.build())
10091027
.setStickyQueueScheduleToStartTimeout(stickyQueueScheduleToStartTimeout)
10101028
.setStickyTaskQueueDrainTimeout(options.getStickyTaskQueueDrainTimeout())

0 commit comments

Comments
 (0)