Skip to content

External Storage Integration: Activity worker, client - #3020

Open
cconstable wants to merge 8 commits into
mainfrom
extstore/activity-client
Open

External Storage Integration: Activity worker, client#3020
cconstable wants to merge 8 commits into
mainfrom
extstore/activity-client

Conversation

@cconstable

@cconstable cconstable commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

What was changed

  • Main integration points are in WorkflowClientInternalImpl, HeartbeatContextImpl, and ActivityWorker.
  • Heartbeat store requests are canceled if they pass a timeout (as defined by the caller/heartbeat code).
  • Lot's of plumbing in other adjacent files.

Why?

  • Activity workers and clients should offload payloads with external storage.

Checklist

  • Added lots of tests.

@cconstable
cconstable changed the base branch from main to extstore/foundation August 19, 2026 18:01
@cconstable cconstable changed the title extstore/activity client External Storage Integration: Activity worker, client Aug 19, 2026
Comment on lines +145 to +148
public ListWorkflowExecutionsResponse listWorkflowExecutions(
ListWorkflowExecutionsRequest listRequest) {
return next.listWorkflowExecutions(listRequest);
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed the previous concern of eagerly fetching external payloads #2978 (comment). They are now loaded lazily.

@cconstable
cconstable force-pushed the extstore/activity-client branch from 37ae3c7 to 7069249 Compare August 19, 2026 18:45

try {
sendReply(taskToken, result, metricsScope);
sendReply(taskToken, result, metricsScope, activityStorageTarget(pollResponse));

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previous comment about pivoting this target based on standalone vs workflow activity has been addressed #2978 (comment)

activityStorageTarget delegates to storageTargetForActivityTask (which can be seen above)

@cconstable
cconstable marked this pull request as ready for review August 19, 2026 18:57
@cconstable
cconstable requested a review from a team as a code owner August 19, 2026 18:57
@cconstable
cconstable force-pushed the extstore/foundation branch from 460bfbf to b3804da Compare August 21, 2026 20:35
@cconstable
cconstable force-pushed the extstore/activity-client branch from 7069249 to 550e4f2 Compare August 21, 2026 21:57
@cconstable
cconstable force-pushed the extstore/foundation branch from b3804da to ca09b50 Compare August 24, 2026 01:23
@cconstable
cconstable force-pushed the extstore/activity-client branch 3 times, most recently from ffafefa to 69a8a3a Compare August 24, 2026 19:38
@cconstable
cconstable force-pushed the extstore/activity-client branch from fad5ed3 to 8d1399c Compare August 27, 2026 20:07
* <p>This This is an internal class that is not exposed to users or workflow code. The intent is to
* use this data converter to consolidate extstore usage within the SDK.
*/
public final class ExternalStorageDataConverter implements DataConverter {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the new abstraction that replaces the previous External Storage client decorator. It wraps the data converter supplied to the WorkflowClient in RootWorkflowClientInvoker

Comment on lines +67 to +72
public RootWorkflowClientInvoker(
GenericWorkflowClient genericClient,
WorkflowClientOptions clientOptions,
WorkerFactoryRegistry workerFactoryRegistry,
@Nullable ExternalStorageRunner externalStorage) {
this.externalStorage = externalStorage;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class inherits the external storage runner from WorkflowClientInternalImpl and then uses it to create the new ExternalStorageDataConverter

Comment on lines 781 to 787
Stream<HistoryEvent> streamHistory(WorkflowExecution execution) {
Preconditions.checkNotNull(execution, "execution is required");

GetWorkflowExecutionHistoryIterator iterator =
new GetWorkflowExecutionHistoryIterator(
options.getNamespace(), execution, null, genericClient);
iterator.init();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This no longer runs through external storage as it was previously being handled by the client decorator. The worker still uses extstore when replaying it's own history (see https://github.com/temporalio/sdk-java/pull/3017/changes#diff-4928a50c4af33d91cd6b57a121d20ed2594cd314e118beb01c259d5e7427728c).

Is this just used for offline replay? Do we need to use extstore here?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we discussed offline we don't need to? At least that is what other SDKs did so we should be consistent

@cconstable
cconstable force-pushed the extstore/activity-client branch from 8d1399c to 0c7b308 Compare August 27, 2026 21:28
throw new IllegalArgumentException("Either activity id or task token are required");
}
RespondActivityTaskFailedByIdRequest request =
RespondActivityTaskFailedByIdRequest unstoredRequest =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this request seems to follow a different pattern then all your others, may be cleaner to follow the convention of alway calling request = storeOutbound(builder.Build()), but just a nit feel free to ignore


public RootWorkflowClientInvoker(
GenericWorkflowClient genericClient,
WorkflowClientOptions clientOptions,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we could get rid of WorkflowClientOptions here so we know no one accidentally calls clientOptions.getDataConverter() now or in the future

this.workflowServiceStubs =
WorkflowServiceStubs.newServiceStubs(serviceStubsOptionsBuilder.build());

WorkflowClient client =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the change for?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

client.getInternal() is used below now so we needed to pull out a reference.

@Quinn-With-Two-Ns

Quinn-With-Two-Ns commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Couple nits, generally looks good. Going to do an AI review as well. Would want Justin to look as well of course

@Quinn-With-Two-Ns

Copy link
Copy Markdown
Contributor

Codex identified these two additional issues

  1. High — Manual completion uses the wrong storage target for workflow
     activities

     The factory paths

     (https://github.com/temporalio/sdk-java/blob/0c7b3087f75afaf6d30a8cc6034686e9f847a13e/temporal-sdk/src/main/java/io/temporal/internal/client/external/ManualActivityCompletionClientFactoryImpl.java#L49-L117)
     and local-manual-completion path

     (https://github.com/temporalio/sdk-java/blob/0c7b3087f75afaf6d30a8cc6034686e9f847a13e/temporal-sdk/src/main/java/io/temporal/internal/activity/ActivityExecutionContextImpl.java#L154-L170)
     always create StorageDriverActivityInfo. For workflow-scheduled activities,
     the established policy is to use StorageDriverWorkflowInfo, as
     ActivityWorker does

     (https://github.com/temporalio/sdk-java/blob/0c7b3087f75afaf6d30a8cc6034686e9f847a13e/temporal-sdk/src/main/java/io/temporal/internal/worker/ActivityWorker.java#L274-L289).

     This means synchronous and manual completion of the same workflow activity
     can select different drivers or storage layouts. The context/execution
     already contains enough information to distinguish workflow from standalone
     activities. I’d centralize that pivot and add tests covering both activity
     kinds across task-token, by-ID, and local manual completion.

  2. Medium — TestActivityEnvironment heartbeat listeners cannot decode
     offloaded details

     The environment now passes external storage into the heartbeat context

     (https://github.com/temporalio/sdk-java/blob/0c7b3087f75afaf6d30a8cc6034686e9f847a13e/temporal-testing/src/main/java/io/temporal/testing/TestActivityEnvironmentInternal.java#L104-L116),
     so sufficiently large heartbeat details reach the mock service as
     references. However, the heartbeat listener still decodes them with the
     ordinary DataConverter

     (https://github.com/temporalio/sdk-java/blob/0c7b3087f75afaf6d30a8cc6034686e9f847a13e/temporal-testing/src/main/java/io/temporal/testing/TestActivityEnvironmentInternal.java#L135-L152)
     without retrieving them first. With threshold 0, for example,
     setActivityHeartbeatListener will receive an external-storage reference and
     fail conversion. Resolve references before listener conversion and add an
     environment regression test.

At a quick glance they look legit, but please let me know.

@cconstable
cconstable force-pushed the extstore/foundation branch from 2d2d90e to 504467c Compare August 28, 2026 16:10
Base automatically changed from extstore/foundation to main August 28, 2026 17:27
@cconstable
cconstable force-pushed the extstore/activity-client branch from 0c7b308 to f527885 Compare August 28, 2026 21:35
@cconstable
cconstable force-pushed the extstore/activity-client branch from f527885 to 2cedf19 Compare August 31, 2026 15:46
@cconstable

Copy link
Copy Markdown
Contributor Author

The first issue that codex identified led me to do a little refactoring. I believe all the feedback has been addressed here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants