跳到主要内容

StepOptions

查看 Markdown

A Step type returns StepOptions to customize its methods. WaitFor and Execute have separate retry policies, timeouts, failure policies, state selections, and lock sets.

Retry policies, failure policies, and RetryAfter are on Failure and retries.

Override StepOptions per Step execution​

The StepOptions that a Step type returns apply to all of its Step executions. To override them for one Step execution, pass StepOptions in the Step movement that starts it.

Java
final StepOptions options = StepOptions.newBuilder()
.waitForRetry(RetryPolicy.newBuilder().maximumAttempts(2).build())
.waitForFailure(WaitForFailurePolicy.PROCEED)
.build();
return StepDecision.goToMany(StepMovement.of(OverrideSecond.class, output, options));

Example: examples/java/src/main/java/io/superdurable/dex/primitives/optionsoverride/OptionsOverrideFlow.java

Durability​

With SYNC durability, Dex Server stores each attempt start before the Worker receives the task. It also stores the Step completion before it goes on.

With ASYNC durability, Dex Server hands the task to the Worker, and starts the next work, before the start and the completion are stored. It batches these writes over about 20 milliseconds. Both modes reach about the same sustained server throughput. ASYNC has lower latency, so a single Flow runs faster. On object storage with low write latency, ASYNC also makes far fewer requests.

If a Dex Server node fails before an ASYNC result is stored, the result is lost. The method runs again, and so does any later work that started from that result. If the node fails before an ASYNC start is stored, Dex runs the same attempt number again without using a retry. See An attempt starts when Dex hands it to a Worker. Make ASYNC methods, and the work they start, idempotent.

Choose SYNC for effects that need the tightest guarantees. Choose ASYNC for short Steps where latency matters and a rare rerun is acceptable. See Failure and redelivery for the full comparison.

SYNC is the default. To set the durability for a whole Flow, see StepDurability in FlowConfig.

Retry policy, method timeout, and heartbeat settings apply to every attempt in both modes.

Java
@Override
public StepOptions getStepOptions() {
return StepOptions.newBuilder()
.executeDurability(StepDurability.SYNC)
.build();
}

Example: examples/java/src/main/java/io/superdurable/dex/primitives/durability/DurabilityFlow.java

Timeouts and total duration​

Three options bound the time a Step method can take.

LimitOptionDefaultEnforcement
Longest single attemptWaitForMethodTimeout or ExecuteMethodTimeoutTwo hours. The server has no hard cap.Dex times out the attempt. Another attempt starts only while TotalDuration has time left.
Time budget for retriesTotalDuration in the retry policyFour hours, for both durability modes.After an attempt fails, Dex retries only while the budget has time left. It never stops an attempt in progress.
Stalled WorkerHeartbeatTimeoutOne minute. The default allowed minimum is 10 seconds.Dex times out an attempt that sends no heartbeat or Stream message within this window. The retry policy then starts another attempt. See Heartbeat.

The method timeout and the heartbeat timeout start when Dex sends the task to the Worker. Waiting for locks or for a Worker, including time in the task queue, does not count. The deadline in the task is the send time plus the method timeout.

TotalDuration starts when Dex first hands attempt 1 to a Worker. If attempt 1 returns to pending without running, the clock restarts. Each retry waits in the task queue again, and that wait counts toward TotalDuration. Dex dispatches queued tasks roughly in the order they became ready, with no strict order. Dex does not check TotalDuration while a retry waits in the queue, so a retry that waited past it still runs. After a long backlog, a failed retry may have no time left for another.

Dex checks TotalDuration only after an attempt fails, to decide whether to retry. It never stops a running attempt, so the last attempt can start or end after TotalDuration. Only the method timeout limits a single attempt: set ExecuteMethodTimeout or WaitForMethodTimeout, not TotalDuration.

To bound a long wait, set the method timeout and TotalDuration to the same maximum. Dex enforces both, so the Step needs no deadline of its own. While Workers are available, the total time is at most about TotalDuration plus one method timeout. Allow a few attempts with backoff in the retry policy, so the Step recovers from transient errors and Worker crashes.

