Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
09573ca
feature(extstore): integrate into workflow worker pipeline, including…
cconstable Aug 18, 2026
55b9f7b
feat(extstore): make sure sticky cache miss path also retrieves exter…
cconstable Aug 24, 2026
03c16cd
refactor(extstore): refactor the way we derive storage targets by usi…
cconstable Aug 24, 2026
0a91150
Explicitly pass cancellation tokens for external storage methods.
cconstable Aug 27, 2026
4a7ae5e
more cancellation token threading
cconstable Aug 27, 2026
8062db7
externalStorage -> externalStorageRunner
cconstable Aug 28, 2026
7be2124
fix(extstore): return workflow task failures properly when extstore f…
cconstable Aug 31, 2026
7ccf9a0
fix(extstore): don't fail tasks when extstore fails more than once.
cconstable Sep 1, 2026
7d02fc0
fix(extstore): add error for worker shutdown and handle extstore canc…
cconstable Sep 1, 2026
268207f
fix(extstore): correctly target SCHEDULE_ACTIVITY_TASK_COMMAND_ATTRIB…
cconstable Sep 1, 2026
f0f193f
fix(extstore): COMPLETE_WORKFLOW_EXECUTION_COMMAND_ATTRIBUTES should …
cconstable Sep 1, 2026
4f2550e
fix(extstore): run extstore inside the try of replay handler.
cconstable Sep 1, 2026
2f3b425
fix replay tests
cconstable Sep 1, 2026
7ffd079
add more tests
cconstable Sep 1, 2026
5a7e291
consolidate test storage drivers.
cconstable Sep 1, 2026
c29f141
if the worker is shutting down and extstore retrieve is canceled, don…
cconstable Sep 1, 2026
62ce0f3
refactor(extstore): centralize cancellation token in the options inst…
cconstable Sep 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,9 @@ public WorkflowTaskResult handleWorkflowTask(
.setForceWorkflowTask(
localActivityTaskCount > 0 && !context.isWorkflowMethodCompleted())
.setNonfirstLocalActivityAttempts(localActivityMeteringHelper.getNonfirstAttempts())
.setSdkFlags(newSdkFlags);
.setSdkFlags(newSdkFlags)
.setParentWorkflowExecution(context.getParentWorkflowExecution())
.setContinuedAsNew(context.getContinuedExecutionRunId().isPresent());
if (workflowStateMachines.sdkNameToWrite() != null) {
result.setWriteSdkName(workflowStateMachines.sdkNameToWrite());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import io.temporal.common.converter.DataConverter;
import io.temporal.internal.common.ProtobufTimeUtils;
import io.temporal.internal.common.WorkflowExecutionUtils;
import io.temporal.internal.payload.storage.ExternalStorageRunner;
import io.temporal.internal.worker.*;
import io.temporal.payload.context.WorkflowSerializationContext;
import io.temporal.serviceclient.MetricsTag;
Expand All @@ -34,6 +35,7 @@
import java.time.Duration;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CancellationException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import org.slf4j.Logger;
Expand Down Expand Up @@ -89,12 +91,19 @@ private Result handleWorkflowTaskWithQuery(
boolean useCache = stickyTaskQueue != null;

try {
workflowTask = retrieveStoredPayloads(workflowTask);
workflowRunTaskHandler =
getOrCreateWorkflowExecutor(useCache, workflowTask, metricsScope, createdNew);
logWorkflowTaskToBeProcessed(workflowTask, createdNew);

ServiceWorkflowHistoryIterator historyIterator =
new ServiceWorkflowHistoryIterator(service, namespace, workflowTask, metricsScope);
new ServiceWorkflowHistoryIterator(
service,
namespace,
workflowTask,
metricsScope,
options.getExternalStorageRunner(),
options.getStorageCancellation());
boolean finalCommand;
Result result;

Expand Down Expand Up @@ -132,7 +141,7 @@ private Result handleWorkflowTaskWithQuery(
}

return result;
} catch (InterruptedException e) {
} catch (InterruptedException | CancellationException e) {
throw e;
} catch (Throwable e) {
// Note here that the executor might not be in the cache, even when the caching is on. In that
Expand Down Expand Up @@ -170,6 +179,18 @@ private Result handleWorkflowTaskWithQuery(
}
}

private PollWorkflowTaskQueueResponse.Builder retrieveStoredPayloads(
PollWorkflowTaskQueueResponse.Builder workflowTask) {
ExternalStorageRunner externalStorageRunner = options.getExternalStorageRunner();
if (externalStorageRunner == null) {
ExternalStorageRunner.throwIfContainsReference(workflowTask.build());
return workflowTask;
}
return externalStorageRunner
.retrieve(workflowTask.build(), options.getStorageCancellation())
.toBuilder();
}

private Result createCompletedWFTRequest(
String workflowType,
PollWorkflowTaskQueueResponseOrBuilder workflowTask,
Expand Down Expand Up @@ -253,7 +274,8 @@ private Result createCompletedWFTRequest(
null,
result.isFinalCommand(),
eventIdSetHandle,
result.getApplyPostCompletionMetrics());
result.getApplyPostCompletionMetrics(),
result.isContinuedAsNew() ? null : result.getParentWorkflowExecution());
}

private Result failureToWFTResult(
Expand Down Expand Up @@ -395,6 +417,13 @@ private WorkflowRunTaskHandler createStatefulHandler(
.blockingStub()
.withOption(METRICS_TAGS_CALL_OPTIONS_KEY, metricsScope)
.getWorkflowExecutionHistory(getHistoryRequest);
ExternalStorageRunner externalStorageRunner = options.getExternalStorageRunner();
if (externalStorageRunner == null) {
ExternalStorageRunner.throwIfContainsReference(getHistoryResponse);
} else {
getHistoryResponse =
externalStorageRunner.retrieve(getHistoryResponse, options.getStorageCancellation());
}
workflowTask
.setHistory(getHistoryResponse.getHistory())
.setNextPageToken(getHistoryResponse.getNextPageToken());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@
import io.temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryRequest;
import io.temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryResponse;
import io.temporal.api.workflowservice.v1.PollWorkflowTaskQueueResponseOrBuilder;
import io.temporal.common.CancellationToken;
import io.temporal.internal.payload.storage.ExternalStorageRunner;
import io.temporal.internal.retryer.GrpcRetryer;
import io.temporal.serviceclient.RpcRetryOptions;
import io.temporal.serviceclient.WorkflowServiceStubs;
import java.time.Duration;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.concurrent.CancellationException;
import javax.annotation.Nullable;

/** Supports iteration over history while loading new pages through calls to the service. */
class ServiceWorkflowHistoryIterator implements WorkflowHistoryIterator {
Expand All @@ -29,6 +33,8 @@ class ServiceWorkflowHistoryIterator implements WorkflowHistoryIterator {
private final Scope metricsScope;
private final PollWorkflowTaskQueueResponseOrBuilder task;
private final GrpcRetryer grpcRetryer;
private final @Nullable ExternalStorageRunner externalStorageRunner;
private final CancellationToken<CancellationException> storageCancellation;
private Deadline deadline;
private Iterator<HistoryEvent> current;
ByteString nextPageToken;
Expand All @@ -38,10 +44,22 @@ class ServiceWorkflowHistoryIterator implements WorkflowHistoryIterator {
String namespace,
PollWorkflowTaskQueueResponseOrBuilder task,
Scope metricsScope) {
this(service, namespace, task, metricsScope, null, CancellationToken.none());
}

ServiceWorkflowHistoryIterator(
WorkflowServiceStubs service,
String namespace,
PollWorkflowTaskQueueResponseOrBuilder task,
Scope metricsScope,
@Nullable ExternalStorageRunner externalStorageRunner,
CancellationToken<CancellationException> storageCancellation) {
this.storageCancellation = storageCancellation;
this.service = service;
this.namespace = namespace;
this.task = task;
this.metricsScope = metricsScope;
this.externalStorageRunner = externalStorageRunner;
// TODO Refactor WorkflowHistoryIteratorTest or WorkflowHistoryIterator to remove this check.
// `service == null` shouldn't be allowed as it's needed for a normal functioning of this
// class.
Expand All @@ -64,7 +82,13 @@ public boolean hasNext() {
// true.
GetWorkflowExecutionHistoryResponse response = queryWorkflowExecutionHistory();

current = response.getHistory().getEventsList().iterator();
History history = response.getHistory();
if (externalStorageRunner == null) {
ExternalStorageRunner.throwIfContainsReference(history);
} else {
history = externalStorageRunner.retrieve(history, storageCancellation);
}
current = history.getEventsList().iterator();
nextPageToken = response.getNextPageToken();
// Server can return an empty page, but a valid nextPageToken that contains
// more events.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package io.temporal.internal.replay;

import io.temporal.api.command.v1.Command;
import io.temporal.api.common.v1.WorkflowExecution;
import io.temporal.api.protocol.v1.Message;
import io.temporal.api.query.v1.WorkflowQueryResult;
import io.temporal.common.VersioningBehavior;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;

public final class WorkflowTaskResult {

Expand All @@ -26,6 +28,8 @@ public static final class Builder {
private String writeSdkVersion;
private VersioningBehavior versioningBehavior;
private Runnable applyPostCompletionMetrics;
private @Nullable WorkflowExecution parentWorkflowExecution;
private boolean continuedAsNew;

public Builder setCommands(List<Command> commands) {
this.commands = commands;
Expand Down Expand Up @@ -77,6 +81,16 @@ public Builder setVersioningBehavior(VersioningBehavior versioningBehavior) {
return this;
}

public Builder setParentWorkflowExecution(@Nullable WorkflowExecution parentWorkflowExecution) {
this.parentWorkflowExecution = parentWorkflowExecution;
return this;
}

public Builder setContinuedAsNew(boolean continuedAsNew) {
this.continuedAsNew = continuedAsNew;
return this;
}

public Builder setApplyPostCompletionMetrics(Runnable applyPostCompletionMetrics) {
this.applyPostCompletionMetrics = applyPostCompletionMetrics;
return this;
Expand All @@ -94,7 +108,9 @@ public WorkflowTaskResult build() {
writeSdkName,
writeSdkVersion,
versioningBehavior == null ? VersioningBehavior.UNSPECIFIED : versioningBehavior,
applyPostCompletionMetrics);
applyPostCompletionMetrics,
parentWorkflowExecution,
continuedAsNew);
}
}

Expand All @@ -109,6 +125,8 @@ public WorkflowTaskResult build() {
private final String writeSdkVersion;
private final VersioningBehavior versioningBehavior;
private final Runnable applyPostCompletionMetrics;
private final @Nullable WorkflowExecution parentWorkflowExecution;
private final boolean continuedAsNew;

private WorkflowTaskResult(
List<Command> commands,
Expand All @@ -121,7 +139,9 @@ private WorkflowTaskResult(
String writeSdkName,
String writeSdkVersion,
VersioningBehavior versioningBehavior,
Runnable applyPostCompletionMetrics) {
Runnable applyPostCompletionMetrics,
@Nullable WorkflowExecution parentWorkflowExecution,
boolean continuedAsNew) {
this.commands = commands;
this.messages = messages;
this.nonfirstLocalActivityAttempts = nonfirstLocalActivityAttempts;
Expand All @@ -136,6 +156,19 @@ private WorkflowTaskResult(
this.writeSdkVersion = writeSdkVersion;
this.versioningBehavior = versioningBehavior;
this.applyPostCompletionMetrics = applyPostCompletionMetrics;
this.parentWorkflowExecution = parentWorkflowExecution;
this.continuedAsNew = continuedAsNew;
}

/** The workflow that started this one as a child, or {@code null} if it has no parent. */
@Nullable
public WorkflowExecution getParentWorkflowExecution() {
return parentWorkflowExecution;
}

/** Whether this run was created by a continue-as-new rather than started directly. */
public boolean isContinuedAsNew() {
return continuedAsNew;
}

public List<Command> getCommands() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.uber.m3.tally.NoopScope;
import com.uber.m3.tally.Scope;
import io.temporal.api.common.v1.WorkerVersionStamp;
import io.temporal.common.CancellationToken;
import io.temporal.common.context.ContextPropagator;
import io.temporal.common.converter.DataConverter;
import io.temporal.common.converter.GlobalDataConverter;
Expand All @@ -12,6 +13,7 @@
import io.temporal.worker.WorkerDeploymentOptions;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.CancellationException;
import javax.annotation.Nullable;

public final class SingleWorkerOptions {
Expand Down Expand Up @@ -48,6 +50,7 @@ public static final class Builder {
private String workerControlTaskQueue;
private PreferredVersionProvider preferredVersionProvider;
private @Nullable ExternalStorageRunner externalStorageRunner;
private CancellationToken<CancellationException> storageCancellation = CancellationToken.none();

private Builder() {}

Expand Down Expand Up @@ -77,6 +80,7 @@ private Builder(SingleWorkerOptions options) {
this.workerControlTaskQueue = options.getWorkerControlTaskQueue();
this.preferredVersionProvider = options.getPreferredVersionProvider();
this.externalStorageRunner = options.getExternalStorageRunner();
this.storageCancellation = options.getStorageCancellation();
}

public Builder setIdentity(String identity) {
Expand Down Expand Up @@ -189,6 +193,13 @@ public Builder setPreferredVersionProvider(PreferredVersionProvider preferredVer
return this;
}

/** Cancelled when this worker stops, to abandon its in-flight external storage work. */
public Builder setStorageCancellation(
CancellationToken<CancellationException> storageCancellation) {
this.storageCancellation = storageCancellation;
return this;
}

public Builder setExternalStorageRunner(@Nullable ExternalStorageRunner externalStorageRunner) {
this.externalStorageRunner = externalStorageRunner;
return this;
Expand Down Expand Up @@ -237,7 +248,8 @@ public SingleWorkerOptions build() {
this.allowActivityHeartbeatDuringShutdown,
this.workerControlTaskQueue,
this.preferredVersionProvider,
this.externalStorageRunner);
this.externalStorageRunner,
this.storageCancellation);
}
}

Expand All @@ -263,6 +275,7 @@ public SingleWorkerOptions build() {
private final String workerControlTaskQueue;
private final PreferredVersionProvider preferredVersionProvider;
private final @Nullable ExternalStorageRunner externalStorageRunner;
private final CancellationToken<CancellationException> storageCancellation;

private SingleWorkerOptions(
String identity,
Expand All @@ -286,7 +299,8 @@ private SingleWorkerOptions(
boolean allowActivityHeartbeatDuringShutdown,
String workerControlTaskQueue,
PreferredVersionProvider preferredVersionProvider,
@Nullable ExternalStorageRunner externalStorageRunner) {
@Nullable ExternalStorageRunner externalStorageRunner,
CancellationToken<CancellationException> storageCancellation) {
this.identity = identity;
this.binaryChecksum = binaryChecksum;
this.buildId = buildId;
Expand All @@ -309,6 +323,7 @@ private SingleWorkerOptions(
this.workerControlTaskQueue = workerControlTaskQueue;
this.preferredVersionProvider = preferredVersionProvider;
this.externalStorageRunner = externalStorageRunner;
this.storageCancellation = storageCancellation;
}

public String getIdentity() {
Expand Down Expand Up @@ -411,6 +426,10 @@ public ExternalStorageRunner getExternalStorageRunner() {
return externalStorageRunner;
}

public CancellationToken<CancellationException> getStorageCancellation() {
return storageCancellation;
}

public WorkerVersioningOptions getWorkerVersioningOptions() {
return new WorkerVersioningOptions(
this.getBuildId(), this.isUsingBuildIdForVersioning(), this.getDeploymentOptions());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import io.temporal.internal.activity.ActivityExecutionContextFactory;
import io.temporal.internal.activity.ActivityTaskHandlerImpl;
import io.temporal.internal.activity.LocalActivityExecutionContextFactoryImpl;
import io.temporal.internal.concurrent.structured.CancelSource;
import io.temporal.internal.replay.ReplayWorkflowTaskHandler;
import io.temporal.internal.sync.POJOWorkflowImplementationFactory;
import io.temporal.internal.sync.WorkflowThreadExecutor;
Expand Down Expand Up @@ -54,6 +55,8 @@ public class SyncWorkflowWorker implements SuspendableWorker {
private final POJOWorkflowImplementationFactory factory;
private final DataConverter dataConverter;
private final ActivityTaskHandlerImpl laTaskHandler;
private final CancelSource<CancellationException> storageCancellation =
new CancelSource<>(() -> new CancellationException("Worker shutdown"));
private boolean runningLocalActivityWorker;

public SyncWorkflowWorker(
Expand All @@ -71,6 +74,10 @@ public SyncWorkflowWorker(
@Nonnull SlotSupplier<WorkflowSlotInfo> slotSupplier,
@Nonnull SlotSupplier<LocalActivitySlotInfo> laSlotSupplier,
@Nonnull NamespaceCapabilities namespaceCapabilities) {
singleWorkerOptions =
SingleWorkerOptions.newBuilder(singleWorkerOptions)
.setStorageCancellation(storageCancellation.token())
.build();
this.identity = singleWorkerOptions.getIdentity();
this.namespace = namespace;
this.taskQueue = taskQueue;
Expand Down Expand Up @@ -175,8 +182,12 @@ public boolean start() {

@Override
public CompletableFuture<Void> shutdown(ShutdownManager shutdownManager, boolean interruptTasks) {
return workflowWorker
.shutdown(shutdownManager, interruptTasks)
CompletableFuture<Void> workflowWorkerShutdown =
workflowWorker.shutdown(shutdownManager, interruptTasks);
if (interruptTasks) {
storageCancellation.cancel();
}
return workflowWorkerShutdown
.thenCompose(ignore -> laWorker.shutdown(shutdownManager, interruptTasks))
.exceptionally(
e -> {
Expand Down
Loading
Loading