Skip to content

Commit 37ae3c7

Browse files
committed
feat(extstore): integrate into activity worker, heartbeats, and client pipelines.
1 parent 460bfbf commit 37ae3c7

22 files changed

Lines changed: 1135 additions & 126 deletions

temporal-sdk/src/main/java/io/temporal/client/WorkflowClientInternalImpl.java

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,15 @@
1818
import io.temporal.internal.WorkflowThreadMarker;
1919
import io.temporal.internal.client.*;
2020
import io.temporal.internal.client.NexusStartWorkflowResponse;
21+
import io.temporal.internal.client.external.ExternalStorageGenericWorkflowClient;
2122
import io.temporal.internal.client.external.GenericWorkflowClient;
2223
import io.temporal.internal.client.external.GenericWorkflowClientImpl;
2324
import io.temporal.internal.client.external.ManualActivityCompletionClientFactory;
2425
import io.temporal.internal.common.PluginUtils;
26+
import io.temporal.internal.payload.storage.ExternalStorage;
2527
import io.temporal.internal.sync.StubMarker;
2628
import io.temporal.internal.worker.HeartbeatManager;
29+
import io.temporal.payload.storage.ExternalStorageOptions;
2730
import io.temporal.serviceclient.MetricsTag;
2831
import io.temporal.serviceclient.WorkflowServiceStubs;
2932
import io.temporal.serviceclient.WorkflowServiceStubsPlugin;
@@ -56,6 +59,7 @@ final class WorkflowClientInternalImpl implements WorkflowClient, WorkflowClient
5659
private final WorkerFactoryRegistry workerFactoryRegistry = new WorkerFactoryRegistry();
5760
private final String workerGroupingKey = java.util.UUID.randomUUID().toString();
5861
private final @Nullable HeartbeatManager heartbeatManager;
62+
private final @Nullable ExternalStorage externalStorage;
5963

