Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -12,6 +12,7 @@
import io.temporal.payload.codec.ChainCodec;
import io.temporal.payload.codec.PayloadCodec;
import io.temporal.payload.context.SerializationContext;
import io.temporal.payload.storage.ExternalStorage;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Collections;
Expand Down Expand Up @@ -190,6 +191,12 @@ public CodecDataConverter withContext(@Nonnull SerializationContext context) {
return new CodecDataConverter(dataConverter, chainCodec, encodeFailureAttributes, context);
}

@Override
@Nullable
public ExternalStorage getExternalStorage() {
return dataConverter.getExternalStorage();
}

@Nonnull
@Override
public List<Payload> encode(@Nonnull List<Payload> payloads) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@
import io.temporal.failure.DefaultFailureConverter;
import io.temporal.payload.codec.PayloadCodec;
import io.temporal.payload.context.SerializationContext;
import io.temporal.payload.storage.ExternalStorage;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Optional;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

/**
* Used by the framework to serialize/deserialize method parameters that need to be sent over the
Expand Down Expand Up @@ -202,6 +204,16 @@ default DataConverter withContext(@Nonnull SerializationContext context) {
return this;
}

/**
* External storage offloads large payloads. This should not be used from inside workflow code as
* it performs nondeterministic operations.
*/
@Experimental
@Nullable
default ExternalStorage getExternalStorage() {
return null;
}

