55import java .time .Duration ;
66import java .util .Objects ;
77import software .amazon .lambda .durable .DurableContext ;
8+ import software .amazon .lambda .durable .DurableFuture ;
89import software .amazon .lambda .durable .TypeToken ;
910import software .amazon .lambda .durable .config .WithRetryConfig ;
1011import software .amazon .lambda .durable .exception .UnrecoverableDurableExecutionException ;
5253 * .build()
5354 * );
5455 * }</pre>
56+ *
57+ * <h2>Usage — async form returning DurableFuture</h2>
58+ *
59+ * <pre>{@code
60+ * DurableFuture<String> future = WithRetryHelper.withRetryAsync(
61+ * context,
62+ * "approval",
63+ * (ctx, attempt) -> ctx.waitForCallback(
64+ * "approval-" + attempt, String.class,
65+ * (callbackId, stepCtx) -> sendApprovalEmail(approverEmail, callbackId)
66+ * ),
67+ * WithRetryConfig.builder()
68+ * .retryStrategy(RetryStrategies.fixedDelay(3, Duration.ofSeconds(2)))
69+ * .build()
70+ * );
71+ * // ... do other work ...
72+ * var result = future.get();
73+ * }</pre>
5574 */
5675public final class WithRetryHelper {
5776
@@ -64,48 +83,88 @@ private WithRetryHelper() {
6483 }
6584
6685 /**
67- * Named form — wraps the retry loop in {@code runInChildContext} by default so all attempts are grouped under a
68- * single named operation in execution history.
86+ * Named async form — wraps the retry loop in {@code runInChildContextAsync} by default so all attempts are grouped
87+ * under a single named operation in execution history, and returns a {@link DurableFuture} that can be composed or
88+ * blocked on.
6989 *
7090 * <p>The child-context wrapping can be disabled via {@link WithRetryConfig.Builder#wrapInChildContext(boolean)}.
91+ * When disabled, the retry loop executes immediately and the returned future is already completed.
7192 *
7293 * @param <T> the result type
7394 * @param context the durable context
7495 * @param name operation name (used for child context and backoff wait names)
7596 * @param operation the retryable operation — receives the context and 1-based attempt number
7697 * @param config retry configuration including the retry strategy
77- * @return the operation result
98+ * @return a future representing the operation result
7899 */
79100 @ SuppressWarnings ("unchecked" )
80- public static <T > T withRetry (DurableContext context , String name , WithRetry <T > operation , WithRetryConfig config ) {
101+ public static <T > DurableFuture <T > withRetryAsync (
102+ DurableContext context , String name , WithRetry <T > operation , WithRetryConfig config ) {
81103 Objects .requireNonNull (context , "context cannot be null" );
82104 Objects .requireNonNull (name , "name cannot be null" );
83105 Objects .requireNonNull (operation , "operation cannot be null" );
84106 Objects .requireNonNull (config , "config cannot be null" );
85107
86108 if (config .wrapInChildContext ()) {
87- return (T ) context .runInChildContext (
109+ return (DurableFuture < T > ) context .runInChildContextAsync (
88110 name , new TypeToken <Object >() {}, childCtx -> executeRetryLoop (childCtx , name , operation , config ));
89111 }
90- return executeRetryLoop (context , name , operation , config );
112+ return new CompletedDurableFuture <>( executeRetryLoop (context , name , operation , config ) );
91113 }
92114
93115 /**
94- * Anonymous form — runs the retry loop directly in the caller's context. No child-context wrapping is applied
95- * regardless of the {@code wrapInChildContext} config setting.
116+ * Named sync form — wraps the retry loop in {@code runInChildContext} by default so all attempts are grouped under
117+ * a single named operation in execution history, and blocks until the result is available.
118+ *
119+ * <p>Equivalent to {@code withRetryAsync(context, name, operation, config).get()}.
96120 *
97121 * @param <T> the result type
98122 * @param context the durable context
123+ * @param name operation name (used for child context and backoff wait names)
99124 * @param operation the retryable operation — receives the context and 1-based attempt number
100125 * @param config retry configuration including the retry strategy
101126 * @return the operation result
102127 */
103- public static <T > T withRetry (DurableContext context , WithRetry <T > operation , WithRetryConfig config ) {
128+ public static <T > T withRetry (DurableContext context , String name , WithRetry <T > operation , WithRetryConfig config ) {
129+ return withRetryAsync (context , name , operation , config ).get ();
130+ }
131+
132+ /**
133+ * Anonymous async form — runs the retry loop directly in the caller's context and returns a {@link DurableFuture}.
134+ * No child-context wrapping is applied regardless of the {@code wrapInChildContext} config setting.
135+ *
136+ * <p>Because the anonymous form executes the retry loop inline (no child context), the returned future is always
137+ * already completed.
138+ *
139+ * @param <T> the result type
140+ * @param context the durable context
141+ * @param operation the retryable operation — receives the context and 1-based attempt number
142+ * @param config retry configuration including the retry strategy
143+ * @return a future representing the operation result
144+ */
145+ public static <T > DurableFuture <T > withRetryAsync (
146+ DurableContext context , WithRetry <T > operation , WithRetryConfig config ) {
104147 Objects .requireNonNull (context , "context cannot be null" );
105148 Objects .requireNonNull (operation , "operation cannot be null" );
106149 Objects .requireNonNull (config , "config cannot be null" );
107150
108- return executeRetryLoop (context , null , operation , config );
151+ return new CompletedDurableFuture <>(executeRetryLoop (context , null , operation , config ));
152+ }
153+
154+ /**
155+ * Anonymous sync form — runs the retry loop directly in the caller's context. No child-context wrapping is applied
156+ * regardless of the {@code wrapInChildContext} config setting.
157+ *
158+ * <p>Equivalent to {@code withRetryAsync(context, operation, config).get()}.
159+ *
160+ * @param <T> the result type
161+ * @param context the durable context
162+ * @param operation the retryable operation — receives the context and 1-based attempt number
163+ * @param config retry configuration including the retry strategy
164+ * @return the operation result
165+ */
166+ public static <T > T withRetry (DurableContext context , WithRetry <T > operation , WithRetryConfig config ) {
167+ return withRetryAsync (context , operation , config ).get ();
109168 }
110169
111170 /**
0 commit comments