6064
/**
6165
* Creates client that connects to an instance of the Temporal Service. Cannot be used from within
@@ -106,15 +110,27 @@ public static WorkflowClient newInstance(
106110
.getOptions()
107111
.getMetricsScope()
108112
.tagged(MetricsTag.defaultTags(options.getNamespace()));
109-
this.genericClient = new GenericWorkflowClientImpl(workflowServiceStubs, metricsScope);
113+
ExternalStorageOptions externalStorageOptions = options.getExternalStorage();
114+
ExternalStorage externalStorage =
115+
externalStorageOptions == null ? null : ExternalStorage.create(externalStorageOptions);
116+
this.externalStorage = externalStorage;
117+
GenericWorkflowClient genericClient =
118+
new GenericWorkflowClientImpl(workflowServiceStubs, metricsScope);
119+
if (externalStorage != null) {
120+
genericClient =
121+
new ExternalStorageGenericWorkflowClient(
122+
genericClient, externalStorage, options.getNamespace());
123+
}
124+
this.genericClient = genericClient;
110125
this.interceptors = options.getInterceptors();
111126
this.workflowClientCallsInvoker = initializeClientInvoker();
112127
this.manualActivityCompletionClientFactory =
113128
ManualActivityCompletionClientFactory.newFactory(
114129
workflowServiceStubs,
115130
options.getNamespace(),
116131
options.getIdentity(),
117-
options.getDataConverter());
132+
options.getDataConverter(),
133+
externalStorage);
118134

119135
java.time.Duration heartbeatInterval = options.getWorkerHeartbeatInterval();
120136
if (!heartbeatInterval.isNegative()) {
@@ -127,7 +143,8 @@ public static WorkflowClient newInstance(
127143

128144
private WorkflowClientCallsInterceptor initializeClientInvoker() {
129145
WorkflowClientCallsInterceptor workflowClientInvoker =
130-
new RootWorkflowClientInvoker(genericClient, options, workerFactoryRegistry);
146+
new RootWorkflowClientInvoker(
147+
genericClient, options, workerFactoryRegistry, externalStorage);
131148
for (WorkflowClientInterceptor clientInterceptor : interceptors) {
132149
workflowClientInvoker =
133150
clientInterceptor.workflowClientCallsInterceptor(workflowClientInvoker);
@@ -815,6 +832,12 @@ public HeartbeatManager getHeartbeatManager() {
815832
return heartbeatManager;
816833
}
817834

835+
@Override
836+
@Nullable
837+
public ExternalStorage getExternalStorage() {
838+
return externalStorage;
839+
}
840+
818841
@Override
819842
public NexusStartWorkflowResponse startNexus(
820843
NexusStartWorkflowRequest request, Functions.Proc workflow) {

temporal-sdk/src/main/java/io/temporal/client/WorkflowClientOptions.java

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@
77
import io.temporal.common.converter.DataConverter;
88
import io.temporal.common.converter.GlobalDataConverter;
99
import io.temporal.common.interceptors.WorkflowClientInterceptor;
10+
import io.temporal.payload.storage.ExternalStorageOptions;
1011
import java.lang.management.ManagementFactory;
1112
import java.time.Duration;
1213
import java.util.Arrays;
1314
import java.util.Collections;
1415
import java.util.List;
1516
import java.util.Objects;
17+
import javax.annotation.Nullable;
1618

1719
/** Options for WorkflowClient configuration. */
1820
public final class WorkflowClientOptions {
@@ -52,6 +54,7 @@ public static final class Builder {
5254
private QueryRejectCondition queryRejectCondition;
5355
private WorkflowClientPlugin[] plugins;
5456
private Duration workerHeartbeatInterval;
57+
private @Nullable ExternalStorageOptions externalStorage;
5558

5659
private Builder() {}
5760

@@ -68,6 +71,7 @@ private Builder(WorkflowClientOptions options) {
6871
queryRejectCondition = options.queryRejectCondition;
6972
plugins = options.plugins;
7073
workerHeartbeatInterval = options.workerHeartbeatInterval;
74+
externalStorage = options.externalStorage;
7175
}
7276

7377
public Builder setNamespace(String namespace) {
@@ -170,6 +174,16 @@ public Builder setWorkerHeartbeatInterval(Duration workerHeartbeatInterval) {
170174
return this;
171175
}
172176

177+
/**
178+
* Configures offloading of large payloads to external storage for workflows and activities
179+
* created through this client and its workers.
180+
*/
181+
@Experimental
182+
public Builder setExternalStorage(@Nullable ExternalStorageOptions externalStorage) {
183+
this.externalStorage = externalStorage;
184+
return this;
185+
}
186+
173187
public WorkflowClientOptions build() {
174188
return new WorkflowClientOptions(
175189
namespace,
@@ -180,7 +194,8 @@ public WorkflowClientOptions build() {
180194
contextPropagators,
181195
queryRejectCondition,
182196
plugins == null ? EMPTY_PLUGINS : plugins,
183-
resolveHeartbeatInterval(workerHeartbeatInterval));
197+
resolveHeartbeatInterval(workerHeartbeatInterval),
198+
externalStorage);
184199
}
185200

186201
/**
@@ -207,7 +222,8 @@ public WorkflowClientOptions validateAndBuildWithDefaults() {
207222
? QueryRejectCondition.QUERY_REJECT_CONDITION_UNSPECIFIED
208223
: queryRejectCondition,
209224
plugins == null ? EMPTY_PLUGINS : plugins,
210-
resolveHeartbeatInterval(workerHeartbeatInterval));
225+
resolveHeartbeatInterval(workerHeartbeatInterval),
226+
externalStorage);
211227
}
212228

213229
private static Duration resolveHeartbeatInterval(Duration raw) {
@@ -250,6 +266,8 @@ private static Duration resolveHeartbeatInterval(Duration raw) {
250266

251267
private final Duration workerHeartbeatInterval;
252268

269+
private final @Nullable ExternalStorageOptions externalStorage;
270+
253271
private WorkflowClientOptions(
254272
String namespace,
255273
DataConverter dataConverter,
@@ -259,7 +277,8 @@ private WorkflowClientOptions(
259277
List<ContextPropagator> contextPropagators,
260278
QueryRejectCondition queryRejectCondition,
261279
WorkflowClientPlugin[] plugins,
262-
Duration workerHeartbeatInterval) {
280+
Duration workerHeartbeatInterval,
281+
@Nullable ExternalStorageOptions externalStorage) {
263282
this.namespace = namespace;
264283
this.dataConverter = dataConverter;
265284
this.interceptors = interceptors;
@@ -269,6 +288,7 @@ private WorkflowClientOptions(
269288
this.queryRejectCondition = queryRejectCondition;
270289
this.plugins = plugins;
271290
this.workerHeartbeatInterval = workerHeartbeatInterval;
291+
this.externalStorage = externalStorage;
272292
}
273293

274294
/**
@@ -335,6 +355,12 @@ public Duration getWorkerHeartbeatInterval() {
335355
return workerHeartbeatInterval;
336356
}
337357

358+
/** External storage configuration, or null when external storage is disabled. */
359+
@Experimental
360+
public @Nullable ExternalStorageOptions getExternalStorage() {
361+
return externalStorage;
362+
}
363+
338364
@Override
339365
public String toString() {
340366
return "WorkflowClientOptions{"
@@ -359,6 +385,8 @@ public String toString() {
359385
+ Arrays.toString(plugins)
360386
+ ", workerHeartbeatInterval="
361387
+ workerHeartbeatInterval
388+
+ ", externalStorage="
389+
+ externalStorage
362390
+ '}';
363391
}
364392

@@ -376,7 +404,8 @@ public boolean equals(Object o) {
376404
&& queryRejectCondition == that.queryRejectCondition
377405
&& Arrays.equals(plugins, that.plugins)
378406
&& com.google.common.base.Objects.equal(
379-
workerHeartbeatInterval, that.workerHeartbeatInterval);
407+
workerHeartbeatInterval, that.workerHeartbeatInterval)
408+
&& com.google.common.base.Objects.equal(externalStorage, that.externalStorage);
380409
}
381410

382411
@Override
@@ -390,6 +419,7 @@ public int hashCode() {
390419
contextPropagators,
391420
queryRejectCondition,
392421
Arrays.hashCode(plugins),
393-
workerHeartbeatInterval);
422+
workerHeartbeatInterval,
423+
externalStorage);
394424
}
395425
}