/**
* @deprecated use {@link DataConverter#fromPayloads(int, Optional, Class, Type)}. This is an SDK
* implementation detail and never was expected to be exposed to users.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package io.temporal.common.converter;

import com.google.common.base.Preconditions;
import io.temporal.payload.storage.ExternalStorage;
import java.util.*;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

/**
* A {@link DataConverter} that delegates payload conversion to type specific {@link
Expand Down Expand Up @@ -101,4 +103,13 @@ public DefaultDataConverter withFailureConverter(@Nonnull FailureConverter failu
this.failureConverter = Preconditions.checkNotNull(failureConverter, "failureConverter");
return this;
}

/**
* Modifies this {@code DefaultDataConverter} by attaching external storage, used to offload and
* restore large payloads at task and RPC boundaries.
*/
public DefaultDataConverter withExternalStorage(@Nullable ExternalStorage externalStorage) {
this.externalStorage = externalStorage;
return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
import io.temporal.api.common.v1.Payloads;
import io.temporal.api.failure.v1.Failure;
import io.temporal.failure.DefaultFailureConverter;
import io.temporal.internal.payload.storage.ExternalStorageNotConfiguredException;
import io.temporal.internal.payload.storage.ExternalStorageReferences;
import io.temporal.payload.context.SerializationContext;
import io.temporal.payload.storage.ExternalStorage;
import java.lang.reflect.Type;
import java.util.*;
import javax.annotation.Nonnull;
Expand All @@ -22,6 +25,7 @@ class PayloadAndFailureDataConverter implements DataConverter {
volatile List<PayloadConverter> converters;
volatile Map<String, PayloadConverter> convertersMap;
volatile FailureConverter failureConverter;
volatile @Nullable ExternalStorage externalStorage;
private final @Nullable SerializationContext serializationContext;

public PayloadAndFailureDataConverter(@Nonnull List<PayloadConverter> converters) {
Expand Down Expand Up @@ -71,6 +75,10 @@ public <T> T fromPayload(Payload payload, Class<T> valueClass, Type valueType)
return (T) new RawValue(payload);
}

if (ExternalStorageReferences.isReference(payload)) {
throw new ExternalStorageNotConfiguredException();

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.

IIUC, this looks to be a DataConverter, which means it runs within the workflow code context. This means that this exception is handleable by user code. I think we need to move this to somewhere before the workflow code executes so we can fail the workflow task without allowing the user code to compensate.

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.

The context here is that if we missed integrating external storage into some piece of the SDK or a new feature was added that forgot to integrate external storage, this would catch it and throw a not configured error. It's purely a defensive fallback to make sure that SDK bugs don't appear as "your payloads can't be decoded/transformed" errors.

I do think ExternalStorageNotConfiguredException is probably the wrong error to be throwing here because it's likely that it was configured right and the sdk just isn't using external storage. Should this be a "some feature you are using has not integrated external storage. please file a bug report" type thing?

}

try {
String encoding =
payload.getMetadataOrThrow(EncodingKeys.METADATA_ENCODING_KEY).toString(UTF_8);
Expand Down Expand Up @@ -143,9 +151,18 @@ public Failure exceptionToFailure(@Nonnull Throwable throwable) {
.exceptionToFailure(throwable, this);
}

@Override
@Nullable
public ExternalStorage getExternalStorage() {
return externalStorage;
}

@Override
public @Nonnull DataConverter withContext(@Nonnull SerializationContext context) {
return new PayloadAndFailureDataConverter(converters, convertersMap, failureConverter, context);
PayloadAndFailureDataConverter copy =
new PayloadAndFailureDataConverter(converters, convertersMap, failureConverter, context);
copy.externalStorage = this.externalStorage;
return copy;
}

static Map<String, PayloadConverter> createConvertersMap(List<PayloadConverter> converters) {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.temporal.internal.payload.storage;

import io.temporal.common.converter.DataConverterException;

/**
* Signals that an external storage reference reached a data converter without storage configured.
* Logs a TMPRL1105 error.
*/
public final class ExternalStorageNotConfiguredException extends DataConverterException {
public ExternalStorageNotConfiguredException() {
super(
"[TMPRL1105] Encountered an external-storage reference payload but external storage is not "
+ "configured. Configure WorkflowClientOptions.Builder.setExternalStorage(...) with a "
+ "driver able to retrieve it.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import io.temporal.common.CancellationToken;
import io.temporal.internal.common.ListUtils;
import io.temporal.internal.concurrent.structured.TaskScope;
import io.temporal.payload.storage.ExternalStorageOptions;
import io.temporal.payload.storage.ExternalStorage;
import io.temporal.payload.storage.StorageDriver;
import io.temporal.payload.storage.StorageDriverClaim;
import io.temporal.payload.storage.StorageDriverRetrieveContext;
Expand All @@ -30,7 +30,7 @@ final class ExternalStoragePayloadTransformer {
private final StorageDriverSelector selector;
private final int payloadSizeThreshold;

static ExternalStoragePayloadTransformer fromOptions(ExternalStorageOptions options) {
static ExternalStoragePayloadTransformer fromOptions(ExternalStorage options) {
Map<String, StorageDriver> driversByName = new LinkedHashMap<>();
for (StorageDriver driver : options.getDrivers()) {
driversByName.put(driver.getName(), driver);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

final class ExternalStorageReferences {
public final class ExternalStorageReferences {
private static final String ENCODING_PROTOBUF_JSON = "json/protobuf";
private static final String REFERENCE_MESSAGE_TYPE =
ExternalStorageReference.getDescriptor().getFullName();
Expand Down Expand Up @@ -64,8 +64,7 @@ static Payload toReferencePayload(
* producer that omits it still yields a readable reference.
*/
static @Nullable ParsedReference tryParseReference(@Nonnull Payload payload) {
if (!hasMetadata(payload, EncodingKeys.METADATA_ENCODING_KEY, ENCODING_PROTOBUF_JSON)
|| !hasMetadata(payload, EncodingKeys.METADATA_MESSAGE_TYPE_KEY, REFERENCE_MESSAGE_TYPE)) {
if (!isReference(payload)) {
return null;
}
ExternalStorageReference.Builder builder = ExternalStorageReference.newBuilder();
Expand All @@ -79,6 +78,12 @@ static Payload toReferencePayload(
reference.getDriverName(), new StorageDriverClaim(reference.getClaimDataMap()));
}

/** True if {@code payload} has an external storage reference encoding and message type. */
public static boolean isReference(Payload payload) {
return hasMetadata(payload, EncodingKeys.METADATA_ENCODING_KEY, ENCODING_PROTOBUF_JSON)
&& hasMetadata(payload, EncodingKeys.METADATA_MESSAGE_TYPE_KEY, REFERENCE_MESSAGE_TYPE);
}

private static boolean hasMetadata(Payload payload, String key, String expected) {
ByteString value = payload.getMetadataMap().get(key);
return value != null && expected.equals(value.toStringUtf8());
Expand Down
Loading
Loading