Skip to content

Commit 21b72f2

Browse files
committed
Support retryoptions when calling act/suborch
delete old task when scheduling retries
1 parent cb2d5ac commit 21b72f2

14 files changed

Lines changed: 1502 additions & 39 deletions

packages/durabletask-js/src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ export { OrchestrationStatus as ProtoOrchestrationStatus } from "./proto/orchest
2020
export { getName, whenAll, whenAny } from "./task";
2121
export { Task } from "./task/task";
2222

23+
// Retry policies and task options
24+
export { RetryPolicy, RetryPolicyOptions } from "./task/retry";
25+
export { TaskOptions, SubOrchestrationOptions, taskOptionsFromRetryPolicy, subOrchestrationOptionsFromRetryPolicy } from "./task/options";
26+
2327
// Types
2428
export { TOrchestrator } from "./types/orchestrator.type";
2529
export { TActivity } from "./types/activity.type";

packages/durabletask-js/src/task/context/orchestration-context.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import { TActivity } from "../../types/activity.type";
55
import { TOrchestrator } from "../../types/orchestrator.type";
6+
import { TaskOptions, SubOrchestrationOptions } from "../options";
67
import { Task } from "../task";
78

89
export abstract class OrchestrationContext {
@@ -48,27 +49,31 @@ export abstract class OrchestrationContext {
4849
/**
4950
* Schedule an activity for execution.
5051
*
51-
* @param {Orchestrator} orchestrator The sub-orchestrator function to call.
52-
* @param {TInput} input The JSON-serializable input value for the sub-orchestrator function.
53-
* @param {string} instanceId The ID to use for the sub-orchestration instance. If not provided, a new GUID will be used.
52+
* @param {TActivity} activity The activity function to call.
53+
* @param {TInput} input The JSON-serializable input value for the activity function.
54+
* @param {TaskOptions} options Optional options to control the behavior of the activity execution, including retry policies.
5455
*
55-
* @returns {Task<TOutput>} A Durable Task that completes when the sub-orchestrator function completes.
56+
* @returns {Task<TOutput>} A Durable Task that completes when the activity function completes.
5657
*/
57-
abstract callActivity<TInput, TOutput>(activity: TActivity<TInput, TOutput> | string, input?: TInput): Task<TOutput>;
58+
abstract callActivity<TInput, TOutput>(
59+
activity: TActivity<TInput, TOutput> | string,
60+
input?: TInput,
61+
options?: TaskOptions,
62+
): Task<TOutput>;
5863

5964
/**
6065
* Schedule sub-orchestrator function for execution.
6166
*
62-
* @param orchestrator A reference to the orchestrator function call
67+
* @param orchestrator A reference to the orchestrator function to call.
6368
* @param input The JSON-serializable input value for the orchestrator function.
64-
* @param instanceId A unique ID to use for the sub-orchestration instance. If not provided, a new GUID will be used.
69+
* @param options Optional options to control the behavior of the sub-orchestration, including retry policies and instance ID.
6570
*
6671
* @returns {Task<TOutput>} A Durable Task that completes when the sub-orchestrator function completes.
6772
*/
6873
abstract callSubOrchestrator<TInput, TOutput>(
6974
orchestrator: TOrchestrator | string,
7075
input?: TInput,
71-
instanceId?: string,
76+
options?: SubOrchestrationOptions,
7277
): Task<TOutput>;
7378

7479
/**
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
export {
5+
TaskOptions,
6+
SubOrchestrationOptions,
7+
taskOptionsFromRetryPolicy,
8+
subOrchestrationOptionsFromRetryPolicy,
9+
} from "./task-options";
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import { RetryPolicy } from "../retry/retry-policy";
5+
6+
/**
7+
* Options that can be used to control the behavior of orchestrator task execution.
8+
*/
9+
export interface TaskOptions {
10+
/**
11+
* The retry policy for the task.
12+
* Controls how many times a task is retried and the delay between retries.
13+
*/
14+
retry?: RetryPolicy;
15+
}
16+
17+
/**
18+
* Options that can be used to control the behavior of sub-orchestrator execution.
19+
* Extends TaskOptions with additional options specific to sub-orchestrations.
20+
*/
21+
export interface SubOrchestrationOptions extends TaskOptions {
22+
/**
23+
* The unique ID to use for the sub-orchestration instance.
24+
* If not specified, a deterministic ID will be generated based on the parent instance ID.
25+
*/
26+
instanceId?: string;
27+
}
28+
29+
/**
30+
* Creates a TaskOptions instance from a RetryPolicy.
31+
*
32+
* @param policy - The retry policy to use
33+
* @returns A TaskOptions instance configured with the retry policy
34+
*
35+
* @example
36+
* ```typescript
37+
* const retryPolicy = new RetryPolicy({
38+
* maxNumberOfAttempts: 3,
39+
* firstRetryIntervalInMilliseconds: 1000
40+
* });
41+
*
42+
* const options = taskOptionsFromRetryPolicy(retryPolicy);
43+
* ```
44+
*/
45+
export function taskOptionsFromRetryPolicy(policy: RetryPolicy): TaskOptions {
46+
return { retry: policy };
47+
}
48+
49+
/**
50+
* Creates a SubOrchestrationOptions instance from a RetryPolicy and optional instance ID.
51+
*
52+
* @param policy - The retry policy to use
53+
* @param instanceId - Optional instance ID for the sub-orchestration
54+
* @returns A SubOrchestrationOptions instance configured with the retry policy
55+
*
56+
* @example
57+
* ```typescript
58+
* const retryPolicy = new RetryPolicy({
59+
* maxNumberOfAttempts: 3,
60+
* firstRetryIntervalInMilliseconds: 1000
61+
* });
62+
*
63+
* const options = subOrchestrationOptionsFromRetryPolicy(retryPolicy, "my-sub-orch-123");
64+
* ```
65+
*/
66+
export function subOrchestrationOptionsFromRetryPolicy(
67+
policy: RetryPolicy,
68+
instanceId?: string,
69+
): SubOrchestrationOptions {
70+
return { retry: policy, instanceId };
71+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import { CompletableTask } from "./completable-task";
5+
import { RetryableTask } from "./retryable-task";
6+
7+
/**
8+
* A timer task that is associated with a retryable task for retry purposes.
9+
*
10+
* When this timer fires, the retryable task should be rescheduled for another attempt.
11+
*/
12+
export class RetryTimerTask<T> extends CompletableTask<T> {
13+
private readonly _retryableParent: RetryableTask<any>;
14+
15+
/**
16+
* Creates a new RetryTimerTask.
17+
*
18+
* @param retryableParent - The retryable task that this timer is associated with
19+
*/
20+
constructor(retryableParent: RetryableTask<any>) {
21+
super();
22+
this._retryableParent = retryableParent;
23+
}
24+
25+
/**
26+
* Gets the retryable task that this timer is associated with.
27+
*/
28+
get retryableParent(): RetryableTask<any> {
29+
return this._retryableParent;
30+
}
31+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
export { RetryPolicy, RetryPolicyOptions } from "./retry-policy";
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
/**
5+
* A declarative retry policy that can be configured for activity or sub-orchestration calls.
6+
*
7+
* @remarks
8+
* Retry policies control how many times a task is retried and the delay between retries.
9+
* The delay between retries increases exponentially based on the backoffCoefficient.
10+
*
11+
* @example
12+
* ```typescript
13+
* const retryPolicy = new RetryPolicy({
14+
* maxNumberOfAttempts: 5,
15+
* firstRetryIntervalInMilliseconds: 1000,
16+
* backoffCoefficient: 2.0,
17+
* maxRetryIntervalInMilliseconds: 30000,
18+
* retryTimeoutInMilliseconds: 300000
19+
* });
20+
* ```
21+
*/
22+
export class RetryPolicy {
23+
private readonly _maxNumberOfAttempts: number;
24+
private readonly _firstRetryIntervalInMilliseconds: number;
25+
private readonly _backoffCoefficient: number;
26+
private readonly _maxRetryIntervalInMilliseconds: number;
27+
private readonly _retryTimeoutInMilliseconds: number;
28+
29+
/**
30+
* Creates a new RetryPolicy instance.
31+
*
32+
* @param options - The retry policy options
33+
* @throws Error if any of the validation constraints are violated
34+
*/
35+
constructor(options: RetryPolicyOptions) {
36+
const {
37+
maxNumberOfAttempts,
38+
firstRetryIntervalInMilliseconds,
39+
backoffCoefficient = 1.0,
40+
maxRetryIntervalInMilliseconds,
41+
retryTimeoutInMilliseconds,
42+
} = options;
43+
44+
// Validation aligned with .NET SDK
45+
if (maxNumberOfAttempts <= 0) {
46+
throw new Error("maxNumberOfAttempts must be greater than zero");
47+
}
48+
49+
if (firstRetryIntervalInMilliseconds <= 0) {
50+
throw new Error("firstRetryIntervalInMilliseconds must be greater than zero");
51+
}
52+
53+
if (backoffCoefficient < 1.0) {
54+
throw new Error("backoffCoefficient must be greater than or equal to 1.0");
55+
}
56+
57+
if (
58+
maxRetryIntervalInMilliseconds !== undefined &&
59+
maxRetryIntervalInMilliseconds !== -1 &&
60+
maxRetryIntervalInMilliseconds < firstRetryIntervalInMilliseconds
61+
) {
62+
throw new Error("maxRetryIntervalInMilliseconds must be greater than or equal to firstRetryIntervalInMilliseconds");
63+
}
64+
65+
if (
66+
retryTimeoutInMilliseconds !== undefined &&
67+
retryTimeoutInMilliseconds !== -1 &&
68+
retryTimeoutInMilliseconds < firstRetryIntervalInMilliseconds
69+
) {
70+
throw new Error("retryTimeoutInMilliseconds must be greater than or equal to firstRetryIntervalInMilliseconds");
71+
}
72+
73+
this._maxNumberOfAttempts = maxNumberOfAttempts;
74+
this._firstRetryIntervalInMilliseconds = firstRetryIntervalInMilliseconds;
75+
this._backoffCoefficient = backoffCoefficient;
76+
// Default to 1 hour (3600000ms) if not specified, -1 means infinite
77+
this._maxRetryIntervalInMilliseconds = maxRetryIntervalInMilliseconds ?? 3600000;
78+
// Default to -1 (infinite) if not specified
79+
this._retryTimeoutInMilliseconds = retryTimeoutInMilliseconds ?? -1;
80+
}
81+
82+
/**
83+
* Gets the max number of attempts for executing a given task.
84+
*/
85+
get maxNumberOfAttempts(): number {
86+
return this._maxNumberOfAttempts;
87+
}
88+
89+
/**
90+
* Gets the amount of time in milliseconds to delay between the first and second attempt.
91+
*/
92+
get firstRetryIntervalInMilliseconds(): number {
93+
return this._firstRetryIntervalInMilliseconds;
94+
}
95+
96+
/**
97+
* Gets the exponential back-off coefficient used to determine the delay between subsequent retries.
98+
* @remarks Defaults to 1.0 for no back-off.
99+
*/
100+
get backoffCoefficient(): number {
101+
return this._backoffCoefficient;
102+
}
103+
104+
/**
105+
* Gets the maximum time in milliseconds to delay between attempts.
106+
* @remarks Defaults to 1 hour (3600000ms). Use -1 for infinite.
107+
*/
108+
get maxRetryIntervalInMilliseconds(): number {
109+
return this._maxRetryIntervalInMilliseconds;
110+
}
111+
112+
/**
113+
* Gets the overall timeout for retries in milliseconds.
114+
* No further attempts will be made after this timeout expires.
115+
* @remarks Defaults to -1 (infinite).
116+
*/
117+
get retryTimeoutInMilliseconds(): number {
118+
return this._retryTimeoutInMilliseconds;
119+
}
120+
}
121+
122+
/**
123+
* Options for creating a RetryPolicy.
124+
*/
125+
export interface RetryPolicyOptions {
126+
/**
127+
* The maximum number of task invocation attempts. Must be 1 or greater.
128+
*/
129+
maxNumberOfAttempts: number;
130+
131+
/**
132+
* The amount of time in milliseconds to delay between the first and second attempt.
133+
* Must be greater than 0.
134+
*/
135+
firstRetryIntervalInMilliseconds: number;
136+
137+
/**
138+
* The exponential back-off coefficient used to determine the delay between subsequent retries.
139+
* Must be 1.0 or greater.
140+
* @default 1.0
141+
*/
142+
backoffCoefficient?: number;
143+
144+
/**
145+
* The maximum time in milliseconds to delay between attempts.
146+
* Must be greater than or equal to firstRetryIntervalInMilliseconds.
147+
* Use -1 for infinite (no maximum).
148+
* @default 3600000 (1 hour)
149+
*/
150+
maxRetryIntervalInMilliseconds?: number;
151+
152+
/**
153+
* The overall timeout for retries in milliseconds.
154+
* No further attempts will be made after this timeout expires.
155+
* Use -1 for infinite (no timeout).
156+
* @default -1 (infinite)
157+
*/
158+
retryTimeoutInMilliseconds?: number;
159+
}

0 commit comments

Comments
 (0)