temporal-sdk/src/main/java/io/temporal/internal/activity/ActivityExecutionContextFactoryImpl.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
import io.temporal.client.WorkflowClient;
55
import io.temporal.common.converter.DataConverter;
66
import io.temporal.internal.client.external.ManualActivityCompletionClientFactory;
7+
import io.temporal.internal.payload.storage.ExternalStorage;
78
import java.nio.ByteBuffer;
89
import java.time.Duration;
910
import java.util.Arrays;
1011
import java.util.Objects;
1112
import java.util.concurrent.ConcurrentHashMap;
1213
import java.util.concurrent.ConcurrentMap;
1314
import java.util.concurrent.ScheduledExecutorService;
15+
import javax.annotation.Nullable;
1416

1517
public class ActivityExecutionContextFactoryImpl implements ActivityExecutionContextFactory {
1618
private final WorkflowClient client;
@@ -21,6 +23,7 @@ public class ActivityExecutionContextFactoryImpl implements ActivityExecutionCon
2123
private final DataConverter dataConverter;
2224
private final ScheduledExecutorService heartbeatExecutor;
2325
private final ManualActivityCompletionClientFactory manualCompletionClientFactory;
26+
private final @Nullable ExternalStorage externalStorage;
2427
private final ConcurrentMap<ByteBuffer, ActivityExecutionContextImpl> activeContexts =
2528
new ConcurrentHashMap<>();
2629

@@ -31,7 +34,8 @@ public ActivityExecutionContextFactoryImpl(
3134
Duration maxHeartbeatThrottleInterval,
3235
Duration defaultHeartbeatThrottleInterval,
3336
DataConverter dataConverter,
34-
ScheduledExecutorService heartbeatExecutor) {
37+
ScheduledExecutorService heartbeatExecutor,
38+
@Nullable ExternalStorage externalStorage) {
3539
this.client = Objects.requireNonNull(client);
3640
this.identity = identity;
3741
this.namespace = Objects.requireNonNull(namespace);
@@ -40,9 +44,10 @@ public ActivityExecutionContextFactoryImpl(
4044
Objects.requireNonNull(defaultHeartbeatThrottleInterval);
4145
this.dataConverter = Objects.requireNonNull(dataConverter);
4246
this.heartbeatExecutor = Objects.requireNonNull(heartbeatExecutor);
47+
this.externalStorage = externalStorage;
4348
this.manualCompletionClientFactory =
4449
ManualActivityCompletionClientFactory.newFactory(
45-
client.getWorkflowServiceStubs(), namespace, identity, dataConverter);
50+
client.getWorkflowServiceStubs(), namespace, identity, dataConverter, externalStorage);
4651
}
4752

4853
@Override
@@ -63,7 +68,8 @@ public InternalActivityExecutionContext createContext(
6368
identity,
6469
maxHeartbeatThrottleInterval,
6570
defaultHeartbeatThrottleInterval,
66-
() -> cleanupContext(info.getTaskToken(), false));
71+
() -> cleanupContext(info.getTaskToken(), false),
72+
externalStorage);
6773
activeContexts.put(taskToken, context);
6874
return context;
6975
}

