Repository navigation
Add Copyable for local activity transfers #3147
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Quinn-With-Two-Ns
wants to merge
3
commits into
main
Choose a base branch
from
temporal-copyable-local-activities
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
30 changes: 30 additions & 0 deletions
30
temporal-sdk/src/main/java/io/temporal/common/Copyable.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| package io.temporal.common; | ||
|
|
||
| /** | ||
| * Supports isolated in-process value transfer without a serialization round trip. | ||
| * | ||
| * <p>Copies must not share mutable reachable state with the original. Deeply immutable values may | ||
| * return {@code this}. A copy must be non-null and compatible with the original's runtime type. | ||
| * | ||
| * <p>Copying must be side-effect-free, safe for concurrent calls, and deterministically equivalent | ||
| * to a round trip through the configured data converter. The SDK may call it on workflow or | ||
| * activity threads, including during replay. Object identity must not affect workflow behavior. | ||
| * | ||
| * <p>Currently, only local activities use this interface. Input batches use scheduling-time | ||
| * snapshots when every non-null argument is copyable. Compatible requested types use fresh copies; | ||
| * other types use the data converter. Copying is independent of marker options; omitted inputs | ||
| * avoid serialization unless conversion or explicit payload access requires it. Outputs remain | ||
| * serialized for history and deserialized on replay. Other execution paths, including eager | ||
| * workflows and eager activities, do not yet use this interface. | ||
| * | ||
| * @param <T> the type of the copied value. | ||
| */ | ||
| @Experimental | ||
| public interface Copyable<T extends Copyable<T>> { | ||
| /** | ||
| * Returns an independent copy satisfying this interface's contract. | ||
| * | ||
| * @return a non-null copy, or {@code this} for a deeply immutable value. | ||
| */ | ||
| T copy(); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
81 changes: 81 additions & 0 deletions
81
temporal-sdk/src/main/java/io/temporal/internal/activity/ActivityInput.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| package io.temporal.internal.activity; | ||
|
|
||
| import io.temporal.api.common.v1.Payloads; | ||
| import io.temporal.api.workflowservice.v1.PollActivityTaskQueueResponseOrBuilder; | ||
| import io.temporal.common.converter.DataConverter; | ||
| import io.temporal.common.converter.EncodedValues; | ||
| import java.lang.reflect.Type; | ||
| import java.util.Objects; | ||
| import java.util.Optional; | ||
| import javax.annotation.Nullable; | ||
|
|
||
| /** One source of activity arguments: serialized payloads or a captured copyable snapshot. */ | ||
| final class ActivityInput { | ||
| // A cached serialized view of the input, materialized only if needed. | ||
| private @Nullable Optional<Payloads> payloads; | ||
| private final @Nullable LocalActivityArguments snapshot; | ||
|
|
||
| ActivityInput( | ||
| PollActivityTaskQueueResponseOrBuilder response, @Nullable LocalActivityArguments snapshot) { | ||
| this.snapshot = snapshot; | ||
| this.payloads = | ||
| response.hasInput() | ||
| ? Optional.of(response.getInput()) | ||
| : snapshot == null ? Optional.empty() : null; | ||
| } | ||
|
|
||
| Object[] getArguments(Class<?>[] types, Type[] genericTypes, DataConverter converter) { | ||
| if (snapshot != null && snapshot.canCopyTo(types, genericTypes)) { | ||
| return snapshot.copy(); | ||
| } | ||
| return converter.fromPayloads(toPayloads(converter), types, genericTypes); | ||
| } | ||
|
|
||
| private Optional<Payloads> toPayloads(DataConverter converter) { | ||
| if (payloads == null) { | ||
| payloads = snapshot.toPayloads(converter); | ||
| } | ||
| return payloads; | ||
| } | ||
|
|
||
| EncodedValues getEncodedValues(DataConverter converter) { | ||
| if (snapshot == null) { | ||
| return new EncodedValues(payloads, converter); | ||
| } | ||
| return new CopyableEncodedValues(converter); | ||
| } | ||
|
|
||
| /** | ||
| * Reads independent copies without exposing the snapshot used by conversion and serialization. | ||
| */ | ||
| private final class CopyableEncodedValues extends EncodedValues { | ||
| private DataConverter converter; | ||
|
|
||
| private CopyableEncodedValues(DataConverter converter) { | ||
| setDataConverter(converter); | ||
| } | ||
|
|
||
| @Override | ||
| public int getSize() { | ||
| return snapshot.size(); | ||
| } | ||
|
|
||
| @Override | ||
| public Optional<Payloads> toPayloads() { | ||
| return ActivityInput.this.toPayloads(converter); | ||
| } | ||
|
|
||
| @Override | ||
| public void setDataConverter(DataConverter converter) { | ||
| this.converter = Objects.requireNonNull(converter); | ||
| } | ||
|
|
||
| @Override | ||
| public <T> T get(int index, Class<T> type, Type genericType) { | ||
| if (snapshot.canCopyTo(index, type, genericType)) { | ||
| return type.cast(snapshot.copy(index)); | ||
| } | ||
| return converter.fromPayloads(index, toPayloads(), type, genericType); | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
82 changes: 82 additions & 0 deletions
82
temporal-sdk/src/main/java/io/temporal/internal/activity/LocalActivityArguments.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| package io.temporal.internal.activity; | ||
|
|
||
| import io.temporal.api.common.v1.Payloads; | ||
| import io.temporal.common.Copyable; | ||
| import io.temporal.common.converter.DataConverter; | ||
| import io.temporal.internal.common.CopyableUtils; | ||
| import java.lang.reflect.Type; | ||
| import java.util.Optional; | ||
| import javax.annotation.Nullable; | ||
|
|
||
| /** A scheduling-time snapshot that is never exposed to an activity attempt. */ | ||
| public final class LocalActivityArguments { | ||
| private final Object[] snapshot; | ||
|
|
||
| private LocalActivityArguments(Object[] snapshot) { | ||
| this.snapshot = snapshot; | ||
| } | ||
|
|
||
| /** | ||
| * Returns a snapshot only when the entire argument list supports copying. Otherwise, the caller | ||
| * must use the data converter for the complete list to preserve its batch conversion semantics. | ||
| */ | ||
| @Nullable | ||
| public static LocalActivityArguments capture(@Nullable Object[] arguments) { | ||
| if (arguments == null) { | ||
| arguments = new Object[0]; | ||
| } | ||
| for (Object argument : arguments) { | ||
| if (argument != null && !(argument instanceof Copyable)) { | ||
| return null; | ||
| } | ||
| } | ||
| return new LocalActivityArguments(copy(arguments)); | ||
| } | ||
|
|
||
| /** Returns independent arguments for each attempt, including retries after a workflow timer. */ | ||
| public Object[] copy() { | ||
| return copy(snapshot); | ||
| } | ||
|
|
||
| public int size() { | ||
| return snapshot.length; | ||
| } | ||
|
|
||
| public Object copy(int index) { | ||
| return snapshot[index] == null ? null : CopyableUtils.copy(snapshot[index]); | ||
| } | ||
|
|
||
| public boolean canCopyTo(Class<?>[] parameterTypes, Type[] genericTypes) { | ||
| if (parameterTypes.length != snapshot.length || genericTypes.length != snapshot.length) { | ||
| return false; | ||
| } | ||
| for (int i = 0; i < snapshot.length; i++) { | ||
| if (!canCopyTo(i, parameterTypes[i], genericTypes[i])) { | ||
| return false; | ||
| } | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
| /** Erased generic types and different target classes require the converter's interpretation. */ | ||
| public boolean canCopyTo(int index, Class<?> type, Type genericType) { | ||
| return index >= 0 | ||
| && index < snapshot.length | ||
| && type.equals(genericType) | ||
| && type.getTypeParameters().length == 0 | ||
| && (snapshot[index] == null ? !type.isPrimitive() : snapshot[index].getClass() == type); | ||
| } | ||
|
|
||
| /** Preserves conversion behavior for untyped calls with different argument types or counts. */ | ||
| public Optional<Payloads> toPayloads(DataConverter converter) { | ||
| return converter.toPayloads(snapshot); | ||
| } | ||
|
|
||
| private static Object[] copy(Object[] arguments) { | ||
| Object[] copy = new Object[arguments.length]; | ||
| for (int i = 0; i < arguments.length; i++) { | ||
| copy[i] = arguments[i] == null ? null : CopyableUtils.copy(arguments[i]); | ||
| } | ||
| return copy; | ||
| } | ||
| } |
20 changes: 20 additions & 0 deletions
20
temporal-sdk/src/main/java/io/temporal/internal/common/CopyableUtils.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| package io.temporal.internal.common; | ||
|
|
||
| import io.temporal.common.Copyable; | ||
| import java.util.Objects; | ||
|
|
||
| /** Validates copies before they cross the local activity boundary. */ | ||
| public final class CopyableUtils { | ||
| private CopyableUtils() {} | ||
|
|
||
| public static Object copy(Object value) { | ||
| Copyable<?> copy = | ||
| Objects.requireNonNull( | ||
| ((Copyable<?>) value).copy(), "Copyable.copy() must not return null"); | ||
| if (!value.getClass().isInstance(copy)) { | ||
| throw new IllegalArgumentException( | ||
| "Copyable.copy() must return a Copyable compatible with " + value.getClass().getName()); | ||
| } | ||
| return copy; | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.