When a timeout expires, Dex fails the attempt with a timeout error. After retries end, the failure policy decides what happens next. If no failure route is configured, the Flow fails. See Execute Failure Recovery. For a long wait on an external system, see Polling.

Heartbeat​

During an attempt, the method must send a heartbeat or a Stream message within each HeartbeatTimeout. A Stream message counts as a heartbeat.

For example, with an Execute method timeout of 60 seconds and a HeartbeatTimeout of 10 seconds, Dex retries a stalled attempt after about 10 seconds instead of 60. To wait for an external system in one heartbeating Step, use the Polling pattern.

A heartbeat can carry a checkpoint value. The next attempt receives the latest stored checkpoint and can resume from it. A heartbeat without a value clears the checkpoint. A Stream message leaves the checkpoint as the last heartbeat set it, including a cleared one. Dex can lose checkpoints; see Heartbeats are not acknowledged.

Java
@Override
public StepDecision execute(final Context context, final Integer batches) {
final int completedBatches = context.hasLastHeartbeatValue()
? context.getLastHeartbeatValue(Integer.class)
: 0;
for (int batch = completedBatches; batch < batches; batch++) {
if (context.isCancellationRequested()) {
return StepDecision.deadEnd();
}
try {
Thread.sleep(Duration.ofSeconds(2).toMillis());
} catch (InterruptedException interrupted) {
Thread.currentThread().interrupt();
return StepDecision.deadEnd();
}
context.recordHeartbeat(batch + 1);
}
return StepDecision.gracefulComplete("processed");
}

@Override
public StepOptions getStepOptions() {
return StepOptions.newBuilder()
.executeMethodTimeout(Duration.ofSeconds(60))
.heartbeatTimeout(Duration.ofSeconds(10))
.executeRetry(RetryPolicy.newBuilder().maximumAttempts(3).build())
.build();
}

Example: examples/java/src/main/java/io/superdurable/dex/primitives/stepheartbeat/StepHeartbeatFlow.java

Cancellation​

Dex sends a cancel request to the Worker when a Step is canceled, for example by a StepDecision or an RPC, and when an attempt times out. The SDK also cancels the method when the attempt deadline in the task passes. The method receives the cancellation without having to return, fail, or send a heartbeat.

The SDK cannot stop running code. A long-running method must check for cancellation and return, as the heartbeat example above does. See Make the Step stop for the check in each language, and Cancel running Steps for how Dex ends a canceled attempt.

Because the SDK cannot stop running code, a canceled attempt may still be running when the next attempt or the failure route starts.

Selective state loading​

Every WaitFor, Execute, and timeout-handler call receives all ordinary Attributes and size metadata for every Channel and ChannelMap instance. AttributeMap values and pending Channel messages are not loaded unless that method selects them.

StepOptions has separate selections for WaitFor and Execute. Select a whole AttributeMap or ChannelMap when the method must go through its current instances. Select one map instance when you already know its key. Locking an Attribute does not load an AttributeMap value.

WaitFor and Execute each get their own snapshot. Dex takes the Execute snapshot after the Wait consumed its messages, so Execute does not see those messages again. Dex takes a method's snapshot when it hands attempt 1 to a Worker. Retries of that method call, and attempts that return to pending, reuse the same snapshot.

This Step loads the pending messages of the Queued Channel for Execute only. Execute deletes the first pending message, and Dex applies the deletion when the method succeeds.

Java
@Override
public StepOptions getStepOptions() {
return StepOptions.newBuilder()
.addExecuteLoadChannel(queued)
.build();
}

Example: examples/java/src/main/java/io/superdurable/dex/primitives/channel/ChannelFlow.java

Reading an AttributeMap value or pending Channel message that the method did not select throws the SDK's not-loaded error. A selected empty Channel returns an empty message list. Sizes and map keys are available without loading. For Go Flows, dexcli visualize reports a Step method that reads state it did not select. See State loads.

Attribute locks​

Steps and RPCs that run at the same time can read and write the same Attribute. To prevent races, have each of them lock that Attribute. Dex Server then lets only one Step method or RPC hold the lock at a time. Set the locks in StepOptions. WaitFor and Execute have separate lock sets.

See Attribute locks for when a Step attempt takes and holds its locks, with WaitFor and Execute examples. See Locks for the lock model.