ADR-001: Threaded Handler Execution
March 6, 2026 ยท View on GitHub
Status: Accepted
Date: 2025-12-29
Context
Durable functions need to suspend execution immediately at suspension points (wait, callback, retry). The system must return PENDING status to Lambda without waiting for the handler to complete.
public String handleRequest(MyInput input, DurableContext context) {
var result1 = context.step("step1", stepCtx -> "first");
context.wait(null, Duration.ofHours(1)); // Should suspend HERE
var result2 = context.step("step2", stepCtx -> "second"); // Don't wait for this
return result1 + result2;
}
Decision
Run the handler in a background thread and race two futures:
var handlerFuture = CompletableFuture.supplyAsync(stepCtx -> handler.apply(input, context), executor);
var suspendFuture = executionManager.getSuspendExecutionFuture();
CompletableFuture.anyOf(handlerFuture, suspendFuture).join();
if (suspendFuture.isDone()) {
return DurableExecutionOutput.pending();
}
Alternatives Considered
Exception-Based Control Flow
try {
O result = handler.apply(input, context);
return DurableExecutionOutput.success(result);
} catch (SuspendExecutionException e) {
return DurableExecutionOutput.pending();
}
Rejected because:
- Users can catch and suppress the exception
- Requires two-level exception handling (operation wants suspend vs. ExecutionManager confirms suspend)
- Exceptions for control flow is an anti-pattern
Consequences
Positive:
- Immediate suspension without waiting for handler completion
- Clean separation: suspension decision is in ExecutionManager, not scattered in operations
- Users cannot accidentally suppress suspension
Negative:
- More complex threading model
- Requires thread tracking in ExecutionManager