Skip to content

Commit 323e49f

Browse files
committed
Validate eager activity reservation limits
1 parent e4a9841 commit 323e49f

3 files changed

Lines changed: 108 additions & 4 deletions

File tree

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,8 @@ public Builder setDisableEagerExecution(boolean disableEagerExecution) {
408408
* Sets the maximum number of activity slots that may be reserved for eager execution when
409409
* completing a workflow task.
410410
*
411-
* <p>The default is 3. Setting this to zero disables eager activity execution.
411+
* <p>The default is 3. The value must be positive. To disable eager activity execution, use
412+
* {@link #setDisableEagerExecution(boolean)}.
412413
*/
413414
public Builder setMaxEagerActivityReservationsPerWorkflowTask(
414415
int maxEagerActivityReservationsPerWorkflowTask) {
@@ -666,8 +667,9 @@ public WorkerOptions validateAndBuildWithDefaults() {
666667
Preconditions.checkState(
667668
maxConcurrentActivityExecutionSize >= 0, "negative maxConcurrentActivityExecutionSize");
668669
Preconditions.checkState(
669-
maxEagerActivityReservationsPerWorkflowTask >= 0,
670-
"negative maxEagerActivityReservationsPerWorkflowTask");
670+
maxEagerActivityReservationsPerWorkflowTask > 0,
671+
"maxEagerActivityReservationsPerWorkflowTask must be positive; use "
672+
+ "setDisableEagerExecution(true) to disable eager activity execution");
671673
Preconditions.checkState(
672674
maxConcurrentWorkflowTaskExecutionSize >= 0,
673675
"negative maxConcurrentWorkflowTaskExecutionSize");

temporal-sdk/src/test/java/io/temporal/worker/WorkerOptionsTest.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,4 +235,21 @@ public void verifyMaxTaskQueuePerSecondsDisablesEagerExecution() {
235235
WorkerOptions w2 = WorkerOptions.newBuilder().setMaxTaskQueueActivitiesPerSecond(2.0).build();
236236
assertTrue(w2.isEagerExecutionDisabled());
237237
}
238+
239+
@Test
240+
public void rejectsNonPositiveMaxEagerActivityReservationsPerWorkflowTask() {
241+
for (int value : new int[] {0, -1}) {
242+
IllegalStateException exception =
243+
assertThrows(
244+
IllegalStateException.class,
245+
() ->
246+
WorkerOptions.newBuilder()
247+
.setMaxEagerActivityReservationsPerWorkflowTask(value)
248+
.validateAndBuildWithDefaults());
249+
assertEquals(
250+
"maxEagerActivityReservationsPerWorkflowTask must be positive; use "
251+
+ "setDisableEagerExecution(true) to disable eager activity execution",
252+
exception.getMessage());
253+
}
254+
}
238255
}

temporal-sdk/src/test/java/io/temporal/workflow/activityTests/EagerActivityDispatchingTest.java

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,22 @@
33
import static org.junit.Assert.*;
44
import static org.junit.Assume.*;
55

6+
import io.grpc.CallOptions;
7+
import io.grpc.Channel;
8+
import io.grpc.ClientCall;
9+
import io.grpc.ClientInterceptor;
10+
import io.grpc.ForwardingClientCall;
11+
import io.grpc.MethodDescriptor;
612
import io.temporal.activity.ActivityOptions;
713
import io.temporal.api.history.v1.HistoryEvent;
14+
import io.temporal.api.workflowservice.v1.RespondWorkflowTaskCompletedRequest;
15+
import io.temporal.api.workflowservice.v1.WorkflowServiceGrpc;
816
import io.temporal.client.WorkflowClient;
917
import io.temporal.client.WorkflowOptions;
1018
import io.temporal.client.WorkflowStub;
1119
import io.temporal.common.WorkflowExecutionHistory;
1220
import io.temporal.internal.Config;
21+
import io.temporal.serviceclient.WorkflowServiceStubsOptions;
1322
import io.temporal.testUtils.CountingSlotSupplier;
1423
import io.temporal.testing.TestWorkflowEnvironment;
1524
import io.temporal.testing.internal.ExternalServiceTestConfigurator;
@@ -23,15 +32,19 @@
2332
import io.temporal.workflow.shared.TestActivities.TestActivitiesImpl;
2433
import java.time.Duration;
2534
import java.util.ArrayList;
35+
import java.util.Collections;
2636
import java.util.Set;
2737
import java.util.concurrent.TimeUnit;
38+
import java.util.concurrent.atomic.AtomicInteger;
2839
import java.util.stream.Collectors;
2940
import org.junit.*;
3041

3142
public class EagerActivityDispatchingTest {
3243
private static final String TASK_QUEUE = "test-eager-activity-dispatch";
3344
private TestWorkflowEnvironment env;
3445
private ArrayList<WorkerFactory> workerFactories;
46+
private final EagerActivityRequestInterceptor eagerActivityRequestInterceptor =
47+
new EagerActivityRequestInterceptor();
3548

3649
private final TestActivitiesImpl activitiesImpl = new TestActivitiesImpl();
3750
CountingSlotSupplier<WorkflowSlotInfo> workflowTaskSlotSupplier = new CountingSlotSupplier<>(100);
@@ -42,9 +55,16 @@ public class EagerActivityDispatchingTest {
4255

4356
@Before
4457
public void setUp() throws Exception {
58+
eagerActivityRequestInterceptor.reset();
4559
this.env =
4660
TestWorkflowEnvironment.newInstance(
47-
ExternalServiceTestConfigurator.configuredTestEnvironmentOptions().build());
61+
ExternalServiceTestConfigurator.configuredTestEnvironmentOptions()
62+
.setWorkflowServiceStubsOptions(
63+
WorkflowServiceStubsOptions.newBuilder()
64+
.setGrpcClientInterceptors(
65+
Collections.singletonList(eagerActivityRequestInterceptor))
66+
.build())
67+
.build());
4868
this.workerFactories = new ArrayList<>();
4969
}
5070

@@ -125,6 +145,25 @@ public void testEagerActivities() {
125145
assertFalse(activityTaskStartedEventIdentity.contains("worker2"));
126146
}
127147

148+
@Test
149+
public void testMaxEagerActivityReservationsPerWorkflowTask() {
150+
setupWorker(
151+
"worker1",
152+
WorkerOptions.newBuilder()
153+
.setMaxEagerActivityReservationsPerWorkflowTask(2)
154+
.setDisableEagerExecution(false),
155+
true);
156+
157+
EagerActivityTestWorkflow workflowStub =
158+
env.getWorkflowClient()
159+
.newWorkflowStub(
160+
EagerActivityTestWorkflow.class,
161+
WorkflowOptions.newBuilder().setTaskQueue(TASK_QUEUE).build());
162+
workflowStub.execute(true);
163+
164+
assertEquals(2, eagerActivityRequestInterceptor.getEagerActivityRequestCount());
165+
}
166+
128167
@Test
129168
public void testNoEagerActivitiesIfDisabledOnWorker() {
130169
assumeTrue(
@@ -222,4 +261,50 @@ public void execute(boolean enableEagerActivityDispatch) {
222261
Promise.allOf(promises).get();
223262
}
224263
}
264+
265+
private static class EagerActivityRequestInterceptor implements ClientInterceptor {
266+
private final AtomicInteger eagerActivityRequestCount = new AtomicInteger(-1);
267+
268+
@Override
269+
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
270+
MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
271+
if (method == WorkflowServiceGrpc.getRespondWorkflowTaskCompletedMethod()) {
272+
return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(
273+
next.newCall(method, callOptions)) {
274+
@Override
275+
public void sendMessage(ReqT message) {
276+
RespondWorkflowTaskCompletedRequest request =
277+
(RespondWorkflowTaskCompletedRequest) message;
278+
long activityCommandCount =
279+
request.getCommandsList().stream()
280+
.filter(command -> command.hasScheduleActivityTaskCommandAttributes())
281+
.count();
282+
if (activityCommandCount > 0) {
283+
int eagerRequestCount =
284+
(int)
285+
request.getCommandsList().stream()
286+
.filter(command -> command.hasScheduleActivityTaskCommandAttributes())
287+
.filter(
288+
command ->
289+
command
290+
.getScheduleActivityTaskCommandAttributes()
291+
.getRequestEagerExecution())
292+
.count();
293+
eagerActivityRequestCount.compareAndSet(-1, eagerRequestCount);
294+
}
295+
super.sendMessage(message);
296+
}
297+
};
298+
}
299+
return next.newCall(method, callOptions);
300+
}
301+
302+
int getEagerActivityRequestCount() {
303+
return eagerActivityRequestCount.get();
304+
}
305+
306+
void reset() {
307+
eagerActivityRequestCount.set(-1);
308+
}
309+
}
225310
}

0 commit comments

Comments
 (0)