Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
## v1.2.0

### Updates
* Add `thenAccept` and `thenApply` to `Task` interface ([#148](https://github.com/microsoft/durabletask-java/pull/148))
* Support Suspend and Resume Client APIs ([#151](https://github.com/microsoft/durabletask-java/pull/151))
* Support restartInstance and pass restartPostUri in HttpManagementPayload ([#108](https://github.com/microsoft/durabletask-java/issues/108))

### Fixes
* Fix retry policy not working with anyOf/allOf pattern ([#152](https://github.com/microsoft/durabletask-java/pull/152))

## v1.1.1

### Updates
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -376,9 +376,15 @@ public <V> Task<V> callSubOrchestrator(
private <V> Task<V> createAppropriateTask(TaskFactory<V> taskFactory, TaskOptions options) {
// Retry policies and retry handlers will cause us to return a RetriableTask<V>
if (options != null && options.hasRetryPolicy()) {
return new RetriableTask<V>(this, taskFactory, options.getRetryPolicy());
} if (options != null && options.hasRetryHandler()) {
return new RetriableTask<V>(this, taskFactory, options.getRetryHandler());
// invoke the factory create logic here, so actions can be added to
// pendingActions when call the callActivity/callSubOrchestrator.
Task<V> currentTask = taskFactory.create();
return new RetriableTask<V>(this, currentTask, options.getRetryPolicy());
} else if (options != null && options.hasRetryHandler()) {
// invoke the factory create logic here, so actions can be added to
// pendingActions when call the callActivity/callSubOrchestrator.
Task<V> currentTask = taskFactory.create();
return new RetriableTask<V>(this, currentTask, options.getRetryHandler());
} else {
// Return a single vanilla task without any wrapper
return taskFactory.create();
Expand Down Expand Up @@ -940,44 +946,44 @@ private class RetriableTask<V> extends CompletableTask<V> {
private final RetryHandler handler;
private final TaskOrchestrationContext context;
private final Instant firstAttempt;
private final TaskFactory<V> taskFactory;

private final Task<V> currentTask;
private int attemptNumber;
private FailureDetails lastFailure;
private Duration totalRetryTime;

public RetriableTask(TaskOrchestrationContext context, TaskFactory<V> taskFactory, RetryPolicy policy) {
this(context, taskFactory, policy, null);
public RetriableTask(TaskOrchestrationContext context, Task<V> currentTask, RetryPolicy policy) {
this(context, currentTask, policy, null);
}

public RetriableTask(TaskOrchestrationContext context, TaskFactory<V> taskFactory, RetryHandler handler) {
this(context, taskFactory, null, handler);
public RetriableTask(TaskOrchestrationContext context, Task<V> currentTask, RetryHandler handler) {
this(context, currentTask, null, handler);
}

private RetriableTask(
TaskOrchestrationContext context,
TaskFactory<V> taskFactory,
Task<V> currentTask,
@Nullable RetryPolicy retryPolicy,
@Nullable RetryHandler retryHandler) {
super(new CompletableFuture<>());
// keep this future to be same as the currentTask's future, so when currentTask future complete,
// this Task will also complete. This is important to guarantee future returned by anyOf/allOf can be completed.
super(currentTask.future);
this.context = context;
this.taskFactory = taskFactory;
this.policy = retryPolicy;
this.handler = retryHandler;
this.firstAttempt = context.getCurrentInstant();
this.totalRetryTime = Duration.ZERO;
this.currentTask = currentTask;
}

@Override
public V await() {
Instant startTime = this.context.getCurrentInstant();
while (true) {
Task<V> currentTask = this.taskFactory.create();

this.attemptNumber++;

try {
return currentTask.await();
return this.currentTask.await();
} catch (TaskFailedException ex) {
this.lastFailure = ex.getErrorDetails();
if (!this.shouldRetry()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.functions;

import com.microsoft.azure.functions.annotation.*;
import com.microsoft.azure.functions.*;

import java.time.Duration;
import java.util.*;

import com.microsoft.durabletask.*;
import com.microsoft.durabletask.azurefunctions.DurableActivityTrigger;
import com.microsoft.durabletask.azurefunctions.DurableClientContext;
import com.microsoft.durabletask.azurefunctions.DurableClientInput;
import com.microsoft.durabletask.azurefunctions.DurableOrchestrationTrigger;

public class ParallelFunctions {
@FunctionName("StartParallelOrchestration")
public HttpResponseMessage startParallelOrchestration(
@HttpTrigger(name = "req", methods = {HttpMethod.GET, HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request,
@DurableClientInput(name = "durableContext") DurableClientContext durableContext,
final ExecutionContext context) {
context.getLogger().info("Java HTTP trigger processed a request.");

DurableTaskClient client = durableContext.getClient();
String instanceId = client.scheduleNewOrchestrationInstance("Parallel");
context.getLogger().info("Created new Java orchestration with instance ID = " + instanceId);
return durableContext.createCheckStatusResponse(request, instanceId);
}

@FunctionName("Parallel")
public List<String> parallelOrchestrator(
@DurableOrchestrationTrigger(name = "ctx") TaskOrchestrationContext ctx) {
RetryPolicy retryPolicy = new RetryPolicy(2, Duration.ofSeconds(5));
TaskOptions taskOptions = new TaskOptions(retryPolicy);
List<Task<String>> tasks = new ArrayList<>();
tasks.add(ctx.callActivity("Append", "Input1", taskOptions, String.class));
tasks.add(ctx.callActivity("Append", "Input2", taskOptions, String.class));
tasks.add(ctx.callActivity("Append", "Input3", taskOptions, String.class));
return ctx.allOf(tasks).await();
}

@FunctionName("Append")
public String append(
@DurableActivityTrigger(name = "name") String name,
final ExecutionContext context) {
context.getLogger().info("Append: " + name);
return name + "-test";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,16 @@ public void setupHost() {
post(hostHealthPingPath).then().statusCode(200);
}

@Test
public void basicChain() throws InterruptedException {
@ParameterizedTest
@ValueSource(strings = {
"StartOrchestration",
"StartParallelOrchestration"
})
public void generalFunctions(String functionName) throws InterruptedException {
Set<String> continueStates = new HashSet<>();
continueStates.add("Pending");
continueStates.add("Running");
String startOrchestrationPath = "/api/StartOrchestration";
String startOrchestrationPath = "/api/" + functionName;
Response response = post(startOrchestrationPath);
JsonPath jsonPath = response.jsonPath();
String statusQueryGetUri = jsonPath.get("statusQueryGetUri");
Expand Down