Is your feature request related to a problem? Please describe.
A PayloadCodec may call an external service during Encode/Decode — examples include the in-tree NewRemotePayloadCodec (HTTP to a sidecar), KMS-backed encryption codecs that call a cloud KMS for data-key operations, and codecs that fetch schemas from a remote registry. Any of these can fail transiently (network blip, throttling, 5xx). Today the SDK has no way to retry a codec call: the first error from any codec immediately aborts the encode/decode and surfaces to user code. Each downstream user has to either fork the codec or wrap retry logic inside their own codec implementation, which duplicates effort and doesn't help the stdlib RemotePayloadCodec, which has no retries today (see converter/codec.go — RemotePayloadCodec.encodeOrDecode does a single pc.options.Client.Do(req) and returns the error directly).
Describe the solution you'd like
A sibling constructor NewCodecDataConverterWithOptions(parent, codecs, CodecDataConverterOptions) mirroring the *WithOptions pattern used in PR #2225 (Serialization Context). The options struct exposes an optional retry policy applied per codec call:
type CodecDataConverterOptions struct {
RetryPolicy *CodecRetryPolicy // nil => no retry (default)
IsRetryable func(error) bool // nil => retry all errors
Context context.Context // nil => context.Background()
}
type CodecRetryPolicy struct {
InitialInterval time.Duration
BackoffCoefficient float64
MaximumInterval time.Duration
MaximumAttempts int
ExpirationInterval time.Duration
}
The existing NewCodecDataConverter(parent, codecs...) stays unchanged and delegates to the new one with a zero-value options struct, preserving backward compatibility (no retries by default).
Retry boundary: each codec's Encode/Decode is retried independently, so a chain of [compress, encrypt] re-encrypts on transient KMS failure without re-compressing (relevant for non-idempotent codecs like authenticated encryption).
Example usage
dc := converter.NewCodecDataConverterWithOptions(
parent,
[]converter.PayloadCodec{kmsCodec},
converter.CodecDataConverterOptions{
RetryPolicy: &converter.CodecRetryPolicy{
InitialInterval: 100 * time.Millisecond,
BackoffCoefficient: 2.0,
MaximumAttempts: 4,
},
IsRetryable: func(err error) bool {
// codec-specific predicate, e.g. detect KMS throttling
return isTransientKMSError(err)
},
Context: ctx,
},
)
Describe alternatives you've considered
- Each codec author implements retry internally. Works, but duplicates effort and doesn't fix
RemotePayloadCodec.
- Exposing
internal/common/backoff publicly. Larger surface; out of proportion to the need.
- Adding
context.Context to PayloadCodec.Encode/Decode. Interface-breaking; separate discussion. The options-struct Context only governs retry sleeps, not the codec call itself.
Additional context
Related prior issues: #2206 (closed by #2228) hit exactly this scenario — a remote codec returned DeadlineExceeded during encodeArgs and, combined with a session activity cancellation, permanently bricked a workflow. PR #2228 fixed the catastrophic side-effect (no more panic; the workflow task fails gracefully and the server retries), but the underlying transient codec failure still kills the in-flight task and forces a full task retry. This proposal is the missing root-cause complement: codecs that opt in recover from a transient blip without burning a workflow task at all.
I'm happy to implement this and send up a PR, but ideally I want to confirm the API shape with maintainers before doing so. Primary open questions remaining are:
- The local
CodecRetryPolicy struct as proposed, or should it thread something else (e.g. temporal.RetryPolicy)? A local struct avoids an import cycle from converter/ back into temporal/.
- Any objection to the per-codec (vs. whole-chain) retry boundary?
- Default
IsRetryable semantics — retry-all (proposed) vs. retry-none (force explicit classification)?
Is your feature request related to a problem? Please describe.
A
PayloadCodecmay call an external service during Encode/Decode — examples include the in-treeNewRemotePayloadCodec(HTTP to a sidecar), KMS-backed encryption codecs that call a cloud KMS for data-key operations, and codecs that fetch schemas from a remote registry. Any of these can fail transiently (network blip, throttling, 5xx). Today the SDK has no way to retry a codec call: the first error from any codec immediately aborts the encode/decode and surfaces to user code. Each downstream user has to either fork the codec or wrap retry logic inside their own codec implementation, which duplicates effort and doesn't help the stdlibRemotePayloadCodec, which has no retries today (seeconverter/codec.go—RemotePayloadCodec.encodeOrDecodedoes a singlepc.options.Client.Do(req)and returns the error directly).Describe the solution you'd like
A sibling constructor
NewCodecDataConverterWithOptions(parent, codecs, CodecDataConverterOptions)mirroring the*WithOptionspattern used in PR #2225 (Serialization Context). The options struct exposes an optional retry policy applied per codec call:The existing
NewCodecDataConverter(parent, codecs...)stays unchanged and delegates to the new one with a zero-value options struct, preserving backward compatibility (no retries by default).Retry boundary: each codec's Encode/Decode is retried independently, so a chain of
[compress, encrypt]re-encrypts on transient KMS failure without re-compressing (relevant for non-idempotent codecs like authenticated encryption).Example usage
Describe alternatives you've considered
RemotePayloadCodec.internal/common/backoffpublicly. Larger surface; out of proportion to the need.context.ContexttoPayloadCodec.Encode/Decode. Interface-breaking; separate discussion. The options-struct Context only governs retry sleeps, not the codec call itself.Additional context
Related prior issues: #2206 (closed by #2228) hit exactly this scenario — a remote codec returned
DeadlineExceededduringencodeArgsand, combined with a session activity cancellation, permanently bricked a workflow. PR #2228 fixed the catastrophic side-effect (no more panic; the workflow task fails gracefully and the server retries), but the underlying transient codec failure still kills the in-flight task and forces a full task retry. This proposal is the missing root-cause complement: codecs that opt in recover from a transient blip without burning a workflow task at all.I'm happy to implement this and send up a PR, but ideally I want to confirm the API shape with maintainers before doing so. Primary open questions remaining are:
CodecRetryPolicystruct as proposed, or should it thread something else (e.g.temporal.RetryPolicy)? A local struct avoids an import cycle fromconverter/back intotemporal/.IsRetryablesemantics — retry-all (proposed) vs. retry-none (force explicit classification)?