Skip to content

Commit fadce47

Browse files
committed
chore: use virtual context when wrapInChildContext is false
1 parent 8357d38 commit fadce47

11 files changed

Lines changed: 181 additions & 972 deletions

File tree

sdk/src/main/java/software/amazon/lambda/durable/DurableContext.java

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -740,9 +740,10 @@ <T> DurableFuture<T> waitForConditionAsync(
740740
* <p>Every side-effect in the loop is a durable operation, so the loop is replay-safe by construction. On replay,
741741
* completed operations return cached results instantly and the loop fast-forwards to the current attempt.
742742
*
743-
* <p>By default, the retry loop runs directly on the caller's context. If
744-
* {@link WithRetryConfig#wrapInChildContext()} is enabled, the loop is wrapped in a child context so all attempts
745-
* are grouped under a single named operation in execution history.
743+
* <p>The retry loop always runs in a child context to provide an isolated operation ID namespace. If
744+
* {@link WithRetryConfig#wrapInChildContext()} is enabled, the child context is checkpointed (persisted) so all
745+
* attempts are grouped under a single named operation in execution history. Otherwise, a virtual child context is
746+
* used — no checkpointing overhead, but the child re-executes on replay.
746747
*
747748
* @param <T> the result type
748749
* @param name operation name (used for backoff wait names, and as the child context name when wrapping)
@@ -755,9 +756,10 @@ <T> DurableFuture<T> waitForConditionAsync(
755756
/**
756757
* Replay-safe retry loop for any durable operation (anonymous form, sync).
757758
*
758-
* <p>By default, the retry loop runs directly on the caller's context. If
759-
* {@link WithRetryConfig#wrapInChildContext()} is enabled, the loop is wrapped in a child context with a default
760-
* name so all attempts are grouped under a single operation in execution history.
759+
* <p>The retry loop always runs in a child context to provide an isolated operation ID namespace. If
760+
* {@link WithRetryConfig#wrapInChildContext()} is enabled, the child context is checkpointed (persisted) so all
761+
* attempts are grouped under a single named operation in execution history. Otherwise, a virtual child context is
762+
* used — no checkpointing overhead, but the child re-executes on replay.
761763
*
762764
* @param <T> the result type
763765
* @param operation the retryable operation — receives the context and 1-based attempt number
@@ -769,8 +771,10 @@ <T> DurableFuture<T> waitForConditionAsync(
769771
/**
770772
* Replay-safe retry loop for any durable operation (named form, async).
771773
*
772-
* <p>Wraps the retry loop in {@code runInChildContextAsync} so all attempts are grouped under a single named
773-
* operation in execution history, and returns a {@link DurableFuture} that can be composed or blocked on.
774+
* <p>The retry loop always runs in a child context to provide an isolated operation ID namespace. If
775+
* {@link WithRetryConfig#wrapInChildContext()} is enabled, the child context is checkpointed (persisted) so all
776+
* attempts are grouped under a single named operation in execution history. Otherwise, a virtual child context is
777+
* used — no checkpointing overhead, but the child re-executes on replay.
774778
*
775779
* @param <T> the result type
776780
* @param name operation name (used for child context and backoff wait names)
@@ -783,8 +787,10 @@ <T> DurableFuture<T> waitForConditionAsync(
783787
/**
784788
* Replay-safe retry loop for any durable operation (anonymous form, async).
785789
*
786-
* <p>Wraps the retry loop in {@code runInChildContextAsync} with a default name so all attempts are grouped under a
787-
* single operation in execution history, and returns a {@link DurableFuture} that can be composed or blocked on.
790+
* <p>The retry loop always runs in a child context to provide an isolated operation ID namespace. If
791+
* {@link WithRetryConfig#wrapInChildContext()} is enabled, the child context is checkpointed (persisted) so all
792+
* attempts are grouped under a single named operation in execution history. Otherwise, a virtual child context is
793+
* used — no checkpointing overhead, but the child re-executes on replay.
788794
*
789795
* @param <T> the result type
790796
* @param operation the retryable operation — receives the context and 1-based attempt number

sdk/src/main/java/software/amazon/lambda/durable/config/RetryOperationConfig.java

Lines changed: 0 additions & 103 deletions
This file was deleted.

sdk/src/main/java/software/amazon/lambda/durable/context/DurableContextImpl.java

Lines changed: 76 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,25 @@ private <T> DurableFuture<T> runInChildContextAsync(
238238
Function<DurableContext, T> func,
239239
RunInChildContextConfig config,
240240
OperationSubType subType) {
241+
return runInChildContextAsync(name, resultType, func, config, subType, false);
242+
}
243+
244+
private <T> DurableFuture<T> runInVirtualChildContextAsync(
245+
String name,
246+
TypeToken<T> resultType,
247+
Function<DurableContext, T> func,
248+
RunInChildContextConfig config,
249+
OperationSubType subType) {
250+
return runInChildContextAsync(name, resultType, func, config, subType, true);
251+
}
252+
253+
private <T> DurableFuture<T> runInChildContextAsync(
254+
String name,
255+
TypeToken<T> resultType,
256+
Function<DurableContext, T> func,
257+
RunInChildContextConfig config,
258+
OperationSubType subType,
259+
boolean isVirtual) {
241260
Objects.requireNonNull(resultType, "resultType cannot be null");
242261
Objects.requireNonNull(config, "RunInChildContextConfig cannot be null");
243262
ParameterValidator.validateOperationName(name);
@@ -253,7 +272,9 @@ private <T> DurableFuture<T> runInChildContextAsync(
253272
func,
254273
resultType,
255274
config,
256-
this);
275+
this,
276+
isVirtual,
277+
null);
257278

258279
operation.execute();
259280
return operation;
@@ -386,10 +407,21 @@ public <T> T withRetry(String name, WithRetry<T> operation, WithRetryConfig conf
386407
Objects.requireNonNull(config, "config cannot be null");
387408

388409
if (config.wrapInChildContext()) {
389-
return (T) runInChildContext(
390-
name, new TypeToken<Object>() {}, childCtx -> executeRetryLoop(childCtx, name, operation, config));
410+
return (T) runInChildContextAsync(
411+
name,
412+
new TypeToken<Object>() {},
413+
childCtx -> executeRetryLoop(childCtx, name, operation, config),
414+
RunInChildContextConfig.builder().build(),
415+
OperationSubType.WITH_RETRY)
416+
.get();
391417
}
392-
return executeRetryLoop(this, name, operation, config);
418+
return (T) runInVirtualChildContextAsync(
419+
name,
420+
new TypeToken<Object>() {},
421+
childCtx -> executeRetryLoop(childCtx, name, operation, config),
422+
RunInChildContextConfig.builder().build(),
423+
OperationSubType.WITH_RETRY)
424+
.get();
393425
}
394426

395427
@Override
@@ -399,12 +431,21 @@ public <T> T withRetry(WithRetry<T> operation, WithRetryConfig config) {
399431
Objects.requireNonNull(config, "config cannot be null");
400432

401433
if (config.wrapInChildContext()) {
402-
return (T) runInChildContext(
403-
ANONYMOUS_CHILD_CONTEXT_NAME,
404-
new TypeToken<Object>() {},
405-
childCtx -> executeRetryLoop(childCtx, null, operation, config));
434+
return (T) runInChildContextAsync(
435+
ANONYMOUS_CHILD_CONTEXT_NAME,
436+
new TypeToken<Object>() {},
437+
childCtx -> executeRetryLoop(childCtx, null, operation, config),
438+
RunInChildContextConfig.builder().build(),
439+
OperationSubType.WITH_RETRY)
440+
.get();
406441
}
407-
return executeRetryLoop(this, null, operation, config);
442+
return (T) runInVirtualChildContextAsync(
443+
ANONYMOUS_CHILD_CONTEXT_NAME,
444+
new TypeToken<Object>() {},
445+
childCtx -> executeRetryLoop(childCtx, null, operation, config),
446+
RunInChildContextConfig.builder().build(),
447+
OperationSubType.WITH_RETRY)
448+
.get();
408449
}
409450

410451
@Override
@@ -414,8 +455,20 @@ public <T> DurableFuture<T> withRetryAsync(String name, WithRetry<T> operation,
414455
Objects.requireNonNull(operation, "operation cannot be null");
415456
Objects.requireNonNull(config, "config cannot be null");
416457

417-
return (DurableFuture<T>) runInChildContextAsync(
418-
name, new TypeToken<Object>() {}, childCtx -> executeRetryLoop(childCtx, name, operation, config));
458+
if (config.wrapInChildContext()) {
459+
return (DurableFuture<T>) runInChildContextAsync(
460+
name,
461+
new TypeToken<Object>() {},
462+
childCtx -> executeRetryLoop(childCtx, name, operation, config),
463+
RunInChildContextConfig.builder().build(),
464+
OperationSubType.WITH_RETRY);
465+
}
466+
return (DurableFuture<T>) runInVirtualChildContextAsync(
467+
name,
468+
new TypeToken<Object>() {},
469+
childCtx -> executeRetryLoop(childCtx, name, operation, config),
470+
RunInChildContextConfig.builder().build(),
471+
OperationSubType.WITH_RETRY);
419472
}
420473

421474
@Override
@@ -424,10 +477,20 @@ public <T> DurableFuture<T> withRetryAsync(WithRetry<T> operation, WithRetryConf
424477
Objects.requireNonNull(operation, "operation cannot be null");
425478
Objects.requireNonNull(config, "config cannot be null");
426479

427-
return (DurableFuture<T>) runInChildContextAsync(
480+
if (config.wrapInChildContext()) {
481+
return (DurableFuture<T>) runInChildContextAsync(
482+
ANONYMOUS_CHILD_CONTEXT_NAME,
483+
new TypeToken<Object>() {},
484+
childCtx -> executeRetryLoop(childCtx, null, operation, config),
485+
RunInChildContextConfig.builder().build(),
486+
OperationSubType.WITH_RETRY);
487+
}
488+
return (DurableFuture<T>) runInVirtualChildContextAsync(
428489
ANONYMOUS_CHILD_CONTEXT_NAME,
429490
new TypeToken<Object>() {},
430-
childCtx -> executeRetryLoop(childCtx, null, operation, config));
491+
childCtx -> executeRetryLoop(childCtx, null, operation, config),
492+
RunInChildContextConfig.builder().build(),
493+
OperationSubType.WITH_RETRY);
431494
}
432495

433496
/**

sdk/src/main/java/software/amazon/lambda/durable/model/OperationSubType.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ public enum OperationSubType {
1515
PARALLEL("Parallel"),
1616
PARALLEL_BRANCH("ParallelBranch"),
1717
WAIT_FOR_CALLBACK("WaitForCallback"),
18-
WAIT_FOR_CONDITION("WaitForCondition");
18+
WAIT_FOR_CONDITION("WaitForCondition"),
19+
WITH_RETRY("WithRetry");
1920

2021
private final String value;
2122

sdk/src/main/java/software/amazon/lambda/durable/operation/ChildContextOperation.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ private Throwable translateException(Operation op, ErrorObject errorObject) {
240240
case WAIT_FOR_CALLBACK -> handleWaitForCallbackFailure();
241241
case MAP_ITERATION -> new MapIterationFailedException(op);
242242
case PARALLEL_BRANCH -> new ParallelBranchFailedException(op);
243-
case RUN_IN_CHILD_CONTEXT -> new ChildContextFailedException(op);
243+
case RUN_IN_CHILD_CONTEXT, WITH_RETRY -> new ChildContextFailedException(op);
244244

245245
// the following subtypes should not be able to reach here
246246
case PARALLEL, MAP, WAIT_FOR_CONDITION -> new IllegalStateException("Unexpected sub-type: " + getSubType());

0 commit comments

Comments
 (0)