Skip to content

Commit cc4f3d0

Browse files
authored
Fix retry option without expiration in test env (#315)
We allow user to specify either expiration or maxAttempt in retry options. Currently if only maxAttempt is set, test Env state machine will throw exception. Moreover, if only maxAttempt is set, expiration should be treated as infinite when deciding whether to retry or not.
1 parent 60408a1 commit cc4f3d0

File tree

2 files changed

+62
-19
lines changed

2 files changed

+62
-19
lines changed

src/main/java/com/uber/cadence/internal/testservice/RetryState.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ final class RetryState {
3434

3535
private RetryState(RetryPolicy retryPolicy, long expirationTime, int attempt) {
3636
this.retryPolicy = retryPolicy;
37-
this.expirationTime = expirationTime;
37+
this.expirationTime =
38+
retryPolicy.getExpirationIntervalInSeconds() == 0 ? Long.MAX_VALUE : expirationTime;
3839
this.attempt = attempt;
3940
}
4041

@@ -121,10 +122,6 @@ static RetryPolicy validateRetryPolicy(RetryPolicy policy) throws BadRequestErro
121122
if (policy.getMaximumAttempts() < 0) {
122123
throw new BadRequestError("MaximumAttempts cannot be less than 0 on retry policy.");
123124
}
124-
if (policy.getExpirationIntervalInSeconds() <= 0) {
125-
throw new BadRequestError(
126-
"ExpirationIntervalInSeconds must be greater than 0 on retry policy.");
127-
}
128125
if (policy.getMaximumAttempts() == 0 && policy.getExpirationIntervalInSeconds() == 0) {
129126
throw new BadRequestError(
130127
"MaximumAttempts and ExpirationIntervalInSeconds are both 0. At least one of them must be specified.");

src/test/java/com/uber/cadence/workflow/WorkflowTest.java

Lines changed: 60 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ public void testSync() {
399399
"executeActivity TestActivities::activity2");
400400
}
401401

402-
public static class TestActivityRetry implements TestWorkflow1 {
402+
public static class TestActivityRetryWithMaxAttempts implements TestWorkflow1 {
403403

404404
@Override
405405
@SuppressWarnings("Finally")
@@ -408,12 +408,9 @@ public String execute(String taskList) {
408408
new ActivityOptions.Builder()
409409
.setTaskList(taskList)
410410
.setHeartbeatTimeout(Duration.ofSeconds(5))
411-
.setScheduleToCloseTimeout(Duration.ofSeconds(5))
412-
.setScheduleToStartTimeout(Duration.ofSeconds(5))
413-
.setStartToCloseTimeout(Duration.ofSeconds(10))
411+
.setScheduleToCloseTimeout(Duration.ofSeconds(3))
414412
.setRetryOptions(
415413
new RetryOptions.Builder()
416-
.setExpiration(Duration.ofSeconds(100))
417414
.setMaximumInterval(Duration.ofSeconds(1))
418415
.setInitialInterval(Duration.ofSeconds(1))
419416
.setMaximumAttempts(3)
@@ -426,16 +423,62 @@ public String execute(String taskList) {
426423
activities.heartbeatAndThrowIO();
427424
} finally {
428425
if (Workflow.currentTimeMillis() - start < 2000) {
429-
throw new RuntimeException("Activity retried without delay");
426+
fail("Activity retried without delay");
430427
}
431428
}
432429
return "ignored";
433430
}
434431
}
435432

436433
@Test
437-
public void testActivityRetry() {
438-
startWorkerFor(TestActivityRetry.class);
434+
public void testActivityRetryWithMaxAttempts() {
435+
startWorkerFor(TestActivityRetryWithMaxAttempts.class);
436+
TestWorkflow1 workflowStub =
437+
workflowClient.newWorkflowStub(
438+
TestWorkflow1.class, newWorkflowOptionsBuilder(taskList).build());
439+
try {
440+
workflowStub.execute(taskList);
441+
fail("unreachable");
442+
} catch (WorkflowException e) {
443+
assertTrue(e.getCause().getCause() instanceof IOException);
444+
}
445+
assertEquals(activitiesImpl.toString(), 3, activitiesImpl.invocations.size());
446+
}
447+
448+
public static class TestActivityRetryWithExpiration implements TestWorkflow1 {
449+
450+
@Override
451+
@SuppressWarnings("Finally")
452+
public String execute(String taskList) {
453+
ActivityOptions options =
454+
new ActivityOptions.Builder()
455+
.setTaskList(taskList)
456+
.setHeartbeatTimeout(Duration.ofSeconds(5))
457+
.setScheduleToCloseTimeout(Duration.ofSeconds(3))
458+
.setRetryOptions(
459+
new RetryOptions.Builder()
460+
.setExpiration(Duration.ofSeconds(3))
461+
.setMaximumInterval(Duration.ofSeconds(1))
462+
.setInitialInterval(Duration.ofSeconds(1))
463+
.setDoNotRetry(AssertionError.class)
464+
.build())
465+
.build();
466+
TestActivities activities = Workflow.newActivityStub(TestActivities.class, options);
467+
long start = Workflow.currentTimeMillis();
468+
try {
469+
activities.heartbeatAndThrowIO();
470+
} finally {
471+
if (Workflow.currentTimeMillis() - start < 2000) {
472+
fail("Activity retried without delay");
473+
}
474+
}
475+
return "ignored";
476+
}
477+
}
478+
479+
@Test
480+
public void testActivityRetryWithExiration() {
481+
startWorkerFor(TestActivityRetryWithExpiration.class);
439482
TestWorkflow1 workflowStub =
440483
workflowClient.newWorkflowStub(
441484
TestWorkflow1.class, newWorkflowOptionsBuilder(taskList).build());
@@ -1241,17 +1284,17 @@ public void testMemo() {
12411284
startWorkerFor(TestMultiargsWorkflowsImpl.class);
12421285
WorkflowOptions workflowOptions = newWorkflowOptionsBuilder(taskList).setMemo(memo).build();
12431286
TestMultiargsWorkflowsFunc stubF =
1244-
workflowClient.newWorkflowStub(TestMultiargsWorkflowsFunc.class, workflowOptions);
1287+
workflowClient.newWorkflowStub(TestMultiargsWorkflowsFunc.class, workflowOptions);
12451288
WorkflowExecution executionF = WorkflowClient.start(stubF::func);
12461289

12471290
GetWorkflowExecutionHistoryResponse historyResp =
1248-
WorkflowExecutionUtils.getHistoryPage(
1249-
new byte[] {}, testEnvironment.getWorkflowService(), DOMAIN, executionF);
1291+
WorkflowExecutionUtils.getHistoryPage(
1292+
new byte[] {}, testEnvironment.getWorkflowService(), DOMAIN, executionF);
12501293
HistoryEvent startEvent = historyResp.history.getEvents().get(0);
12511294
Memo memoFromEvent = startEvent.workflowExecutionStartedEventAttributes.getMemo();
12521295
byte[] memoBytes = memoFromEvent.getFields().get(testMemoKey).array();
12531296
String memoRetrieved =
1254-
JsonDataConverter.getInstance().fromData(memoBytes, String.class, String.class);
1297+
JsonDataConverter.getInstance().fromData(memoBytes, String.class, String.class);
12551298
assertEquals(testMemoValue, memoRetrieved);
12561299
}
12571300
}
@@ -3761,7 +3804,9 @@ public void testGetVersion() {
37613804
}
37623805

37633806
static CompletableFuture<Boolean> executionStarted = new CompletableFuture<>();
3764-
public static class TestGetVersionWithoutDecisionEventWorkflowImpl implements TestWorkflowSignaled {
3807+
3808+
public static class TestGetVersionWithoutDecisionEventWorkflowImpl
3809+
implements TestWorkflowSignaled {
37653810

37663811
CompletablePromise<Boolean> signalReceived = Workflow.newPromise();
37673812

@@ -3774,7 +3819,8 @@ public String execute() {
37743819
executionStarted.complete(true);
37753820
signalReceived.get();
37763821
} else {
3777-
// Execute getVersion in replay mode. In this case we have no decision event, only a signal.
3822+
// Execute getVersion in replay mode. In this case we have no decision event, only a
3823+
// signal.
37783824
int version = Workflow.getVersion("test_change", Workflow.DEFAULT_VERSION, 1);
37793825
if (version == Workflow.DEFAULT_VERSION) {
37803826
signalReceived.get();

0 commit comments

Comments
 (0)