temporal-sdk/src/main/java/io/temporal/internal/activity/ActivityExecutionContextImpl.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,17 @@
1010
import io.temporal.common.CancellationToken;
1111
import io.temporal.common.converter.DataConverter;
1212
import io.temporal.internal.client.external.ManualActivityCompletionClientFactory;
13+
import io.temporal.internal.payload.storage.ExternalStorage;
1314
import io.temporal.payload.context.ActivitySerializationContext;
15+
import io.temporal.payload.storage.StorageDriverActivityInfo;
1416
import io.temporal.workflow.Functions;
1517
import java.lang.reflect.Type;
1618
import java.time.Duration;
1719
import java.util.Optional;
1820
import java.util.concurrent.ScheduledExecutorService;
1921
import java.util.concurrent.locks.Lock;
2022
import java.util.concurrent.locks.ReentrantLock;
23+
import javax.annotation.Nullable;
2124
import javax.annotation.concurrent.ThreadSafe;
2225

2326
/**
@@ -55,7 +58,8 @@ class ActivityExecutionContextImpl implements InternalActivityExecutionContext {
5558
String identity,
5659
Duration maxHeartbeatThrottleInterval,
5760
Duration defaultHeartbeatThrottleInterval,
58-
Functions.Proc closeCallback) {
61+
Functions.Proc closeCallback,
62+
@Nullable ExternalStorage externalStorage) {
5963
this.client = client;
6064
this.activity = activity;
6165
this.metricsScope = metricsScope;
@@ -73,7 +77,8 @@ class ActivityExecutionContextImpl implements InternalActivityExecutionContext {
7377
metricsScope,
7478
identity,
7579
maxHeartbeatThrottleInterval,
76-
defaultHeartbeatThrottleInterval);
80+
defaultHeartbeatThrottleInterval,
81+
externalStorage);
7782
}
7883

7984
/**
@@ -155,7 +160,14 @@ public ManualActivityCompletionClient useLocalManualCompletion() {
155160
new ActivitySerializationContext(info);
156161
return new CompletionAwareManualCompletionClient(
157162
manualCompletionClientFactory.getClient(
158-
info.getTaskToken(), metricsScope, activitySerializationContext),
163+
info.getTaskToken(),
164+
metricsScope,
165+
activitySerializationContext,
166+
new StorageDriverActivityInfo(
167+
info.getNamespace(),
168+
info.getActivityId(),
169+
info.getActivityRunId(),
170+
info.getActivityType())),
159171
completionHandle);
160172
} finally {
161173
lock.unlock();

0 commit comments

Comments
 (0)