Rename task Executor interfaces to Handler and unify discard into SideEffectTaskHandler (#9655)

Rename SideEffectTaskExecutor to SideEffectTaskHandler and
PureTaskExecutor to PureTaskHandler across the CHASM framework and all
library implementations (activity, callback, nexusoperation, scheduler).

Merge the separate SideEffectTaskDiscarder interface into
SideEffectTaskHandler as a required Discard method. Introduce
SideEffectTaskHandlerBase[T] which provides a default Discard returning
ErrTaskDiscarded, and PureTaskHandlerBase with an unexported marker
method — both must be embedded by implementations.

This eliminates HasDiscardHandler() from RegistrableTask and the
conditional nil check in ExecuteSideEffectDiscardTask, replacing it with
eager validation that rejects pure tasks at the call site. The discard
function is now always present on side-effect tasks, simplifying the
standby task execution path.

Also rename executor source files to tasks files in the callback and
nexusoperation packages and update receiver names from `e` to `h`
throughout.

## Why?

- The split interfaces required duplicate registration but in practice
both interfaces were implemented by a single struct.
- A base implementation is great for future proofing when more optional
methods are added.
This commit is contained in:
Roey Berman
2026-03-25 15:42:28 -07:00
committed by GitHub
parent 24cea9d4f3
commit b781c57a13
46 changed files with 656 additions and 654 deletions

View File

@@ -50,7 +50,7 @@ type MutableContext interface {
Context
// AddTask adds a task to be emitted as part of the current transaction.
// The task is associated with the given component and will be invoked via the registered executor for the given task
// The task is associated with the given component and will be invoked via the registered handler for the given task
// referencing the component.
AddTask(Component, TaskAttributes, any)

View File

@@ -12,23 +12,24 @@ import (
"go.uber.org/fx"
)
type activityDispatchTaskExecutorOptions struct {
type activityDispatchTaskHandlerOptions struct {
fx.In
MatchingClient resource.MatchingClient
}
type activityDispatchTaskExecutor struct {
opts activityDispatchTaskExecutorOptions
type activityDispatchTaskHandler struct {
chasm.SideEffectTaskHandlerBase[*activitypb.ActivityDispatchTask]
opts activityDispatchTaskHandlerOptions
}
func newActivityDispatchTaskExecutor(opts activityDispatchTaskExecutorOptions) *activityDispatchTaskExecutor {
return &activityDispatchTaskExecutor{
opts,
func newActivityDispatchTaskHandler(opts activityDispatchTaskHandlerOptions) *activityDispatchTaskHandler {
return &activityDispatchTaskHandler{
opts: opts,
}
}
func (e *activityDispatchTaskExecutor) Validate(
func (h *activityDispatchTaskHandler) Validate(
ctx chasm.Context,
activity *Activity,
_ chasm.TaskAttributes,
@@ -39,27 +40,27 @@ func (e *activityDispatchTaskExecutor) Validate(
task.Stamp == activity.LastAttempt.Get(ctx).GetStamp()), nil
}
func (e *activityDispatchTaskExecutor) Execute(
func (h *activityDispatchTaskHandler) Execute(
ctx context.Context,
activityRef chasm.ComponentRef,
_ chasm.TaskAttributes,
_ *activitypb.ActivityDispatchTask,
) error {
return e.pushToMatching(ctx, activityRef)
return h.pushToMatching(ctx, activityRef)
}
// Discard spills the task to matching instead of silently discarding it on standby clusters when the activity
// dispatch task has been pending past the discard delay.
func (e *activityDispatchTaskExecutor) Discard(
func (h *activityDispatchTaskHandler) Discard(
ctx context.Context,
activityRef chasm.ComponentRef,
_ chasm.TaskAttributes,
_ *activitypb.ActivityDispatchTask,
) error {
return e.pushToMatching(ctx, activityRef)
return h.pushToMatching(ctx, activityRef)
}
func (e *activityDispatchTaskExecutor) pushToMatching(
func (h *activityDispatchTaskHandler) pushToMatching(
ctx context.Context,
activityRef chasm.ComponentRef,
) error {
@@ -73,19 +74,20 @@ func (e *activityDispatchTaskExecutor) pushToMatching(
return err
}
_, err = e.opts.MatchingClient.AddActivityTask(ctx, request)
_, err = h.opts.MatchingClient.AddActivityTask(ctx, request)
return err
}
type scheduleToStartTimeoutTaskExecutor struct {
type scheduleToStartTimeoutTaskHandler struct {
chasm.PureTaskHandlerBase
}
func newScheduleToStartTimeoutTaskExecutor() *scheduleToStartTimeoutTaskExecutor {
return &scheduleToStartTimeoutTaskExecutor{}
func newScheduleToStartTimeoutTaskHandler() *scheduleToStartTimeoutTaskHandler {
return &scheduleToStartTimeoutTaskHandler{}
}
func (e *scheduleToStartTimeoutTaskExecutor) Validate(
func (h *scheduleToStartTimeoutTaskHandler) Validate(
ctx chasm.Context,
activity *Activity,
_ chasm.TaskAttributes,
@@ -95,7 +97,7 @@ func (e *scheduleToStartTimeoutTaskExecutor) Validate(
task.Stamp == activity.LastAttempt.Get(ctx).GetStamp()), nil
}
func (e *scheduleToStartTimeoutTaskExecutor) Execute(
func (h *scheduleToStartTimeoutTaskHandler) Execute(
ctx chasm.MutableContext,
activity *Activity,
_ chasm.TaskAttributes,
@@ -115,13 +117,13 @@ func (e *scheduleToStartTimeoutTaskExecutor) Execute(
return TransitionTimedOut.Apply(activity, ctx, event)
}
type scheduleToCloseTimeoutTaskExecutor struct{}
type scheduleToCloseTimeoutTaskHandler struct{ chasm.PureTaskHandlerBase }
func newScheduleToCloseTimeoutTaskExecutor() *scheduleToCloseTimeoutTaskExecutor {
return &scheduleToCloseTimeoutTaskExecutor{}
func newScheduleToCloseTimeoutTaskHandler() *scheduleToCloseTimeoutTaskHandler {
return &scheduleToCloseTimeoutTaskHandler{}
}
func (e *scheduleToCloseTimeoutTaskExecutor) Validate(
func (h *scheduleToCloseTimeoutTaskHandler) Validate(
_ chasm.Context,
activity *Activity,
_ chasm.TaskAttributes,
@@ -130,7 +132,7 @@ func (e *scheduleToCloseTimeoutTaskExecutor) Validate(
return TransitionTimedOut.Possible(activity), nil
}
func (e *scheduleToCloseTimeoutTaskExecutor) Execute(
func (h *scheduleToCloseTimeoutTaskHandler) Execute(
ctx chasm.MutableContext,
activity *Activity,
_ chasm.TaskAttributes,
@@ -149,13 +151,13 @@ func (e *scheduleToCloseTimeoutTaskExecutor) Execute(
return TransitionTimedOut.Apply(activity, ctx, event)
}
type startToCloseTimeoutTaskExecutor struct{}
type startToCloseTimeoutTaskHandler struct{ chasm.PureTaskHandlerBase }
func newStartToCloseTimeoutTaskExecutor() *startToCloseTimeoutTaskExecutor {
return &startToCloseTimeoutTaskExecutor{}
func newStartToCloseTimeoutTaskHandler() *startToCloseTimeoutTaskHandler {
return &startToCloseTimeoutTaskHandler{}
}
func (e *startToCloseTimeoutTaskExecutor) Validate(
func (h *startToCloseTimeoutTaskHandler) Validate(
ctx chasm.Context,
activity *Activity,
_ chasm.TaskAttributes,
@@ -168,7 +170,7 @@ func (e *startToCloseTimeoutTaskExecutor) Validate(
// Execute executes a StartToCloseTimeoutTask. It fails the attempt, leading to retry or activity
// failure.
func (e *startToCloseTimeoutTaskExecutor) Execute(
func (h *startToCloseTimeoutTaskHandler) Execute(
ctx chasm.MutableContext,
activity *Activity,
_ chasm.TaskAttributes,
@@ -198,14 +200,14 @@ func (e *startToCloseTimeoutTaskExecutor) Execute(
}
// HeartbeatTimeoutTask is a pure task that enforces heartbeat timeouts.
type heartbeatTimeoutTaskExecutor struct{}
type heartbeatTimeoutTaskHandler struct{ chasm.PureTaskHandlerBase }
func newHeartbeatTimeoutTaskExecutor() *heartbeatTimeoutTaskExecutor {
return &heartbeatTimeoutTaskExecutor{}
func newHeartbeatTimeoutTaskHandler() *heartbeatTimeoutTaskHandler {
return &heartbeatTimeoutTaskHandler{}
}
// Validate validates a HeartbeatTimeoutTask.
func (e *heartbeatTimeoutTaskExecutor) Validate(
func (h *heartbeatTimeoutTaskHandler) Validate(
ctx chasm.Context,
activity *Activity,
taskAttrs chasm.TaskAttributes,
@@ -247,7 +249,7 @@ func (e *heartbeatTimeoutTaskExecutor) Validate(
// Execute executes a HeartbeatTimeoutTask. It fails the attempt, leading to retry or activity
// failure.
func (e *heartbeatTimeoutTaskExecutor) Execute(
func (h *heartbeatTimeoutTaskHandler) Execute(
ctx chasm.MutableContext,
activity *Activity,
_ chasm.TaskAttributes,

View File

@@ -11,11 +11,11 @@ var HistoryModule = fx.Module(
"activity-history",
fx.Provide(
ConfigProvider,
newActivityDispatchTaskExecutor,
newScheduleToStartTimeoutTaskExecutor,
newScheduleToCloseTimeoutTaskExecutor,
newStartToCloseTimeoutTaskExecutor,
newHeartbeatTimeoutTaskExecutor,
newActivityDispatchTaskHandler,
newScheduleToStartTimeoutTaskHandler,
newScheduleToCloseTimeoutTaskHandler,
newStartToCloseTimeoutTaskHandler,
newHeartbeatTimeoutTaskHandler,
newHandler,
newLibrary,
),
@@ -33,7 +33,7 @@ var FrontendModule = fx.Module(
fx.Provide(newComponentOnlyLibrary),
fx.Invoke(func(l *componentOnlyLibrary, registry *chasm.Registry) error {
// Frontend needs to register the component in order to serialize ComponentRefs, but doesn't
// need task executors.
// need task handlers.
return registry.Register(l)
}),
)

View File

@@ -77,32 +77,32 @@ func (l *componentOnlyLibrary) Components() []*chasm.RegistrableComponent {
type library struct {
componentOnlyLibrary
handler *handler
activityDispatchTaskExecutor *activityDispatchTaskExecutor
scheduleToStartTimeoutTaskExecutor *scheduleToStartTimeoutTaskExecutor
scheduleToCloseTimeoutTaskExecutor *scheduleToCloseTimeoutTaskExecutor
startToCloseTimeoutTaskExecutor *startToCloseTimeoutTaskExecutor
heartbeatTimeoutTaskExecutor *heartbeatTimeoutTaskExecutor
handler *handler
activityDispatchTaskHandler *activityDispatchTaskHandler
scheduleToStartTimeoutTaskHandler *scheduleToStartTimeoutTaskHandler
scheduleToCloseTimeoutTaskHandler *scheduleToCloseTimeoutTaskHandler
startToCloseTimeoutTaskHandler *startToCloseTimeoutTaskHandler
heartbeatTimeoutTaskHandler *heartbeatTimeoutTaskHandler
}
func newLibrary(
handler *handler,
activityDispatchTaskExecutor *activityDispatchTaskExecutor,
scheduleToStartTimeoutTaskExecutor *scheduleToStartTimeoutTaskExecutor,
scheduleToCloseTimeoutTaskExecutor *scheduleToCloseTimeoutTaskExecutor,
startToCloseTimeoutTaskExecutor *startToCloseTimeoutTaskExecutor,
heartbeatTimeoutTaskExecutor *heartbeatTimeoutTaskExecutor,
activityDispatchTaskHandler *activityDispatchTaskHandler,
scheduleToStartTimeoutTaskHandler *scheduleToStartTimeoutTaskHandler,
scheduleToCloseTimeoutTaskHandler *scheduleToCloseTimeoutTaskHandler,
startToCloseTimeoutTaskHandler *startToCloseTimeoutTaskHandler,
heartbeatTimeoutTaskHandler *heartbeatTimeoutTaskHandler,
config *Config,
namespaceRegistry namespace.Registry,
) *library {
return &library{
componentOnlyLibrary: *newComponentOnlyLibrary(config, namespaceRegistry),
handler: handler,
activityDispatchTaskExecutor: activityDispatchTaskExecutor,
scheduleToStartTimeoutTaskExecutor: scheduleToStartTimeoutTaskExecutor,
scheduleToCloseTimeoutTaskExecutor: scheduleToCloseTimeoutTaskExecutor,
startToCloseTimeoutTaskExecutor: startToCloseTimeoutTaskExecutor,
heartbeatTimeoutTaskExecutor: heartbeatTimeoutTaskExecutor,
componentOnlyLibrary: *newComponentOnlyLibrary(config, namespaceRegistry),
handler: handler,
activityDispatchTaskHandler: activityDispatchTaskHandler,
scheduleToStartTimeoutTaskHandler: scheduleToStartTimeoutTaskHandler,
scheduleToCloseTimeoutTaskHandler: scheduleToCloseTimeoutTaskHandler,
startToCloseTimeoutTaskHandler: startToCloseTimeoutTaskHandler,
heartbeatTimeoutTaskHandler: heartbeatTimeoutTaskHandler,
}
}
@@ -114,28 +114,23 @@ func (l *library) Tasks() []*chasm.RegistrableTask {
return []*chasm.RegistrableTask{
chasm.NewRegistrableSideEffectTask(
"dispatch",
l.activityDispatchTaskExecutor,
l.activityDispatchTaskExecutor,
l.activityDispatchTaskHandler,
),
chasm.NewRegistrablePureTask(
"scheduleToStartTimer",
l.scheduleToStartTimeoutTaskExecutor,
l.scheduleToStartTimeoutTaskExecutor,
l.scheduleToStartTimeoutTaskHandler,
),
chasm.NewRegistrablePureTask(
"scheduleToCloseTimer",
l.scheduleToCloseTimeoutTaskExecutor,
l.scheduleToCloseTimeoutTaskExecutor,
l.scheduleToCloseTimeoutTaskHandler,
),
chasm.NewRegistrablePureTask(
"startToCloseTimer",
l.startToCloseTimeoutTaskExecutor,
l.startToCloseTimeoutTaskExecutor,
l.startToCloseTimeoutTaskHandler,
),
chasm.NewRegistrablePureTask(
"heartbeatTimer",
l.heartbeatTimeoutTaskExecutor,
l.heartbeatTimeoutTaskExecutor,
l.heartbeatTimeoutTaskHandler,
),
}
}

View File

@@ -52,7 +52,7 @@ func logInternalError(logger log.Logger, internalMsg string, internalErr error)
func (c chasmInvocation) Invoke(
ctx context.Context,
ns *namespace.Namespace,
e InvocationTaskExecutor,
h InvocationTaskHandler,
task *callbackspb.InvocationTask,
taskAttr chasm.TaskAttributes,
) invocationResult {
@@ -64,30 +64,30 @@ func (c chasmInvocation) Invoke(
// Get back the base64-encoded ComponentRef from the header.
encodedRef := header.Get(commonnexus.CallbackTokenHeader)
if encodedRef == "" {
return invocationResultFail{logInternalError(e.logger, "callback missing token", nil)}
return invocationResultFail{logInternalError(h.logger, "callback missing token", nil)}
}
decodedRef, err := base64.RawURLEncoding.DecodeString(encodedRef)
if err != nil {
return invocationResultFail{logInternalError(e.logger, "failed to decode CHASM ComponentRef", err)}
return invocationResultFail{logInternalError(h.logger, "failed to decode CHASM ComponentRef", err)}
}
// Validate that the bytes are a valid ChasmComponentRef
ref := &persistencespb.ChasmComponentRef{}
err = proto.Unmarshal(decodedRef, ref)
if err != nil {
return invocationResultFail{logInternalError(e.logger, "failed to unmarshal CHASM ComponentRef", err)}
return invocationResultFail{logInternalError(h.logger, "failed to unmarshal CHASM ComponentRef", err)}
}
request, err := c.getHistoryRequest(decodedRef)
if err != nil {
return invocationResultFail{logInternalError(e.logger, "failed to build history request", err)}
return invocationResultFail{logInternalError(h.logger, "failed to build history request", err)}
}
// RPC to History for cross-shard completion delivery.
_, err = e.historyClient.CompleteNexusOperationChasm(ctx, request)
_, err = h.historyClient.CompleteNexusOperationChasm(ctx, request)
if err != nil {
msg := logInternalError(e.logger, "failed to complete Nexus operation", err)
msg := logInternalError(h.logger, "failed to complete Nexus operation", err)
if isRetryableRPCResponse(err) {
return invocationResultRetry{err: msg}
}

View File

@@ -57,8 +57,8 @@ var Module = fx.Module(
"chasm.lib.callback",
fx.Provide(configProvider),
fx.Provide(httpCallerProviderProvider),
fx.Provide(NewInvocationTaskExecutor),
fx.Provide(NewBackoffTaskExecutor),
fx.Provide(NewInvocationTaskHandler),
fx.Provide(NewBackoffTaskHandler),
fx.Provide(newLibrary),
fx.Invoke(register),
)

View File

@@ -9,18 +9,18 @@ type (
Library struct {
chasm.UnimplementedLibrary
InvocationTaskExecutor *InvocationTaskExecutor
BackoffTaskExecutor *BackoffTaskExecutor
InvocationTaskHandler *InvocationTaskHandler
BackoffTaskHandler *BackoffTaskHandler
}
)
func newLibrary(
InvocationTaskExecutor *InvocationTaskExecutor,
BackoffTaskExecutor *BackoffTaskExecutor,
InvocationTaskHandler *InvocationTaskHandler,
BackoffTaskHandler *BackoffTaskHandler,
) *Library {
return &Library{
InvocationTaskExecutor: InvocationTaskExecutor,
BackoffTaskExecutor: BackoffTaskExecutor,
InvocationTaskHandler: InvocationTaskHandler,
BackoffTaskHandler: BackoffTaskHandler,
}
}
@@ -41,13 +41,11 @@ func (l *Library) Tasks() []*chasm.RegistrableTask {
return []*chasm.RegistrableTask{
chasm.NewRegistrableSideEffectTask(
"invoke",
l.InvocationTaskExecutor,
l.InvocationTaskExecutor,
l.InvocationTaskHandler,
),
chasm.NewRegistrablePureTask(
"backoff",
l.BackoffTaskExecutor,
l.BackoffTaskExecutor,
l.BackoffTaskHandler,
),
}
}

View File

@@ -42,12 +42,12 @@ func (n nexusInvocation) WrapError(result invocationResult, err error) error {
func (n nexusInvocation) Invoke(
ctx context.Context,
ns *namespace.Namespace,
e InvocationTaskExecutor,
h InvocationTaskHandler,
task *callbackspb.InvocationTask,
taskAttr chasm.TaskAttributes,
) invocationResult {
if e.httpTraceProvider != nil {
traceLogger := log.With(e.logger,
if h.httpTraceProvider != nil {
traceLogger := log.With(h.logger,
tag.WorkflowNamespace(ns.Name().String()),
tag.Operation("CompleteNexusOperation"),
tag.String("destination", taskAttr.Destination),
@@ -56,13 +56,13 @@ func (n nexusInvocation) Invoke(
tag.AttemptStart(time.Now().UTC()),
tag.Attempt(n.attempt),
)
if trace := e.httpTraceProvider.NewTrace(n.attempt, traceLogger); trace != nil {
if trace := h.httpTraceProvider.NewTrace(n.attempt, traceLogger); trace != nil {
ctx = httptrace.WithClientTrace(ctx, trace)
}
}
client := nexusrpc.NewCompletionHTTPClient(nexusrpc.CompletionHTTPClientOptions{
HTTPCaller: e.httpCallerProvider(queuescommon.NamespaceIDAndDestination{
HTTPCaller: h.httpCallerProvider(queuescommon.NamespaceIDAndDestination{
NamespaceID: ns.ID().String(),
Destination: taskAttr.Destination,
}),
@@ -77,12 +77,12 @@ func (n nexusInvocation) Invoke(
namespaceTag := metrics.NamespaceTag(ns.Name().String())
destTag := metrics.DestinationTag(taskAttr.Destination)
outcomeTag := metrics.OutcomeTag(outcomeTag(ctx, err))
e.metricsHandler.Counter(RequestCounter.Name()).Record(1, namespaceTag, destTag, outcomeTag)
e.metricsHandler.Timer(RequestLatencyHistogram.Name()).Record(time.Since(startTime), namespaceTag, destTag, outcomeTag)
h.metricsHandler.Counter(RequestCounter.Name()).Record(1, namespaceTag, destTag, outcomeTag)
h.metricsHandler.Timer(RequestLatencyHistogram.Name()).Record(time.Since(startTime), namespaceTag, destTag, outcomeTag)
if err != nil {
retryable := isRetryableCallError(err)
e.logger.Error("Callback request failed", tag.Error(err), tag.Bool("retryable", retryable))
h.logger.Error("Callback request failed", tag.Error(err), tag.Bool("retryable", retryable))
if retryable {
return invocationResultRetry{err}
}

View File

@@ -91,7 +91,7 @@ func routeRequest(
if r.URL.String() == commonnexus.SystemCallbackURL {
return routeSystemCallbackRequest(r, clusterMetadata, namespaceRegistry, httpClientCache, callbackTokenGenerator, localClient, logger)
}
// This source header is populated in nexusoperations/executors (via the ClientProvider) for worker targets
// This source header is populated in nexusoperations/tasks (via the ClientProvider) for worker targets
// if this header is not populated then we assume it's an external target.
if r.Header == nil || r.Header.Get(callbackSourceHeader) == "" {
return defaultClient.Do(r)

View File

@@ -20,8 +20,8 @@ import (
type HTTPCaller func(*http.Request) (*http.Response, error)
type HTTPCallerProvider func(common.NamespaceIDAndDestination) HTTPCaller
func NewInvocationTaskExecutor(opts InvocationTaskExecutorOptions) *InvocationTaskExecutor {
return &InvocationTaskExecutor{
func NewInvocationTaskHandler(opts InvocationTaskHandlerOptions) *InvocationTaskHandler {
return &InvocationTaskHandler{
config: opts.Config,
namespaceRegistry: opts.NamespaceRegistry,
metricsHandler: opts.MetricsHandler,
@@ -32,7 +32,7 @@ func NewInvocationTaskExecutor(opts InvocationTaskExecutorOptions) *InvocationTa
}
}
type InvocationTaskExecutorOptions struct {
type InvocationTaskHandlerOptions struct {
fx.In
Config *Config
@@ -44,7 +44,8 @@ type InvocationTaskExecutorOptions struct {
HistoryClient resource.HistoryClient
}
type InvocationTaskExecutor struct {
type InvocationTaskHandler struct {
chasm.SideEffectTaskHandlerBase[*callbackspb.InvocationTask]
config *Config
namespaceRegistry namespace.Registry
metricsHandler metrics.Handler
@@ -54,15 +55,15 @@ type InvocationTaskExecutor struct {
historyClient resource.HistoryClient
}
func (e InvocationTaskExecutor) Execute(ctx context.Context, ref chasm.ComponentRef, attrs chasm.TaskAttributes, task *callbackspb.InvocationTask) error {
return e.Invoke(ctx, ref, attrs, task)
func (h InvocationTaskHandler) Execute(ctx context.Context, ref chasm.ComponentRef, attrs chasm.TaskAttributes, task *callbackspb.InvocationTask) error {
return h.Invoke(ctx, ref, attrs, task)
}
func (e InvocationTaskExecutor) Validate(ctx chasm.Context, cb *Callback, attrs chasm.TaskAttributes, task *callbackspb.InvocationTask) (bool, error) {
func (h InvocationTaskHandler) Validate(ctx chasm.Context, cb *Callback, attrs chasm.TaskAttributes, task *callbackspb.InvocationTask) (bool, error) {
return cb.Attempt == task.Attempt && cb.Status == callbackspb.CALLBACK_STATUS_SCHEDULED, nil
}
// invocationResult is a marker for the callbackInvokable.Invoke result to indicate to the executor how to handle the
// invocationResult is a marker for the callbackInvokable.Invoke result to indicate to the handler how to handle the
// invocation outcome.
type invocationResult interface {
// A marker for all possible implementations.
@@ -103,19 +104,19 @@ func (r invocationResultRetry) error() error {
type callbackInvokable interface {
// Invoke executes the callback logic and returns the invocation result.
Invoke(ctx context.Context, ns *namespace.Namespace, e InvocationTaskExecutor, task *callbackspb.InvocationTask, taskAttr chasm.TaskAttributes) invocationResult
// WrapError provides each variant the opportunity to wrap the error returned by the task executor for, e.g. to
Invoke(ctx context.Context, ns *namespace.Namespace, h InvocationTaskHandler, task *callbackspb.InvocationTask, taskAttr chasm.TaskAttributes) invocationResult
// WrapError provides each variant the opportunity to wrap the error returned by the task handler for, e.g. to
// trigger the circuit breaker.
WrapError(result invocationResult, err error) error
}
func (e InvocationTaskExecutor) Invoke(
func (h InvocationTaskHandler) Invoke(
ctx context.Context,
ref chasm.ComponentRef,
taskAttr chasm.TaskAttributes,
task *callbackspb.InvocationTask,
) error {
ns, err := e.namespaceRegistry.GetNamespaceByID(namespace.ID(ref.NamespaceID))
ns, err := h.namespaceRegistry.GetNamespaceByID(namespace.ID(ref.NamespaceID))
if err != nil {
return fmt.Errorf("failed to get namespace by ID: %w", err)
}
@@ -132,30 +133,31 @@ func (e InvocationTaskExecutor) Invoke(
callCtx, cancel := context.WithTimeout(
ctx,
e.config.RequestTimeout(ns.Name().String(), taskAttr.Destination),
h.config.RequestTimeout(ns.Name().String(), taskAttr.Destination),
)
defer cancel()
result := invokable.Invoke(callCtx, ns, e, task, taskAttr)
result := invokable.Invoke(callCtx, ns, h, task, taskAttr)
_, _, saveErr := chasm.UpdateComponent(
ctx,
ref,
(*Callback).saveResult,
saveResultInput{
result: result,
retryPolicy: e.config.RetryPolicy(),
retryPolicy: h.config.RetryPolicy(),
},
)
return invokable.WrapError(result, saveErr)
}
type BackoffTaskExecutor struct {
type BackoffTaskHandler struct {
chasm.PureTaskHandlerBase
config *Config
metricsHandler metrics.Handler
logger log.Logger
}
type BackoffTaskExecutorOptions struct {
type BackoffTaskHandlerOptions struct {
fx.In
Config *Config
@@ -163,8 +165,8 @@ type BackoffTaskExecutorOptions struct {
Logger log.Logger
}
func NewBackoffTaskExecutor(opts BackoffTaskExecutorOptions) *BackoffTaskExecutor {
return &BackoffTaskExecutor{
func NewBackoffTaskHandler(opts BackoffTaskHandlerOptions) *BackoffTaskHandler {
return &BackoffTaskHandler{
config: opts.Config,
metricsHandler: opts.MetricsHandler,
logger: opts.Logger,
@@ -173,7 +175,7 @@ func NewBackoffTaskExecutor(opts BackoffTaskExecutorOptions) *BackoffTaskExecuto
// Execute transitions the callback from BACKING_OFF to SCHEDULED state
// and generates an InvocationTask for the next attempt.
func (e *BackoffTaskExecutor) Execute(
func (h *BackoffTaskHandler) Execute(
ctx chasm.MutableContext,
callback *Callback,
taskAttrs chasm.TaskAttributes,
@@ -182,7 +184,7 @@ func (e *BackoffTaskExecutor) Execute(
return TransitionRescheduled.Apply(callback, ctx, EventRescheduled{})
}
func (e *BackoffTaskExecutor) Validate(
func (h *BackoffTaskHandler) Validate(
ctx chasm.Context,
callback *Callback,
taskAttr chasm.TaskAttributes,

View File

@@ -68,7 +68,7 @@ func (l *mockNexusCompletionGetterLibrary) Components() []*chasm.RegistrableComp
}
}
// Test the full executeInvocationTask flow with direct executor calls
// Test the full executeInvocationTask flow with direct handler calls
func TestExecuteInvocationTaskNexus_Outcomes(t *testing.T) {
cases := []struct {
name string
@@ -161,13 +161,13 @@ func TestExecuteInvocationTaskNexus_Outcomes(t *testing.T) {
timeSource := clock.NewEventTimeSource()
timeSource.Update(time.Now())
// Create task executor with mock namespace registry
// Create task handler with mock namespace registry
nsRegistry := namespace.NewMockRegistry(ctrl)
nsRegistry.EXPECT().GetNamespaceByID(gomock.Any()).Return(ns, nil)
// Create mock engine
mockEngine := chasm.NewMockEngine(ctrl)
executor := &InvocationTaskExecutor{
handler := &InvocationTaskHandler{
config: &Config{
RequestTimeout: dynamicconfig.GetDurationPropertyFnFilteredByDestination(time.Second),
RetryPolicy: func() backoff.RetryPolicy {
@@ -184,7 +184,7 @@ func TestExecuteInvocationTaskNexus_Outcomes(t *testing.T) {
chasmRegistry := chasm.NewRegistry(logger)
err = chasmRegistry.Register(&Library{
InvocationTaskExecutor: executor,
InvocationTaskHandler: handler,
})
require.NoError(t, err)
err = chasmRegistry.Register(&mockNexusCompletionGetterLibrary{})
@@ -224,7 +224,7 @@ func TestExecuteInvocationTaskNexus_Outcomes(t *testing.T) {
_, err = root.CloseTransaction()
require.NoError(t, err)
// Setup engine expectations to directly call executor logic with MockMutableContext
// Setup engine expectations to directly call handler logic with MockMutableContext
mockEngine.EXPECT().ReadComponent(
gomock.Any(),
gomock.Any(),
@@ -269,7 +269,7 @@ func TestExecuteInvocationTaskNexus_Outcomes(t *testing.T) {
// Execute with engine context
engineCtx := chasm.NewEngineContext(context.Background(), mockEngine)
err = executor.Invoke(
err = handler.Invoke(
engineCtx,
ref,
chasm.TaskAttributes{Destination: "http://localhost"},
@@ -321,7 +321,7 @@ func TestProcessBackoffTask(t *testing.T) {
},
}
executor := BackoffTaskExecutor{
handler := BackoffTaskHandler{
config: &Config{
RequestTimeout: dynamicconfig.GetDurationPropertyFnFilteredByDestination(time.Second),
RetryPolicy: func() backoff.RetryPolicy {
@@ -334,7 +334,7 @@ func TestProcessBackoffTask(t *testing.T) {
// Execute the backoff task
task := &callbackspb.BackoffTask{Attempt: 1}
attrs := chasm.TaskAttributes{Destination: "http://localhost"}
err := executor.Execute(mockCtx, callback, attrs, task)
err := handler.Execute(mockCtx, callback, attrs, task)
// Verify no error
require.NoError(t, err)
@@ -549,7 +549,7 @@ func TestExecuteInvocationTaskChasm_Outcomes(t *testing.T) {
// Create mock engine and setup expectations
mockEngine := chasm.NewMockEngine(ctrl)
executor := &InvocationTaskExecutor{
handler := &InvocationTaskHandler{
config: &Config{
RequestTimeout: dynamicconfig.GetDurationPropertyFnFilteredByDestination(time.Second),
RetryPolicy: func() backoff.RetryPolicy {
@@ -564,7 +564,7 @@ func TestExecuteInvocationTaskChasm_Outcomes(t *testing.T) {
chasmRegistry := chasm.NewRegistry(logger)
err = chasmRegistry.Register(&Library{
InvocationTaskExecutor: executor,
InvocationTaskHandler: handler,
})
require.NoError(t, err)
err = chasmRegistry.Register(&mockNexusCompletionGetterLibrary{})
@@ -669,7 +669,7 @@ func TestExecuteInvocationTaskChasm_Outcomes(t *testing.T) {
// Execute the invocation task
task := &callbackspb.InvocationTask{Attempt: 1}
err = executor.Invoke(
err = handler.Invoke(
ctx,
ref,
chasm.TaskAttributes{},

View File

@@ -11,7 +11,7 @@ import (
"go.uber.org/fx"
)
type CancellationTaskExecutorOptions struct {
type CancellationTaskHandlerOptions struct {
fx.In
Config *Config
@@ -20,22 +20,23 @@ type CancellationTaskExecutorOptions struct {
Logger log.Logger
}
type CancellationTaskExecutor struct {
type CancellationTaskHandler struct {
chasm.SideEffectTaskHandlerBase[*nexusoperationpb.CancellationTask]
config *Config
metricsHandler metrics.Handler
logger log.Logger
}
func NewCancellationTaskExecutor(opts CancellationTaskExecutorOptions) *CancellationTaskExecutor {
return &CancellationTaskExecutor{
func NewCancellationTaskHandler(opts CancellationTaskHandlerOptions) *CancellationTaskHandler {
return &CancellationTaskHandler{
config: opts.Config,
metricsHandler: opts.MetricsHandler,
logger: opts.Logger,
}
}
func (e *CancellationTaskExecutor) Validate(
func (h *CancellationTaskHandler) Validate(
ctx chasm.Context,
cancellation *Cancellation,
attrs chasm.TaskAttributes,
@@ -44,7 +45,7 @@ func (e *CancellationTaskExecutor) Validate(
return false, serviceerror.NewUnimplemented("unimplemented")
}
func (e *CancellationTaskExecutor) Execute(
func (h *CancellationTaskHandler) Execute(
ctx context.Context,
cancelRef chasm.ComponentRef,
attrs chasm.TaskAttributes,
@@ -53,22 +54,23 @@ func (e *CancellationTaskExecutor) Execute(
return serviceerror.NewUnimplemented("unimplemented")
}
type CancellationBackoffTaskExecutor struct {
type CancellationBackoffTaskHandler struct {
chasm.PureTaskHandlerBase
config *Config
metricsHandler metrics.Handler
logger log.Logger
}
func NewCancellationBackoffTaskExecutor(opts CancellationTaskExecutorOptions) *CancellationBackoffTaskExecutor {
return &CancellationBackoffTaskExecutor{
func NewCancellationBackoffTaskHandler(opts CancellationTaskHandlerOptions) *CancellationBackoffTaskHandler {
return &CancellationBackoffTaskHandler{
config: opts.Config,
metricsHandler: opts.MetricsHandler,
logger: opts.Logger,
}
}
func (e *CancellationBackoffTaskExecutor) Validate(
func (h *CancellationBackoffTaskHandler) Validate(
ctx chasm.Context,
cancellation *Cancellation,
attrs chasm.TaskAttributes,
@@ -77,7 +79,7 @@ func (e *CancellationBackoffTaskExecutor) Validate(
return false, serviceerror.NewUnimplemented("unimplemented")
}
func (e *CancellationBackoffTaskExecutor) Execute(
func (h *CancellationBackoffTaskHandler) Execute(
ctx chasm.MutableContext,
cancellation *Cancellation,
attrs chasm.TaskAttributes,

View File

@@ -8,11 +8,11 @@ import (
var Module = fx.Module(
"chasm.lib.nexusoperations",
fx.Provide(configProvider),
fx.Provide(NewOperationInvocationTaskExecutor),
fx.Provide(NewOperationBackoffTaskExecutor),
fx.Provide(NewOperationTimeoutTaskExecutor),
fx.Provide(NewCancellationTaskExecutor),
fx.Provide(NewCancellationBackoffTaskExecutor),
fx.Provide(NewOperationInvocationTaskHandler),
fx.Provide(NewOperationBackoffTaskHandler),
fx.Provide(NewOperationTimeoutTaskHandler),
fx.Provide(NewCancellationTaskHandler),
fx.Provide(NewCancellationBackoffTaskHandler),
fx.Provide(newLibrary),
fx.Invoke(register),
)

View File

@@ -2,19 +2,18 @@ package nexusoperation
import (
"go.temporal.io/server/chasm"
"go.temporal.io/server/chasm/lib/nexusoperation/gen/nexusoperationpb/v1"
"google.golang.org/grpc"
)
type Library struct {
chasm.UnimplementedLibrary
OperationInvocationTaskExecutor *OperationInvocationTaskExecutor
OperationBackoffTaskExecutor *OperationBackoffTaskExecutor
OperationTimeoutTaskExecutor *OperationTimeoutTaskExecutor
OperationInvocationTaskHandler *OperationInvocationTaskHandler
OperationBackoffTaskHandler *OperationBackoffTaskHandler
OperationTimeoutTaskHandler *OperationTimeoutTaskHandler
CancellationTaskExecutor *CancellationTaskExecutor
CancellationBackoffTaskExecutor *CancellationBackoffTaskExecutor
CancellationTaskHandler *CancellationTaskHandler
CancellationBackoffTaskHandler *CancellationBackoffTaskHandler
}
func newLibrary() *Library {
@@ -34,11 +33,11 @@ func (l *Library) Components() []*chasm.RegistrableComponent {
func (l *Library) Tasks() []*chasm.RegistrableTask {
return []*chasm.RegistrableTask{
chasm.NewRegistrableSideEffectTask[*Operation, *nexusoperationpb.InvocationTask]("invocation", l.OperationInvocationTaskExecutor, l.OperationInvocationTaskExecutor),
chasm.NewRegistrablePureTask[*Operation, *nexusoperationpb.InvocationBackoffTask]("invocationBackoff", l.OperationBackoffTaskExecutor, l.OperationBackoffTaskExecutor),
chasm.NewRegistrablePureTask[*Operation, *nexusoperationpb.InvocationTimeoutTask]("scheduleToCloseTimeout", l.OperationTimeoutTaskExecutor, l.OperationTimeoutTaskExecutor),
chasm.NewRegistrableSideEffectTask[*Cancellation, *nexusoperationpb.CancellationTask]("cancellation", l.CancellationTaskExecutor, l.CancellationTaskExecutor),
chasm.NewRegistrablePureTask[*Cancellation, *nexusoperationpb.CancellationBackoffTask]("cancellationBackoff", l.CancellationBackoffTaskExecutor, l.CancellationBackoffTaskExecutor),
chasm.NewRegistrableSideEffectTask("invocation", l.OperationInvocationTaskHandler),
chasm.NewRegistrablePureTask("invocationBackoff", l.OperationBackoffTaskHandler),
chasm.NewRegistrablePureTask("scheduleToCloseTimeout", l.OperationTimeoutTaskHandler),
chasm.NewRegistrableSideEffectTask("cancellation", l.CancellationTaskHandler),
chasm.NewRegistrablePureTask("cancellationBackoff", l.CancellationBackoffTaskHandler),
}
}

View File

@@ -11,8 +11,8 @@ import (
"go.uber.org/fx"
)
// OperationTaskExecutorOptions is the fx parameter object for common options supplied to all operation task executors.
type OperationTaskExecutorOptions struct {
// OperationTaskHandlerOptions is the fx parameter object for common options supplied to all operation task handlers.
type OperationTaskHandlerOptions struct {
fx.In
Config *Config
@@ -21,22 +21,23 @@ type OperationTaskExecutorOptions struct {
Logger log.Logger
}
type OperationInvocationTaskExecutor struct {
type OperationInvocationTaskHandler struct {
chasm.SideEffectTaskHandlerBase[*nexusoperationpb.InvocationTask]
config *Config
metricsHandler metrics.Handler
logger log.Logger
}
func NewOperationInvocationTaskExecutor(opts OperationTaskExecutorOptions) *OperationInvocationTaskExecutor {
return &OperationInvocationTaskExecutor{
func NewOperationInvocationTaskHandler(opts OperationTaskHandlerOptions) *OperationInvocationTaskHandler {
return &OperationInvocationTaskHandler{
config: opts.Config,
metricsHandler: opts.MetricsHandler,
logger: opts.Logger,
}
}
func (e *OperationInvocationTaskExecutor) Validate(
func (h *OperationInvocationTaskHandler) Validate(
ctx chasm.Context,
op *Operation,
attrs chasm.TaskAttributes,
@@ -45,7 +46,7 @@ func (e *OperationInvocationTaskExecutor) Validate(
return false, serviceerror.NewUnimplemented("unimplemented")
}
func (e *OperationInvocationTaskExecutor) Execute(
func (h *OperationInvocationTaskHandler) Execute(
ctx context.Context,
opRef chasm.ComponentRef,
attrs chasm.TaskAttributes,
@@ -54,22 +55,23 @@ func (e *OperationInvocationTaskExecutor) Execute(
return serviceerror.NewUnimplemented("unimplemented")
}
type OperationBackoffTaskExecutor struct {
type OperationBackoffTaskHandler struct {
chasm.PureTaskHandlerBase
config *Config
metricsHandler metrics.Handler
logger log.Logger
}
func NewOperationBackoffTaskExecutor(opts OperationTaskExecutorOptions) *OperationBackoffTaskExecutor {
return &OperationBackoffTaskExecutor{
func NewOperationBackoffTaskHandler(opts OperationTaskHandlerOptions) *OperationBackoffTaskHandler {
return &OperationBackoffTaskHandler{
config: opts.Config,
metricsHandler: opts.MetricsHandler,
logger: opts.Logger,
}
}
func (e *OperationBackoffTaskExecutor) Validate(
func (h *OperationBackoffTaskHandler) Validate(
ctx chasm.Context,
op *Operation,
attrs chasm.TaskAttributes,
@@ -78,7 +80,7 @@ func (e *OperationBackoffTaskExecutor) Validate(
return false, serviceerror.NewUnimplemented("unimplemented")
}
func (e *OperationBackoffTaskExecutor) Execute(
func (h *OperationBackoffTaskHandler) Execute(
ctx chasm.MutableContext,
op *Operation,
attrs chasm.TaskAttributes,
@@ -87,22 +89,23 @@ func (e *OperationBackoffTaskExecutor) Execute(
return serviceerror.NewUnimplemented("unimplemented")
}
type OperationTimeoutTaskExecutor struct {
type OperationTimeoutTaskHandler struct {
chasm.PureTaskHandlerBase
config *Config
metricsHandler metrics.Handler
logger log.Logger
}
func NewOperationTimeoutTaskExecutor(opts OperationTaskExecutorOptions) *OperationTimeoutTaskExecutor {
return &OperationTimeoutTaskExecutor{
func NewOperationTimeoutTaskHandler(opts OperationTaskHandlerOptions) *OperationTimeoutTaskHandler {
return &OperationTimeoutTaskHandler{
config: opts.Config,
metricsHandler: opts.MetricsHandler,
logger: opts.Logger,
}
}
func (e *OperationTimeoutTaskExecutor) Validate(
func (h *OperationTimeoutTaskHandler) Validate(
ctx chasm.Context,
op *Operation,
attrs chasm.TaskAttributes,
@@ -111,7 +114,7 @@ func (e *OperationTimeoutTaskExecutor) Validate(
return false, serviceerror.NewUnimplemented("unimplemented")
}
func (e *OperationTimeoutTaskExecutor) Execute(
func (h *OperationTimeoutTaskHandler) Execute(
ctx chasm.MutableContext,
op *Operation,
attrs chasm.TaskAttributes,

View File

@@ -17,7 +17,7 @@ import (
)
type (
BackfillerTaskExecutorOptions struct {
BackfillerTaskHandlerOptions struct {
fx.In
Config *Config
@@ -26,7 +26,8 @@ type (
SpecProcessor SpecProcessor
}
BackfillerTaskExecutor struct {
BackfillerTaskHandler struct {
chasm.PureTaskHandlerBase
config *Config
metricsHandler metrics.Handler
baseLogger log.Logger
@@ -34,8 +35,8 @@ type (
}
)
func NewBackfillerTaskExecutor(opts BackfillerTaskExecutorOptions) *BackfillerTaskExecutor {
return &BackfillerTaskExecutor{
func NewBackfillerTaskHandler(opts BackfillerTaskHandlerOptions) *BackfillerTaskHandler {
return &BackfillerTaskHandler{
config: opts.Config,
metricsHandler: opts.MetricsHandler,
baseLogger: opts.BaseLogger,
@@ -43,7 +44,7 @@ func NewBackfillerTaskExecutor(opts BackfillerTaskExecutorOptions) *BackfillerTa
}
}
func (b *BackfillerTaskExecutor) Validate(
func (b *BackfillerTaskHandler) Validate(
ctx chasm.Context,
backfiller *Backfiller,
attrs chasm.TaskAttributes,
@@ -55,7 +56,7 @@ func (b *BackfillerTaskExecutor) Validate(
)
}
func (b *BackfillerTaskExecutor) Execute(
func (b *BackfillerTaskHandler) Execute(
ctx chasm.MutableContext,
backfiller *Backfiller,
_ chasm.TaskAttributes,
@@ -119,13 +120,13 @@ func (b *BackfillerTaskExecutor) Execute(
return nil
}
func (b *BackfillerTaskExecutor) rescheduleBackfill(ctx chasm.MutableContext, backfiller *Backfiller) {
func (b *BackfillerTaskHandler) rescheduleBackfill(ctx chasm.MutableContext, backfiller *Backfiller) {
backoffTime := ctx.Now(backfiller).Add(b.backoffDelay(backfiller))
backfiller.scheduleTask(ctx, backoffTime)
}
// processBackfill processes a Backfiller's BackfillRequest.
func (b *BackfillerTaskExecutor) processBackfill(
func (b *BackfillerTaskHandler) processBackfill(
_ chasm.MutableContext,
scheduler *Scheduler,
backfiller *Backfiller,
@@ -172,14 +173,14 @@ func (b *BackfillerTaskExecutor) processBackfill(
}
// backoffDelay returns the amount of delay that should be added when retrying.
func (b *BackfillerTaskExecutor) backoffDelay(backfiller *Backfiller) time.Duration {
func (b *BackfillerTaskHandler) backoffDelay(backfiller *Backfiller) time.Duration {
// Increment GetAttempt here early, to avoid needing to increment
// backfiller.Attempt wherever backoffDelay's result is needed.
return b.config.RetryPolicy().ComputeNextDelay(0, int(backfiller.GetAttempt()+1), nil)
}
// processTrigger processes a Backfiller's TriggerImmediatelyRequest.
func (b *BackfillerTaskExecutor) processTrigger(
func (b *BackfillerTaskHandler) processTrigger(
_ chasm.MutableContext,
scheduler *Scheduler,
backfiller *Backfiller,
@@ -214,7 +215,7 @@ func (b *BackfillerTaskExecutor) processTrigger(
// allowedBufferedStarts returns the number of BufferedStarts that the Backfiller should
// buffer, taking into account buffer limits and concurrent backfills.
func (b *BackfillerTaskExecutor) allowedBufferedStarts(
func (b *BackfillerTaskHandler) allowedBufferedStarts(
ctx chasm.Context,
scheduler *Scheduler,
invoker *Invoker,

View File

@@ -251,13 +251,13 @@ func TestBackfillTask_PartialFill(t *testing.T) {
// task is in the future (after backoff delay).
invoker := sched.Invoker.Get(ctx)
invoker.BufferedStarts = nil // Clear to make room for next batch
executor := scheduler.NewBackfillerTaskExecutor(scheduler.BackfillerTaskExecutorOptions{
handler := scheduler.NewBackfillerTaskHandler(scheduler.BackfillerTaskHandlerOptions{
Config: defaultConfig(),
MetricsHandler: metrics.NoopMetricsHandler,
BaseLogger: env.Logger,
SpecProcessor: env.SpecProcessor,
})
err = executor.Execute(ctx, backfiller, chasm.TaskAttributes{}, &schedulerpb.BackfillerTask{})
err = handler.Execute(ctx, backfiller, chasm.TaskAttributes{}, &schedulerpb.BackfillerTask{})
require.NoError(t, err)
require.NoError(t, env.CloseTransaction())

View File

@@ -20,13 +20,13 @@ var Module = fx.Module(
fx.Provide(NewSpecProcessor),
fx.Provide(func(impl *SpecProcessorImpl) SpecProcessor { return impl }),
fx.Provide(newHandler),
fx.Provide(NewSchedulerIdleTaskExecutor),
fx.Provide(NewSchedulerCallbacksTaskExecutor),
fx.Provide(NewGeneratorTaskExecutor),
fx.Provide(NewInvokerExecuteTaskExecutor),
fx.Provide(NewInvokerProcessBufferTaskExecutor),
fx.Provide(NewBackfillerTaskExecutor),
fx.Provide(NewSchedulerMigrateToWorkflowTaskExecutor),
fx.Provide(NewSchedulerIdleTaskHandler),
fx.Provide(NewSchedulerCallbacksTaskHandler),
fx.Provide(NewGeneratorTaskHandler),
fx.Provide(NewInvokerExecuteTaskHandler),
fx.Provide(NewInvokerProcessBufferTaskHandler),
fx.Provide(NewBackfillerTaskHandler),
fx.Provide(NewSchedulerMigrateToWorkflowTaskHandler),
fx.Provide(NewLibrary),
fx.Invoke(Register),
)

View File

@@ -16,7 +16,7 @@ import (
)
type (
GeneratorTaskExecutorOptions struct {
GeneratorTaskHandlerOptions struct {
fx.In
Config *Config
@@ -26,7 +26,8 @@ type (
SpecBuilder *scheduler.SpecBuilder
}
GeneratorTaskExecutor struct {
GeneratorTaskHandler struct {
chasm.PureTaskHandlerBase
config *Config
metricsHandler metrics.Handler
baseLogger log.Logger
@@ -35,8 +36,8 @@ type (
}
)
func NewGeneratorTaskExecutor(opts GeneratorTaskExecutorOptions) *GeneratorTaskExecutor {
return &GeneratorTaskExecutor{
func NewGeneratorTaskHandler(opts GeneratorTaskHandlerOptions) *GeneratorTaskHandler {
return &GeneratorTaskHandler{
config: opts.Config,
metricsHandler: opts.MetricsHandler,
baseLogger: opts.BaseLogger,
@@ -45,7 +46,7 @@ func NewGeneratorTaskExecutor(opts GeneratorTaskExecutorOptions) *GeneratorTaskE
}
}
func (g *GeneratorTaskExecutor) Execute(
func (g *GeneratorTaskHandler) Execute(
ctx chasm.MutableContext,
generator *Generator,
_ chasm.TaskAttributes,
@@ -141,13 +142,13 @@ func (g *GeneratorTaskExecutor) Execute(
return nil
}
func (g *GeneratorTaskExecutor) logSchedule(logger log.Logger, msg string, scheduler *Scheduler) {
func (g *GeneratorTaskHandler) logSchedule(logger log.Logger, msg string, sched *Scheduler) {
logger.Debug(msg,
tag.Stringer("spec", jsonStringer{scheduler.Schedule.Spec}),
tag.Stringer("policies", jsonStringer{scheduler.Schedule.Policies}))
tag.Stringer("spec", jsonStringer{sched.Schedule.Spec}),
tag.Stringer("policies", jsonStringer{sched.Schedule.Policies}))
}
func (g *GeneratorTaskExecutor) Validate(
func (g *GeneratorTaskHandler) Validate(
ctx chasm.Context,
generator *Generator,
attrs chasm.TaskAttributes,

View File

@@ -17,8 +17,8 @@ import (
"google.golang.org/protobuf/types/known/timestamppb"
)
func newGeneratorExecutor(env *testEnv) *scheduler.GeneratorTaskExecutor {
return scheduler.NewGeneratorTaskExecutor(scheduler.GeneratorTaskExecutorOptions{
func newGeneratorHandler(env *testEnv) *scheduler.GeneratorTaskHandler {
return scheduler.NewGeneratorTaskHandler(scheduler.GeneratorTaskHandlerOptions{
Config: defaultConfig(),
MetricsHandler: metrics.NoopMetricsHandler,
BaseLogger: env.Logger,
@@ -52,13 +52,13 @@ func TestGeneratorTask_Execute_ProcessTimeRangeFails(t *testing.T) {
}, nil).AnyTimes()
env := newTestEnv(t, withSpecProcessor(specProcessor))
executor := newGeneratorExecutor(env)
handler := newGeneratorHandler(env)
ctx := env.MutableContext()
generator := env.Scheduler.Generator.Get(ctx)
// If ProcessTimeRange fails, we should fail the task as an internal error.
err := executor.Execute(ctx, generator, chasm.TaskAttributes{}, &schedulerpb.GeneratorTask{})
err := handler.Execute(ctx, generator, chasm.TaskAttributes{}, &schedulerpb.GeneratorTask{})
var target *queueerrors.UnprocessableTaskError
require.ErrorAs(t, err, &target)
require.Equal(t, "failed to process a time range: processTimeRange bug", target.Message)
@@ -66,7 +66,7 @@ func TestGeneratorTask_Execute_ProcessTimeRangeFails(t *testing.T) {
func TestGeneratorTask_ExecuteBufferTask_Basic(t *testing.T) {
env := newTestEnv(t)
executor := newGeneratorExecutor(env)
handler := newGeneratorHandler(env)
ctx := env.MutableContext()
sched := env.Scheduler
@@ -78,7 +78,7 @@ func TestGeneratorTask_ExecuteBufferTask_Basic(t *testing.T) {
generator.LastProcessedTime = timestamppb.New(highWatermark)
// Execute the generate task.
err := executor.Execute(ctx, generator, chasm.TaskAttributes{}, &schedulerpb.GeneratorTask{})
err := handler.Execute(ctx, generator, chasm.TaskAttributes{}, &schedulerpb.GeneratorTask{})
require.NoError(t, err)
// We expect 5 buffered starts.
@@ -103,12 +103,12 @@ func TestGeneratorTask_ExecuteBufferTask_Basic(t *testing.T) {
func TestGeneratorTask_UpdateFutureActionTimes_UnlimitedActions(t *testing.T) {
env := newTestEnv(t)
executor := newGeneratorExecutor(env)
handler := newGeneratorHandler(env)
ctx := env.MutableContext()
generator := env.Scheduler.Generator.Get(ctx)
err := executor.Execute(ctx, generator, chasm.TaskAttributes{}, &schedulerpb.GeneratorTask{})
err := handler.Execute(ctx, generator, chasm.TaskAttributes{}, &schedulerpb.GeneratorTask{})
require.NoError(t, err)
require.NotEmpty(t, generator.FutureActionTimes)
@@ -117,7 +117,7 @@ func TestGeneratorTask_UpdateFutureActionTimes_UnlimitedActions(t *testing.T) {
func TestGeneratorTask_UpdateFutureActionTimes_LimitedActions(t *testing.T) {
env := newTestEnv(t)
executor := newGeneratorExecutor(env)
handler := newGeneratorHandler(env)
ctx := env.MutableContext()
sched := env.Scheduler
@@ -126,7 +126,7 @@ func TestGeneratorTask_UpdateFutureActionTimes_LimitedActions(t *testing.T) {
sched.Schedule.State.LimitedActions = true
sched.Schedule.State.RemainingActions = 2
err := executor.Execute(ctx, generator, chasm.TaskAttributes{}, &schedulerpb.GeneratorTask{})
err := handler.Execute(ctx, generator, chasm.TaskAttributes{}, &schedulerpb.GeneratorTask{})
require.NoError(t, err)
require.Len(t, generator.FutureActionTimes, 2)
@@ -134,7 +134,7 @@ func TestGeneratorTask_UpdateFutureActionTimes_LimitedActions(t *testing.T) {
func TestGeneratorTask_UpdateFutureActionTimes_SkipsBeforeUpdateTime(t *testing.T) {
env := newTestEnv(t)
executor := newGeneratorExecutor(env)
handler := newGeneratorHandler(env)
ctx := env.MutableContext()
sched := env.Scheduler
@@ -145,7 +145,7 @@ func TestGeneratorTask_UpdateFutureActionTimes_SkipsBeforeUpdateTime(t *testing.
updateTime := baseTime.Add(defaultInterval / 2)
sched.Info.UpdateTime = timestamppb.New(updateTime)
err := executor.Execute(ctx, generator, chasm.TaskAttributes{}, &schedulerpb.GeneratorTask{})
err := handler.Execute(ctx, generator, chasm.TaskAttributes{}, &schedulerpb.GeneratorTask{})
require.NoError(t, err)
require.NotEmpty(t, generator.FutureActionTimes)

View File

@@ -81,7 +81,7 @@ func defaultConfig() *scheduler.Config {
func newTestLibrary(logger log.Logger, specProcessor scheduler.SpecProcessor) *scheduler.Library {
config := defaultConfig()
specBuilder := legacyscheduler.NewSpecBuilder()
invokerOpts := scheduler.InvokerTaskExecutorOptions{
invokerOpts := scheduler.InvokerTaskHandlerOptions{
Config: config,
MetricsHandler: metrics.NoopMetricsHandler,
BaseLogger: logger,
@@ -89,28 +89,28 @@ func newTestLibrary(logger log.Logger, specProcessor scheduler.SpecProcessor) *s
}
return scheduler.NewLibrary(
nil,
scheduler.NewSchedulerIdleTaskExecutor(scheduler.SchedulerIdleTaskExecutorOptions{
scheduler.NewSchedulerIdleTaskHandler(scheduler.SchedulerIdleTaskHandlerOptions{
Config: config,
}),
scheduler.NewSchedulerCallbacksTaskExecutor(scheduler.SchedulerCallbacksTaskExecutorOptions{
scheduler.NewSchedulerCallbacksTaskHandler(scheduler.SchedulerCallbacksTaskHandlerOptions{
Config: config,
}),
scheduler.NewGeneratorTaskExecutor(scheduler.GeneratorTaskExecutorOptions{
scheduler.NewGeneratorTaskHandler(scheduler.GeneratorTaskHandlerOptions{
Config: config,
MetricsHandler: metrics.NoopMetricsHandler,
BaseLogger: logger,
SpecProcessor: specProcessor,
SpecBuilder: specBuilder,
}),
scheduler.NewInvokerExecuteTaskExecutor(invokerOpts),
scheduler.NewInvokerProcessBufferTaskExecutor(invokerOpts),
scheduler.NewBackfillerTaskExecutor(scheduler.BackfillerTaskExecutorOptions{
scheduler.NewInvokerExecuteTaskHandler(invokerOpts),
scheduler.NewInvokerProcessBufferTaskHandler(invokerOpts),
scheduler.NewBackfillerTaskHandler(scheduler.BackfillerTaskHandlerOptions{
Config: config,
MetricsHandler: metrics.NoopMetricsHandler,
BaseLogger: logger,
SpecProcessor: specProcessor,
}),
scheduler.NewSchedulerMigrateToWorkflowTaskExecutor(scheduler.SchedulerMigrateToWorkflowTaskExecutorOptions{
scheduler.NewSchedulerMigrateToWorkflowTaskHandler(scheduler.SchedulerMigrateToWorkflowTaskHandlerOptions{
Config: config,
MetricsHandler: metrics.NoopMetricsHandler,
BaseLogger: logger,

View File

@@ -23,7 +23,7 @@ import (
// invokerExecuteTestEnv extends testEnv with mock clients for invoker execute tests.
type invokerExecuteTestEnv struct {
*testEnv
executor *scheduler.InvokerExecuteTaskExecutor
handler *scheduler.InvokerExecuteTaskHandler
mockFrontendClient *workflowservicemock.MockWorkflowServiceClient
mockHistoryClient *historyservicemock.MockHistoryServiceClient
}
@@ -34,7 +34,7 @@ func newInvokerExecuteTestEnv(t *testing.T) *invokerExecuteTestEnv {
mockFrontendClient := workflowservicemock.NewMockWorkflowServiceClient(env.Ctrl)
mockHistoryClient := historyservicemock.NewMockHistoryServiceClient(env.Ctrl)
executor := scheduler.NewInvokerExecuteTaskExecutor(scheduler.InvokerTaskExecutorOptions{
handler := scheduler.NewInvokerExecuteTaskHandler(scheduler.InvokerTaskHandlerOptions{
Config: defaultConfig(),
MetricsHandler: metrics.NoopMetricsHandler,
BaseLogger: env.Logger,
@@ -45,7 +45,7 @@ func newInvokerExecuteTestEnv(t *testing.T) *invokerExecuteTestEnv {
return &invokerExecuteTestEnv{
testEnv: env,
executor: executor,
handler: handler,
mockFrontendClient: mockFrontendClient,
mockHistoryClient: mockHistoryClient,
}
@@ -98,7 +98,7 @@ func runExecuteTestCase(t *testing.T, env *invokerExecuteTestEnv, c *executeTest
// Create engine context for side effect task execution.
engineCtx := env.EngineContext()
err := env.executor.Execute(engineCtx, chasm.ComponentRef{}, chasm.TaskAttributes{}, &schedulerpb.InvokerExecuteTask{})
err := env.handler.Execute(engineCtx, chasm.ComponentRef{}, chasm.TaskAttributes{}, &schedulerpb.InvokerExecuteTask{})
require.NoError(t, err)
require.NoError(t, env.CloseTransaction())

View File

@@ -16,8 +16,8 @@ import (
"google.golang.org/protobuf/types/known/timestamppb"
)
func newProcessBufferExecutor(env *testEnv) *scheduler.InvokerProcessBufferTaskExecutor {
return scheduler.NewInvokerProcessBufferTaskExecutor(scheduler.InvokerTaskExecutorOptions{
func newProcessBufferHandler(env *testEnv) *scheduler.InvokerProcessBufferTaskHandler {
return scheduler.NewInvokerProcessBufferTaskHandler(scheduler.InvokerTaskHandlerOptions{
Config: defaultConfig(),
MetricsHandler: metrics.NoopMetricsHandler,
BaseLogger: env.Logger,
@@ -64,8 +64,8 @@ func runProcessBufferTestCase(t *testing.T, env *testEnv, c *processBufferTestCa
// Set LastProcessedTime to current time to ensure time checks pass.
invoker.LastProcessedTime = timestamppb.New(env.TimeSource.Now())
executor := newProcessBufferExecutor(env)
err := executor.Execute(ctx, invoker, chasm.TaskAttributes{}, &schedulerpb.InvokerProcessBufferTask{})
handler := newProcessBufferHandler(env)
err := handler.Execute(ctx, invoker, chasm.TaskAttributes{}, &schedulerpb.InvokerProcessBufferTask{})
require.NoError(t, err)
require.NoError(t, env.CloseTransaction())

View File

@@ -29,7 +29,7 @@ import (
)
type (
InvokerTaskExecutorOptions struct {
InvokerTaskHandlerOptions struct {
fx.In
Config *Config
@@ -45,7 +45,8 @@ type (
FrontendClient workflowservice.WorkflowServiceClient
}
InvokerExecuteTaskExecutor struct {
InvokerExecuteTaskHandler struct {
chasm.SideEffectTaskHandlerBase[*schedulerpb.InvokerExecuteTask]
config *Config
metricsHandler metrics.Handler
baseLogger log.Logger
@@ -53,7 +54,8 @@ type (
frontendClient workflowservice.WorkflowServiceClient
}
InvokerProcessBufferTaskExecutor struct {
InvokerProcessBufferTaskHandler struct {
chasm.PureTaskHandlerBase
config *Config
metricsHandler metrics.Handler
baseLogger log.Logger
@@ -62,7 +64,7 @@ type (
}
// Per-task context.
invokerTaskExecutorContext struct {
invokerTaskHandlerContext struct {
context.Context
actionsTaken int
@@ -88,8 +90,8 @@ var (
_ error = &rateLimitedError{}
)
func NewInvokerExecuteTaskExecutor(opts InvokerTaskExecutorOptions) *InvokerExecuteTaskExecutor {
return &InvokerExecuteTaskExecutor{
func NewInvokerExecuteTaskHandler(opts InvokerTaskHandlerOptions) *InvokerExecuteTaskHandler {
return &InvokerExecuteTaskHandler{
config: opts.Config,
metricsHandler: opts.MetricsHandler,
baseLogger: opts.BaseLogger,
@@ -98,8 +100,8 @@ func NewInvokerExecuteTaskExecutor(opts InvokerTaskExecutorOptions) *InvokerExec
}
}
func NewInvokerProcessBufferTaskExecutor(opts InvokerTaskExecutorOptions) *InvokerProcessBufferTaskExecutor {
return &InvokerProcessBufferTaskExecutor{
func NewInvokerProcessBufferTaskHandler(opts InvokerTaskHandlerOptions) *InvokerProcessBufferTaskHandler {
return &InvokerProcessBufferTaskHandler{
config: opts.Config,
metricsHandler: opts.MetricsHandler,
baseLogger: opts.BaseLogger,
@@ -108,7 +110,7 @@ func NewInvokerProcessBufferTaskExecutor(opts InvokerTaskExecutorOptions) *Invok
}
}
func (e *InvokerExecuteTaskExecutor) Validate(
func (h *InvokerExecuteTaskHandler) Validate(
_ chasm.Context,
invoker *Invoker,
_ chasm.TaskAttributes,
@@ -123,7 +125,7 @@ func (e *InvokerExecuteTaskExecutor) Validate(
return valid, nil
}
func (e *InvokerExecuteTaskExecutor) Execute(
func (h *InvokerExecuteTaskHandler) Execute(
ctx context.Context,
invokerRef chasm.ComponentRef,
_ chasm.TaskAttributes,
@@ -171,8 +173,8 @@ func (e *InvokerExecuteTaskExecutor) Execute(
return fmt.Errorf("failed to read component: %w", err)
}
logger := newTaggedLogger(e.baseLogger, scheduler)
metricsHandler := newTaggedMetricsHandler(e.metricsHandler, scheduler)
logger := newTaggedLogger(h.baseLogger, scheduler)
metricsHandler := newTaggedMetricsHandler(h.metricsHandler, scheduler)
// Terminate, cancel, and start workflows. The result struct contains the
// complete outcome of all requests executed in a single batch.
@@ -180,10 +182,10 @@ func (e *InvokerExecuteTaskExecutor) Execute(
// Invoker will never have work pending for more than one of these calls (terminate,
// cancel, start) at a time, so it isn't sensible to run them in parallel. The
// structure below is simply for code simplicity.
ictx := e.newInvokerTaskExecutorContext(ctx, scheduler)
result = result.Append(e.terminateWorkflows(ictx, logger, metricsHandler, scheduler, invoker.GetTerminateWorkflows()))
result = result.Append(e.cancelWorkflows(ictx, logger, metricsHandler, scheduler, invoker.GetCancelWorkflows()))
sres, startResults := e.startWorkflows(ictx, logger, metricsHandler, scheduler, invoker, lastCompletionState, callback)
ictx := h.newInvokerTaskHandlerContext(ctx, scheduler)
result = result.Append(h.terminateWorkflows(ictx, logger, metricsHandler, scheduler, invoker.GetTerminateWorkflows()))
result = result.Append(h.cancelWorkflows(ictx, logger, metricsHandler, scheduler, invoker.GetCancelWorkflows()))
sres, startResults := h.startWorkflows(ictx, logger, metricsHandler, scheduler, invoker, lastCompletionState, callback)
result = result.Append(sres)
// Record action results on the Invoker (internal state), as well as the
@@ -210,7 +212,7 @@ func (e *InvokerExecuteTaskExecutor) Execute(
// takeNextAction increments the context's actionTaken counter, returning true if
// the action should be executed, and false if the task should instead yield.
func (i *invokerTaskExecutorContext) takeNextAction() bool {
func (i *invokerTaskHandlerContext) takeNextAction() bool {
allowed := i.actionsTaken < i.maxActions
if allowed {
i.actionsTaken++
@@ -219,8 +221,8 @@ func (i *invokerTaskExecutorContext) takeNextAction() bool {
}
// cancelWorkflows does a best-effort attempt to cancel all workflow executions provided in targets.
func (e *InvokerExecuteTaskExecutor) cancelWorkflows(
ctx invokerTaskExecutorContext,
func (h *InvokerExecuteTaskHandler) cancelWorkflows(
ctx invokerTaskHandlerContext,
logger log.Logger,
metricsHandler metrics.Handler,
scheduler *Scheduler,
@@ -237,7 +239,7 @@ func (e *InvokerExecuteTaskExecutor) cancelWorkflows(
// Run all cancels concurrently.
newCtx := ctx.Clone()
wg.Go(func() {
err := e.cancelWorkflow(newCtx, scheduler, wf)
err := h.cancelWorkflow(newCtx, scheduler, wf)
resultMutex.Lock()
defer resultMutex.Unlock()
@@ -257,8 +259,8 @@ func (e *InvokerExecuteTaskExecutor) cancelWorkflows(
}
// terminateWorkflows does a best-effort attempt to terminate all workflow executions provided in targets.
func (e *InvokerExecuteTaskExecutor) terminateWorkflows(
ctx invokerTaskExecutorContext,
func (h *InvokerExecuteTaskHandler) terminateWorkflows(
ctx invokerTaskHandlerContext,
logger log.Logger,
metricsHandler metrics.Handler,
scheduler *Scheduler,
@@ -275,7 +277,7 @@ func (e *InvokerExecuteTaskExecutor) terminateWorkflows(
// Run all terminates concurrently.
newCtx := ctx.Clone()
wg.Go(func() {
err := e.terminateWorkflow(newCtx, scheduler, wf)
err := h.terminateWorkflow(newCtx, scheduler, wf)
resultMutex.Lock()
defer resultMutex.Unlock()
@@ -295,8 +297,8 @@ func (e *InvokerExecuteTaskExecutor) terminateWorkflows(
}
// startWorkflows executes the provided list of starts, returning a result with their outcomes.
func (e *InvokerExecuteTaskExecutor) startWorkflows(
ctx invokerTaskExecutorContext,
func (h *InvokerExecuteTaskHandler) startWorkflows(
ctx invokerTaskHandlerContext,
logger log.Logger,
metricsHandler metrics.Handler,
scheduler *Scheduler,
@@ -332,7 +334,7 @@ func (e *InvokerExecuteTaskExecutor) startWorkflows(
// Run all starts concurrently.
newCtx := ctx.Clone()
wg.Go(func() {
startResult, err := e.startWorkflow(newCtx, metricsHandler, scheduler, start, lastCompletionState, callback)
startResult, err := h.startWorkflow(newCtx, metricsHandler, scheduler, start, lastCompletionState, callback)
resultMutex.Lock()
defer resultMutex.Unlock()
@@ -348,7 +350,7 @@ func (e *InvokerExecuteTaskExecutor) startWorkflows(
if isRetryableError(err) {
// Apply backoff to start and retry.
e.applyBackoff(start, err)
h.applyBackoff(start, err)
result.RetryableStarts = append(result.RetryableStarts, start)
} else {
// Drop the start from the buffer.
@@ -368,7 +370,7 @@ func (e *InvokerExecuteTaskExecutor) startWorkflows(
return
}
func (e *InvokerProcessBufferTaskExecutor) Validate(
func (h *InvokerProcessBufferTaskHandler) Validate(
ctx chasm.Context,
invoker *Invoker,
attrs chasm.TaskAttributes,
@@ -380,7 +382,7 @@ func (e *InvokerProcessBufferTaskExecutor) Validate(
)
}
func (e *InvokerProcessBufferTaskExecutor) Execute(
func (h *InvokerProcessBufferTaskHandler) Execute(
ctx chasm.MutableContext,
invoker *Invoker,
_ chasm.TaskAttributes,
@@ -395,7 +397,7 @@ func (e *InvokerProcessBufferTaskExecutor) Execute(
}
// Compute actions to take from the current buffer.
result := e.processBuffer(ctx, invoker, scheduler)
result := h.processBuffer(ctx, invoker, scheduler)
// Update Scheduler metadata.
scheduler.recordActionResult(&schedulerActionResult{
@@ -412,7 +414,7 @@ func (e *InvokerProcessBufferTaskExecutor) Execute(
// processBuffer resolves the Invoker's buffered starts that haven't yet begun
// execution. This is where the decision is made to drive execution to
// completion, or skip/drop a start.
func (e *InvokerProcessBufferTaskExecutor) processBuffer(
func (h *InvokerProcessBufferTaskHandler) processBuffer(
ctx chasm.MutableContext,
invoker *Invoker,
scheduler *Scheduler,
@@ -453,7 +455,7 @@ func (e *InvokerProcessBufferTaskExecutor) processBuffer(
continue
}
if ctx.Now(invoker).After(e.startWorkflowDeadline(ctx, scheduler, start)) {
if ctx.Now(invoker).After(h.startWorkflowDeadline(ctx, scheduler, start)) {
// Drop expired starts.
result.missedCatchupWindow++
result.discardStarts = append(result.discardStarts, start)
@@ -481,7 +483,7 @@ func (e *InvokerProcessBufferTaskExecutor) processBuffer(
}
// applyBackoff updates start's BackoffTime based on err and the retry policy.
func (e *InvokerExecuteTaskExecutor) applyBackoff(start *schedulespb.BufferedStart, err error) {
func (h *InvokerExecuteTaskHandler) applyBackoff(start *schedulespb.BufferedStart, err error) {
if err == nil {
return
}
@@ -493,7 +495,7 @@ func (e *InvokerExecuteTaskExecutor) applyBackoff(start *schedulespb.BufferedSta
} else {
// Otherwise, use the backoff policy. Elapsed time is left at 0 because we bound
// on number of attempts.
delay = e.config.RetryPolicy().ComputeNextDelay(0, int(start.Attempt), nil)
delay = h.config.RetryPolicy().ComputeNextDelay(0, int(start.Attempt), nil)
}
start.BackoffTime = timestamppb.New(time.Now().Add(delay))
@@ -502,7 +504,7 @@ func (e *InvokerExecuteTaskExecutor) applyBackoff(start *schedulespb.BufferedSta
// startWorkflowDeadline returns the latest time at which a buffered workflow
// should be started, instead of dropped. The deadline puts an upper bound on
// the number of retry attempts per buffered start.
func (e *InvokerProcessBufferTaskExecutor) startWorkflowDeadline(
func (h *InvokerProcessBufferTaskHandler) startWorkflowDeadline(
ctx chasm.Context,
scheduler *Scheduler,
start *schedulespb.BufferedStart,
@@ -518,7 +520,7 @@ func (e *InvokerProcessBufferTaskExecutor) startWorkflowDeadline(
// Set request deadline based on the schedule's catchup window, which is the
// latest time that it's acceptable to start this workflow.
tweakables := e.config.Tweakables(scheduler.Namespace)
tweakables := h.config.Tweakables(scheduler.Namespace)
timeout = catchupWindow(scheduler, tweakables)
timeout = max(timeout, startWorkflowMinDeadline)
@@ -526,7 +528,7 @@ func (e *InvokerProcessBufferTaskExecutor) startWorkflowDeadline(
return start.ActualTime.AsTime().Add(timeout)
}
func (e *InvokerExecuteTaskExecutor) startWorkflow(
func (h *InvokerExecuteTaskHandler) startWorkflow(
ctx context.Context,
metricsHandler metrics.Handler,
scheduler *Scheduler,
@@ -542,7 +544,7 @@ func (e *InvokerExecuteTaskExecutor) startWorkflow(
// Get rate limiter permission once per buffered start, on the first attempt only.
if start.Attempt == 1 {
delay, err := e.getRateLimiterPermission()
delay, err := h.getRateLimiterPermission()
if err != nil {
return nil, err
}
@@ -585,7 +587,7 @@ func (e *InvokerExecuteTaskExecutor) startWorkflow(
},
}
result, err := e.frontendClient.StartWorkflowExecution(ctx, request)
result, err := h.frontendClient.StartWorkflowExecution(ctx, request)
if err != nil {
return nil, err
}
@@ -616,7 +618,7 @@ func (e *InvokerExecuteTaskExecutor) startWorkflow(
}, nil
}
func (e *InvokerExecuteTaskExecutor) terminateWorkflow(
func (h *InvokerExecuteTaskHandler) terminateWorkflow(
ctx context.Context,
scheduler *Scheduler,
target *commonpb.WorkflowExecution,
@@ -631,11 +633,11 @@ func (e *InvokerExecuteTaskExecutor) terminateWorkflow(
FirstExecutionRunId: target.RunId,
},
}
_, err := e.historyClient.TerminateWorkflowExecution(ctx, request)
_, err := h.historyClient.TerminateWorkflowExecution(ctx, request)
return err
}
func (e *InvokerExecuteTaskExecutor) cancelWorkflow(
func (h *InvokerExecuteTaskHandler) cancelWorkflow(
ctx context.Context,
scheduler *Scheduler,
target *commonpb.WorkflowExecution,
@@ -650,14 +652,14 @@ func (e *InvokerExecuteTaskExecutor) cancelWorkflow(
FirstExecutionRunId: target.RunId,
},
}
_, err := e.historyClient.RequestCancelWorkflowExecution(ctx, request)
_, err := h.historyClient.RequestCancelWorkflowExecution(ctx, request)
return err
}
// getRateLimiterPermission returns a delay for which the caller should wait
// before proceeding. If an error is returned, execution should not proceed, and
// reservation should be retried.
func (e *InvokerExecuteTaskExecutor) getRateLimiterPermission() (delay time.Duration, err error) {
func (h *InvokerExecuteTaskHandler) getRateLimiterPermission() (delay time.Duration, err error) {
// For now, we're only going to rate limit via APS.
return
}
@@ -691,22 +693,22 @@ func (r *rateLimitedError) Error() string {
return fmt.Sprintf("rate limited for %s", r.delay)
}
func (e *InvokerExecuteTaskExecutor) newInvokerTaskExecutorContext(
func (h *InvokerExecuteTaskHandler) newInvokerTaskHandlerContext(
ctx context.Context,
scheduler *Scheduler,
) invokerTaskExecutorContext {
tweakables := e.config.Tweakables(scheduler.Namespace)
) invokerTaskHandlerContext {
tweakables := h.config.Tweakables(scheduler.Namespace)
maxActions := tweakables.MaxActionsPerExecution
return invokerTaskExecutorContext{
return invokerTaskHandlerContext{
Context: ctx,
actionsTaken: 0,
maxActions: maxActions,
}
}
func (i invokerTaskExecutorContext) Clone() invokerTaskExecutorContext {
return invokerTaskExecutorContext{
func (i invokerTaskHandlerContext) Clone() invokerTaskHandlerContext {
return invokerTaskHandlerContext{
Context: i.Context,
actionsTaken: i.actionsTaken,
maxActions: i.maxActions,

View File

@@ -12,17 +12,17 @@ type (
handler *handler
SchedulerIdleTaskExecutor *SchedulerIdleTaskExecutor
SchedulerCallbacksTaskExecutor *SchedulerCallbacksTaskExecutor
GeneratorTaskExecutor *GeneratorTaskExecutor
InvokerExecuteTaskExecutor *InvokerExecuteTaskExecutor
InvokerProcessBufferTaskExecutor *InvokerProcessBufferTaskExecutor
BackfillerTaskExecutor *BackfillerTaskExecutor
MigrateToWorkflowTaskExecutor *SchedulerMigrateToWorkflowTaskExecutor
SchedulerIdleTaskHandler *SchedulerIdleTaskHandler
SchedulerCallbacksTaskHandler *SchedulerCallbacksTaskHandler
GeneratorTaskHandler *GeneratorTaskHandler
InvokerExecuteTaskHandler *InvokerExecuteTaskHandler
InvokerProcessBufferTaskHandler *InvokerProcessBufferTaskHandler
BackfillerTaskHandler *BackfillerTaskHandler
MigrateToWorkflowTaskHandler *SchedulerMigrateToWorkflowTaskHandler
}
)
// NewNilLibrary creates a Library with all nil executors. Useful for
// NewNilLibrary creates a Library with all nil handlers. Useful for
// registration-only contexts like tdbg where no task execution is needed.
func NewNilLibrary() *Library {
return &Library{}
@@ -30,23 +30,23 @@ func NewNilLibrary() *Library {
func NewLibrary(
handler *handler,
SchedulerIdleTaskExecutor *SchedulerIdleTaskExecutor,
SchedulerCallbacksTaskExecutor *SchedulerCallbacksTaskExecutor,
GeneratorTaskExecutor *GeneratorTaskExecutor,
InvokerExecuteTaskExecutor *InvokerExecuteTaskExecutor,
InvokerProcessBufferTaskExecutor *InvokerProcessBufferTaskExecutor,
BackfillerTaskExecutor *BackfillerTaskExecutor,
MigrateToWorkflowTaskExecutor *SchedulerMigrateToWorkflowTaskExecutor,
SchedulerIdleTaskHandler *SchedulerIdleTaskHandler,
SchedulerCallbacksTaskHandler *SchedulerCallbacksTaskHandler,
GeneratorTaskHandler *GeneratorTaskHandler,
InvokerExecuteTaskHandler *InvokerExecuteTaskHandler,
InvokerProcessBufferTaskHandler *InvokerProcessBufferTaskHandler,
BackfillerTaskHandler *BackfillerTaskHandler,
MigrateToWorkflowTaskHandler *SchedulerMigrateToWorkflowTaskHandler,
) *Library {
return &Library{
handler: handler,
SchedulerIdleTaskExecutor: SchedulerIdleTaskExecutor,
SchedulerCallbacksTaskExecutor: SchedulerCallbacksTaskExecutor,
GeneratorTaskExecutor: GeneratorTaskExecutor,
InvokerExecuteTaskExecutor: InvokerExecuteTaskExecutor,
InvokerProcessBufferTaskExecutor: InvokerProcessBufferTaskExecutor,
BackfillerTaskExecutor: BackfillerTaskExecutor,
MigrateToWorkflowTaskExecutor: MigrateToWorkflowTaskExecutor,
handler: handler,
SchedulerIdleTaskHandler: SchedulerIdleTaskHandler,
SchedulerCallbacksTaskHandler: SchedulerCallbacksTaskHandler,
GeneratorTaskHandler: GeneratorTaskHandler,
InvokerExecuteTaskHandler: InvokerExecuteTaskHandler,
InvokerProcessBufferTaskHandler: InvokerProcessBufferTaskHandler,
BackfillerTaskHandler: BackfillerTaskHandler,
MigrateToWorkflowTaskHandler: MigrateToWorkflowTaskHandler,
}
}
@@ -71,38 +71,31 @@ func (l *Library) Tasks() []*chasm.RegistrableTask {
return []*chasm.RegistrableTask{
chasm.NewRegistrablePureTask(
"idle",
l.SchedulerIdleTaskExecutor,
l.SchedulerIdleTaskExecutor,
l.SchedulerIdleTaskHandler,
),
chasm.NewRegistrableSideEffectTask(
"callbacks",
l.SchedulerCallbacksTaskExecutor,
l.SchedulerCallbacksTaskExecutor,
l.SchedulerCallbacksTaskHandler,
),
chasm.NewRegistrablePureTask(
"generate",
l.GeneratorTaskExecutor,
l.GeneratorTaskExecutor,
l.GeneratorTaskHandler,
),
chasm.NewRegistrableSideEffectTask(
"execute",
l.InvokerExecuteTaskExecutor,
l.InvokerExecuteTaskExecutor,
l.InvokerExecuteTaskHandler,
),
chasm.NewRegistrablePureTask(
"processBuffer",
l.InvokerProcessBufferTaskExecutor,
l.InvokerProcessBufferTaskExecutor,
l.InvokerProcessBufferTaskHandler,
),
chasm.NewRegistrablePureTask(
"backfill",
l.BackfillerTaskExecutor,
l.BackfillerTaskExecutor,
l.BackfillerTaskHandler,
),
chasm.NewRegistrableSideEffectTask(
"migrateToWorkflow",
l.MigrateToWorkflowTaskExecutor,
l.MigrateToWorkflowTaskExecutor,
l.MigrateToWorkflowTaskHandler,
),
}
}

View File

@@ -40,7 +40,7 @@ func runIdleValidateTestCase(t *testing.T, env *testEnv, c *idleValidateTestCase
},
}
executor := scheduler.NewSchedulerIdleTaskExecutor(scheduler.SchedulerIdleTaskExecutorOptions{
handler := scheduler.NewSchedulerIdleTaskHandler(scheduler.SchedulerIdleTaskHandlerOptions{
Config: config,
})
@@ -59,7 +59,7 @@ func runIdleValidateTestCase(t *testing.T, env *testEnv, c *idleValidateTestCase
ScheduledTime: scheduledTime,
}
isValid, err := executor.Validate(ctx, sched, taskAttrs, task)
isValid, err := handler.Validate(ctx, sched, taskAttrs, task)
require.NoError(t, err)
require.Equal(t, c.expectedValid, isValid)
}
@@ -69,7 +69,7 @@ func TestIdleTask_Execute(t *testing.T) {
ctx := env.MutableContext()
sched := env.Scheduler
executor := scheduler.NewSchedulerIdleTaskExecutor(scheduler.SchedulerIdleTaskExecutorOptions{
handler := scheduler.NewSchedulerIdleTaskHandler(scheduler.SchedulerIdleTaskHandlerOptions{
Config: defaultConfig(),
})
@@ -77,7 +77,7 @@ func TestIdleTask_Execute(t *testing.T) {
require.False(t, sched.Closed)
// Execute the idle task.
err := executor.Execute(ctx, sched, chasm.TaskAttributes{}, &schedulerpb.SchedulerIdleTask{})
err := handler.Execute(ctx, sched, chasm.TaskAttributes{}, &schedulerpb.SchedulerIdleTask{})
require.NoError(t, err)
// Verify scheduler is now closed.

View File

@@ -30,7 +30,7 @@ import (
)
type (
SchedulerMigrateToWorkflowTaskExecutorOptions struct {
SchedulerMigrateToWorkflowTaskHandlerOptions struct {
fx.In
Config *Config
@@ -39,7 +39,8 @@ type (
HistoryClient resource.HistoryClient
}
SchedulerMigrateToWorkflowTaskExecutor struct {
SchedulerMigrateToWorkflowTaskHandler struct {
chasm.SideEffectTaskHandlerBase[*schedulerpb.SchedulerMigrateToWorkflowTask]
config *Config
metricsHandler metrics.Handler
baseLogger log.Logger
@@ -47,10 +48,10 @@ type (
}
)
func NewSchedulerMigrateToWorkflowTaskExecutor(
opts SchedulerMigrateToWorkflowTaskExecutorOptions,
) *SchedulerMigrateToWorkflowTaskExecutor {
return &SchedulerMigrateToWorkflowTaskExecutor{
func NewSchedulerMigrateToWorkflowTaskHandler(
opts SchedulerMigrateToWorkflowTaskHandlerOptions,
) *SchedulerMigrateToWorkflowTaskHandler {
return &SchedulerMigrateToWorkflowTaskHandler{
config: opts.Config,
metricsHandler: opts.MetricsHandler,
baseLogger: opts.BaseLogger,
@@ -58,7 +59,7 @@ func NewSchedulerMigrateToWorkflowTaskExecutor(
}
}
func (e *SchedulerMigrateToWorkflowTaskExecutor) Validate(
func (h *SchedulerMigrateToWorkflowTaskHandler) Validate(
_ chasm.Context,
scheduler *Scheduler,
_ chasm.TaskAttributes,
@@ -70,7 +71,7 @@ func (e *SchedulerMigrateToWorkflowTaskExecutor) Validate(
return scheduler.WorkflowMigration != nil, nil
}
func (e *SchedulerMigrateToWorkflowTaskExecutor) Execute(
func (h *SchedulerMigrateToWorkflowTaskHandler) Execute(
ctx context.Context,
schedulerRef chasm.ComponentRef,
_ chasm.TaskAttributes,
@@ -169,7 +170,7 @@ func (e *SchedulerMigrateToWorkflowTaskExecutor) Execute(
Priority: &commonpb.Priority{},
}
_, err = e.historyClient.StartWorkflowExecution(
_, err = h.historyClient.StartWorkflowExecution(
ctx,
common.CreateHistoryStartWorkflowRequest(result.namespaceID, startReq, nil, nil, result.now),
)

View File

@@ -21,23 +21,24 @@ import (
"google.golang.org/protobuf/types/known/timestamppb"
)
type SchedulerIdleTaskExecutorOptions struct {
type SchedulerIdleTaskHandlerOptions struct {
fx.In
Config *Config
}
type SchedulerIdleTaskExecutor struct {
type SchedulerIdleTaskHandler struct {
chasm.PureTaskHandlerBase
config *Config
}
func NewSchedulerIdleTaskExecutor(opts SchedulerIdleTaskExecutorOptions) *SchedulerIdleTaskExecutor {
return &SchedulerIdleTaskExecutor{
func NewSchedulerIdleTaskHandler(opts SchedulerIdleTaskHandlerOptions) *SchedulerIdleTaskHandler {
return &SchedulerIdleTaskHandler{
config: opts.Config,
}
}
func (r *SchedulerIdleTaskExecutor) Execute(
func (r *SchedulerIdleTaskHandler) Execute(
ctx chasm.MutableContext,
scheduler *Scheduler,
_ chasm.TaskAttributes,
@@ -47,7 +48,7 @@ func (r *SchedulerIdleTaskExecutor) Execute(
return nil
}
func (r *SchedulerIdleTaskExecutor) Validate(
func (r *SchedulerIdleTaskHandler) Validate(
ctx chasm.Context,
scheduler *Scheduler,
taskAttrs chasm.TaskAttributes,
@@ -65,7 +66,7 @@ func (r *SchedulerIdleTaskExecutor) Validate(
return !scheduler.Closed, nil
}
type SchedulerCallbacksTaskExecutorOptions struct {
type SchedulerCallbacksTaskHandlerOptions struct {
fx.In
Config *Config
@@ -73,14 +74,15 @@ type SchedulerCallbacksTaskExecutorOptions struct {
FrontendClient workflowservice.WorkflowServiceClient
}
type SchedulerCallbacksTaskExecutor struct {
type SchedulerCallbacksTaskHandler struct {
chasm.SideEffectTaskHandlerBase[*schedulerpb.SchedulerCallbacksTask]
config *Config
historyClient resource.HistoryClient
frontendClient workflowservice.WorkflowServiceClient
}
func NewSchedulerCallbacksTaskExecutor(opts SchedulerCallbacksTaskExecutorOptions) *SchedulerCallbacksTaskExecutor {
return &SchedulerCallbacksTaskExecutor{
func NewSchedulerCallbacksTaskHandler(opts SchedulerCallbacksTaskHandlerOptions) *SchedulerCallbacksTaskHandler {
return &SchedulerCallbacksTaskHandler{
config: opts.Config,
historyClient: opts.HistoryClient,
frontendClient: opts.FrontendClient,
@@ -94,7 +96,7 @@ type watchResult struct {
completed *schedulespb.CompletedResult
}
func (r *SchedulerCallbacksTaskExecutor) Execute(
func (r *SchedulerCallbacksTaskHandler) Execute(
ctx context.Context,
schedulerRef chasm.ComponentRef,
_ chasm.TaskAttributes,
@@ -172,7 +174,7 @@ func (r *SchedulerCallbacksTaskExecutor) Execute(
// watchRunningStart will attach a Nexus completion callback to a running
// BufferedStart. If the start's workflow has already closed, the start is updated
// to indicate it has completed. Intended for migration/anti-entropy cases.
func (r *SchedulerCallbacksTaskExecutor) watchRunningStart(
func (r *SchedulerCallbacksTaskHandler) watchRunningStart(
ctx context.Context,
scheduler *Scheduler,
start *schedulespb.BufferedStart,
@@ -254,7 +256,7 @@ func (r *SchedulerCallbacksTaskExecutor) watchRunningStart(
return &watchResult{}, nil
}
func (r *SchedulerCallbacksTaskExecutor) Validate(
func (r *SchedulerCallbacksTaskHandler) Validate(
ctx chasm.Context,
scheduler *Scheduler,
taskAttrs chasm.TaskAttributes,

View File

@@ -27,7 +27,7 @@ func TestNewSentinel(t *testing.T) {
func TestSentinelIdleTask_Validate_Valid(t *testing.T) {
sentinel, ctx, _ := setupSentinelForTest(t)
executor := scheduler.NewSchedulerIdleTaskExecutor(scheduler.SchedulerIdleTaskExecutorOptions{
executor := scheduler.NewSchedulerIdleTaskHandler(scheduler.SchedulerIdleTaskHandlerOptions{
Config: defaultConfig(),
})
@@ -47,7 +47,7 @@ func TestSentinelIdleTask_Validate_InvalidAfterClosed(t *testing.T) {
sentinel, ctx, _ := setupSentinelForTest(t)
sentinel.Closed = true
executor := scheduler.NewSchedulerIdleTaskExecutor(scheduler.SchedulerIdleTaskExecutorOptions{
executor := scheduler.NewSchedulerIdleTaskHandler(scheduler.SchedulerIdleTaskHandlerOptions{
Config: defaultConfig(),
})
@@ -66,7 +66,7 @@ func TestSentinelIdleTask_Validate_InvalidAfterClosed(t *testing.T) {
func TestSentinelIdleTask_Validate_MismatchedScheduledTime(t *testing.T) {
sentinel, ctx, _ := setupSentinelForTest(t)
executor := scheduler.NewSchedulerIdleTaskExecutor(scheduler.SchedulerIdleTaskExecutorOptions{
executor := scheduler.NewSchedulerIdleTaskHandler(scheduler.SchedulerIdleTaskHandlerOptions{
Config: defaultConfig(),
})
@@ -85,7 +85,7 @@ func TestSentinelIdleTask_Validate_MismatchedScheduledTime(t *testing.T) {
func TestSentinelIdleTask_Execute(t *testing.T) {
sentinel, ctx, _ := setupSentinelForTest(t)
executor := scheduler.NewSchedulerIdleTaskExecutor(scheduler.SchedulerIdleTaskExecutorOptions{
executor := scheduler.NewSchedulerIdleTaskHandler(scheduler.SchedulerIdleTaskHandlerOptions{
Config: defaultConfig(),
})

View File

@@ -47,13 +47,11 @@ func (l *library) Tasks() []*chasm.RegistrableTask {
return []*chasm.RegistrableTask{
chasm.NewRegistrablePureTask(
"payloadTTLPureTask",
&PayloadTTLPureTaskValidator{},
&PayloadTTLPureTaskExecutor{},
&PayloadTTLPureTaskHandler{},
),
chasm.NewRegistrableSideEffectTask(
"payloadTTLSideEffectTask",
&PayloadTTLSideEffectTaskValidator{},
&PayloadTTLSideEffectTaskExecutor{},
&PayloadTTLSideEffectTaskHandler{},
),
}
}

View File

@@ -7,12 +7,9 @@ import (
"go.temporal.io/server/chasm/lib/tests/gen/testspb/v1"
)
type (
PayloadTTLPureTaskExecutor struct{}
PayloadTTLPureTaskValidator struct{}
)
type PayloadTTLPureTaskHandler struct{ chasm.PureTaskHandlerBase }
func (e *PayloadTTLPureTaskExecutor) Execute(
func (h *PayloadTTLPureTaskHandler) Execute(
mutableContext chasm.MutableContext,
store *PayloadStore,
_ chasm.TaskAttributes,
@@ -26,7 +23,7 @@ func (e *PayloadTTLPureTaskExecutor) Execute(
return err
}
func (v *PayloadTTLPureTaskValidator) Validate(
func (h *PayloadTTLPureTaskHandler) Validate(
chasmContext chasm.Context,
store *PayloadStore,
attributes chasm.TaskAttributes,
@@ -35,12 +32,11 @@ func (v *PayloadTTLPureTaskValidator) Validate(
return validateTask(chasmContext, store, attributes, task.PayloadKey)
}
type (
PayloadTTLSideEffectTaskExecutor struct{}
PayloadTTLSideEffectTaskValidator struct{}
)
type PayloadTTLSideEffectTaskHandler struct {
chasm.SideEffectTaskHandlerBase[*testspb.TestPayloadTTLSideEffectTask]
}
func (e *PayloadTTLSideEffectTaskExecutor) Execute(
func (h *PayloadTTLSideEffectTaskHandler) Execute(
ctx context.Context,
ref chasm.ComponentRef,
_ chasm.TaskAttributes,
@@ -55,7 +51,7 @@ func (e *PayloadTTLSideEffectTaskExecutor) Execute(
return err
}
func (v *PayloadTTLSideEffectTaskValidator) Validate(
func (h *PayloadTTLSideEffectTaskHandler) Validate(
chasmContext chasm.Context,
store *PayloadStore,
attributes chasm.TaskAttributes,

View File

@@ -20,7 +20,6 @@ func (b *CoreLibrary) Tasks() []*RegistrableTask {
NewRegistrableSideEffectTask(
"visTask",
defaultVisibilityTaskHandler,
defaultVisibilityTaskHandler,
),
}
}

View File

@@ -31,21 +31,12 @@ type (
)
// NewRegistrableSideEffectTask creates a new registrable side-effect task. NOTE: C is not Component but any.
// If the executor also implements SideEffectTaskDiscarder, the framework will call Discard instead of silently
// discarding the task on standby clusters after the discard delay.
// The handler's Discard method is called on standby clusters when a task has been pending past the discard delay.
func NewRegistrableSideEffectTask[C any, T any](
taskType string,
validator TaskValidator[C, T],
executor SideEffectTaskExecutor[C, T],
handler SideEffectTaskHandler[C, T],
opts ...RegistrableTaskOption,
) *RegistrableTask {
var discardFn sideEffectTaskDiscardFn
if discarder, ok := any(executor).(SideEffectTaskDiscarder[T]); ok {
discardFn = func(ctx context.Context, ref ComponentRef, attrs TaskAttributes, task any) error {
return discarder.Discard(ctx, ref, attrs, task.(T))
}
}
return newRegistrableTask(
taskType,
reflect.TypeFor[T](),
@@ -57,7 +48,7 @@ func NewRegistrableSideEffectTask[C any, T any](
taskData any,
registry *Registry,
) (bool, error) {
return validator.Validate(
return handler.Validate(
ctx,
component.(C),
taskAttrs,
@@ -71,18 +62,19 @@ func NewRegistrableSideEffectTask[C any, T any](
taskAttrs TaskAttributes,
taskData any,
) error {
return executor.Execute(ctx, componentRef, taskAttrs, taskData.(T))
return handler.Execute(ctx, componentRef, taskAttrs, taskData.(T))
},
false,
discardFn,
func(ctx context.Context, ref ComponentRef, attrs TaskAttributes, task any) error {
return handler.Discard(ctx, ref, attrs, task.(T))
},
opts...,
)
}
func NewRegistrablePureTask[C any, T any](
taskType string,
validator TaskValidator[C, T],
executor PureTaskExecutor[C, T],
handler PureTaskHandler[C, T],
opts ...RegistrableTaskOption,
) *RegistrableTask {
return newRegistrableTask(
@@ -96,7 +88,7 @@ func NewRegistrablePureTask[C any, T any](
taskData any,
registry *Registry,
) (bool, error) {
return validator.Validate(
return handler.Validate(
ctx,
component.(C),
taskAttrs,
@@ -110,7 +102,7 @@ func NewRegistrablePureTask[C any, T any](
taskData any,
registry *Registry,
) error {
return executor.Execute(
return handler.Execute(
ctx,
component.(C),
taskAttrs,
@@ -171,12 +163,6 @@ func (rt *RegistrableTask) GoType() reflect.Type {
return rt.goType
}
// HasDiscardHandler returns true if the task's executor implements SideEffectTaskDiscarder, meaning it has custom
// discard behavior for standby clusters.
func (rt *RegistrableTask) HasDiscardHandler() bool {
return rt.sideEffectTaskDiscardFn != nil
}
// fqType returns the fully qualified name of the task, which is a combination of
// the library name and the task type. This is used to uniquely identify
// the task in the registry.

View File

@@ -117,15 +117,13 @@ func (s *RegistryTestSuite) TestRegistry_RegisterTasks_Success() {
lib.EXPECT().NexusServiceProcessors().Return(nil)
lib.EXPECT().Tasks().Return([]*chasm.RegistrableTask{
chasm.NewRegistrableSideEffectTask[*chasm.MockComponent, testTask1](
chasm.NewRegistrableSideEffectTask(
"Task1",
chasm.NewMockTaskValidator[*chasm.MockComponent, testTask1](ctrl),
chasm.NewMockSideEffectTaskExecutor[*chasm.MockComponent, testTask1](ctrl),
chasm.NewMockSideEffectTaskHandler[*chasm.MockComponent, testTask1](ctrl),
),
chasm.NewRegistrablePureTask[testTaskComponentInterface, testTask2](
chasm.NewRegistrablePureTask(
"Task2",
chasm.NewMockTaskValidator[testTaskComponentInterface, testTask2](ctrl),
chasm.NewMockPureTaskExecutor[testTaskComponentInterface, testTask2](ctrl),
chasm.NewMockPureTaskHandler[testTaskComponentInterface, testTask2](ctrl),
),
})
@@ -375,8 +373,7 @@ func (s *RegistryTestSuite) TestRegistry_RegisterTasks_Error() {
lib.EXPECT().Tasks().Return([]*chasm.RegistrableTask{
chasm.NewRegistrablePureTask[*chasm.MockComponent, testTask1](
"",
chasm.NewMockTaskValidator[*chasm.MockComponent, testTask1](ctrl),
chasm.NewMockPureTaskExecutor[*chasm.MockComponent, testTask1](ctrl),
chasm.NewMockPureTaskHandler[*chasm.MockComponent, testTask1](ctrl),
),
})
err := r.Register(lib)
@@ -388,8 +385,7 @@ func (s *RegistryTestSuite) TestRegistry_RegisterTasks_Error() {
lib.EXPECT().Tasks().Return([]*chasm.RegistrableTask{
chasm.NewRegistrablePureTask[*chasm.MockComponent, testTask1](
"bad.task.name",
chasm.NewMockTaskValidator[*chasm.MockComponent, testTask1](ctrl),
chasm.NewMockPureTaskExecutor[*chasm.MockComponent, testTask1](ctrl),
chasm.NewMockPureTaskHandler[*chasm.MockComponent, testTask1](ctrl),
),
})
r := chasm.NewRegistry(s.logger)
@@ -402,13 +398,11 @@ func (s *RegistryTestSuite) TestRegistry_RegisterTasks_Error() {
lib.EXPECT().Tasks().Return([]*chasm.RegistrableTask{
chasm.NewRegistrablePureTask[*chasm.MockComponent, testTask1](
"Task1",
chasm.NewMockTaskValidator[*chasm.MockComponent, testTask1](ctrl),
chasm.NewMockPureTaskExecutor[*chasm.MockComponent, testTask1](ctrl),
chasm.NewMockPureTaskHandler[*chasm.MockComponent, testTask1](ctrl),
),
chasm.NewRegistrableSideEffectTask[*chasm.MockComponent, testTask1](
"Task1",
chasm.NewMockTaskValidator[*chasm.MockComponent, testTask1](ctrl),
chasm.NewMockSideEffectTaskExecutor[*chasm.MockComponent, testTask1](ctrl),
chasm.NewMockSideEffectTaskHandler[*chasm.MockComponent, testTask1](ctrl),
),
})
r := chasm.NewRegistry(s.logger)
@@ -421,13 +415,11 @@ func (s *RegistryTestSuite) TestRegistry_RegisterTasks_Error() {
lib.EXPECT().Tasks().Return([]*chasm.RegistrableTask{
chasm.NewRegistrablePureTask[*chasm.MockComponent, testTask1](
"Task1",
chasm.NewMockTaskValidator[*chasm.MockComponent, testTask1](ctrl),
chasm.NewMockPureTaskExecutor[*chasm.MockComponent, testTask1](ctrl),
chasm.NewMockPureTaskHandler[*chasm.MockComponent, testTask1](ctrl),
),
chasm.NewRegistrablePureTask[*chasm.MockComponent, testTask1](
"Task2",
chasm.NewMockTaskValidator[*chasm.MockComponent, testTask1](ctrl),
chasm.NewMockPureTaskExecutor[*chasm.MockComponent, testTask1](ctrl),
chasm.NewMockPureTaskHandler[*chasm.MockComponent, testTask1](ctrl),
),
})
r := chasm.NewRegistry(s.logger)
@@ -445,8 +437,7 @@ func (s *RegistryTestSuite) TestRegistry_RegisterTasks_Error() {
lib2.EXPECT().NexusServiceProcessors().Return(nil)
task := chasm.NewRegistrablePureTask[*chasm.MockComponent, testTask1](
"Task1",
chasm.NewMockTaskValidator[*chasm.MockComponent, testTask1](ctrl),
chasm.NewMockPureTaskExecutor[*chasm.MockComponent, testTask1](ctrl),
chasm.NewMockPureTaskHandler[*chasm.MockComponent, testTask1](ctrl),
)
lib2.EXPECT().Tasks().Return([]*chasm.RegistrableTask{task})
r2 := chasm.NewRegistry(s.logger)
@@ -464,8 +455,7 @@ func (s *RegistryTestSuite) TestRegistry_RegisterTasks_Error() {
lib.EXPECT().Tasks().Return([]*chasm.RegistrableTask{
chasm.NewRegistrablePureTask[*chasm.MockComponent, string](
"Task1",
chasm.NewMockTaskValidator[*chasm.MockComponent, string](ctrl),
chasm.NewMockPureTaskExecutor[*chasm.MockComponent, string](ctrl),
chasm.NewMockPureTaskHandler[*chasm.MockComponent, string](ctrl),
),
})
r := chasm.NewRegistry(s.logger)

View File

@@ -4,32 +4,51 @@ package chasm
import (
"context"
"errors"
"time"
)
// ErrTaskDiscarded is the error returned by the default [SideEffectTaskHandlerBase] Discard implementation,
// indicating that a side-effect task on a standby cluster has been pending past the discard delay.
var ErrTaskDiscarded = errors.New("standby task pending for too long")
type (
// TaskAttributes specifies scheduling metadata for a task.
TaskAttributes struct {
// ScheduledTime is when the task should fire. Use [TaskScheduledTimeImmediate] (zero value)
// for tasks that should execute as soon as possible.
ScheduledTime time.Time
Destination string
// Destination is an optional routing key for outbound tasks (e.g., a URL host for HTTP
// callbacks). When non-empty, the task is categorized as outbound; when empty, it is
// categorized as a transfer task. Destination must only be set on immediate tasks.
Destination string
}
SideEffectTaskExecutor[C any, T any] interface {
// SideEffectTaskHandler handles side effect tasks that run outside of the state lock and have access to a Go
// context to perform I/O and access chasm engine methods such as [UpdateComponent]. Implementations must embed
// [SideEffectTaskHandlerBase].
SideEffectTaskHandler[C any, T any] interface {
TaskValidator[C, T]
Execute(context.Context, ComponentRef, TaskAttributes, T) error
// Discard implements custom discard behavior on standby clusters. When a side-effect task has been
// pending on standby past the discard delay, the framework calls Discard instead of silently dropping
// the task. For example, the activity dispatch handler implements this to spill tasks to matching.
// The ctx carries engine access, but implementations must avoid mutating component state on standby
// clusters.
Discard(context.Context, ComponentRef, TaskAttributes, T) error
sideEffectTaskHandler()
}
PureTaskExecutor[C any, T any] interface {
// PureTaskHandler handles pure tasks that run while holding execution state write lock and should not do I/O.
// Implementations must embed [PureTaskHandlerBase].
PureTaskHandler[C any, T any] interface {
TaskValidator[C, T]
Execute(MutableContext, C, TaskAttributes, T) error
pureTaskHandler()
}
// SideEffectTaskDiscarder is an optional interface that a side-effect task executor can implement to define
// custom discard behavior on standby clusters. When a side-effect task has been pending on standby past the
// discard delay, the framework calls Discard instead of silently discarding the task. For example, the
// activity dispatch executor implements this to spill tasks to matching. The ctx always carries engine access, and
// implementations must avoid mutating component state on standby clusters.
SideEffectTaskDiscarder[T any] interface {
Discard(ctx context.Context, ref ComponentRef, attrs TaskAttributes, task T) error
}
// TaskValidator is implemented by both [SideEffectTaskHandler] and [PureTaskHandler] to gate
// whether a task should proceed with execution.
TaskValidator[C any, T any] interface {
// Validate determines whether a task should proceed with execution based on the current context, component
// state, task attributes, and task data.
@@ -54,13 +73,17 @@ type (
}
)
// TaskScheduledTimeImmediate is the zero time value used to indicate that a task should execute immediately.
var TaskScheduledTimeImmediate = time.Time{}
// IsImmediate reports whether the task is scheduled for immediate execution (zero or unset scheduled time).
func (a *TaskAttributes) IsImmediate() bool {
return a.ScheduledTime.IsZero() ||
a.ScheduledTime.Equal(TaskScheduledTimeImmediate)
}
// IsValid reports whether the task attributes are well-formed. A Destination may only be set on
// immediate tasks; deferred tasks with a Destination are invalid.
func (a *TaskAttributes) IsValid() bool {
return a.Destination == "" || a.IsImmediate()
}

View File

@@ -0,0 +1,18 @@
package chasm
import "context"
// SideEffectTaskHandlerBase provides a default Discard implementation that returns ErrTaskDiscarded.
// Embed this in side-effect task handler structs to satisfy the SideEffectTaskHandler interface.
type SideEffectTaskHandlerBase[T any] struct{}
func (SideEffectTaskHandlerBase[T]) Discard(_ context.Context, _ ComponentRef, _ TaskAttributes, _ T) error {
return ErrTaskDiscarded
}
func (SideEffectTaskHandlerBase[T]) sideEffectTaskHandler() {}
// PureTaskHandlerBase must be embedded in all pure task handler implementations.
type PureTaskHandlerBase struct{}
func (PureTaskHandlerBase) pureTaskHandler() {}

View File

@@ -16,118 +16,148 @@ import (
gomock "go.uber.org/mock/gomock"
)
// MockSideEffectTaskExecutor is a mock of SideEffectTaskExecutor interface.
type MockSideEffectTaskExecutor[C any, T any] struct {
// MockSideEffectTaskHandler is a mock of SideEffectTaskHandler interface.
type MockSideEffectTaskHandler[C any, T any] struct {
ctrl *gomock.Controller
recorder *MockSideEffectTaskExecutorMockRecorder[C, T]
recorder *MockSideEffectTaskHandlerMockRecorder[C, T]
isgomock struct{}
}
// MockSideEffectTaskExecutorMockRecorder is the mock recorder for MockSideEffectTaskExecutor.
type MockSideEffectTaskExecutorMockRecorder[C any, T any] struct {
mock *MockSideEffectTaskExecutor[C, T]
// MockSideEffectTaskHandlerMockRecorder is the mock recorder for MockSideEffectTaskHandler.
type MockSideEffectTaskHandlerMockRecorder[C any, T any] struct {
mock *MockSideEffectTaskHandler[C, T]
}
// NewMockSideEffectTaskExecutor creates a new mock instance.
func NewMockSideEffectTaskExecutor[C any, T any](ctrl *gomock.Controller) *MockSideEffectTaskExecutor[C, T] {
mock := &MockSideEffectTaskExecutor[C, T]{ctrl: ctrl}
mock.recorder = &MockSideEffectTaskExecutorMockRecorder[C, T]{mock}
// NewMockSideEffectTaskHandler creates a new mock instance.
func NewMockSideEffectTaskHandler[C any, T any](ctrl *gomock.Controller) *MockSideEffectTaskHandler[C, T] {
mock := &MockSideEffectTaskHandler[C, T]{ctrl: ctrl}
mock.recorder = &MockSideEffectTaskHandlerMockRecorder[C, T]{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockSideEffectTaskExecutor[C, T]) EXPECT() *MockSideEffectTaskExecutorMockRecorder[C, T] {
return m.recorder
}
// Execute mocks base method.
func (m *MockSideEffectTaskExecutor[C, T]) Execute(arg0 context.Context, arg1 ComponentRef, arg2 TaskAttributes, arg3 T) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Execute", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].(error)
return ret0
}
// Execute indicates an expected call of Execute.
func (mr *MockSideEffectTaskExecutorMockRecorder[C, T]) Execute(arg0, arg1, arg2, arg3 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Execute", reflect.TypeOf((*MockSideEffectTaskExecutor[C, T])(nil).Execute), arg0, arg1, arg2, arg3)
}
// MockPureTaskExecutor is a mock of PureTaskExecutor interface.
type MockPureTaskExecutor[C any, T any] struct {
ctrl *gomock.Controller
recorder *MockPureTaskExecutorMockRecorder[C, T]
isgomock struct{}
}
// MockPureTaskExecutorMockRecorder is the mock recorder for MockPureTaskExecutor.
type MockPureTaskExecutorMockRecorder[C any, T any] struct {
mock *MockPureTaskExecutor[C, T]
}
// NewMockPureTaskExecutor creates a new mock instance.
func NewMockPureTaskExecutor[C any, T any](ctrl *gomock.Controller) *MockPureTaskExecutor[C, T] {
mock := &MockPureTaskExecutor[C, T]{ctrl: ctrl}
mock.recorder = &MockPureTaskExecutorMockRecorder[C, T]{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockPureTaskExecutor[C, T]) EXPECT() *MockPureTaskExecutorMockRecorder[C, T] {
return m.recorder
}
// Execute mocks base method.
func (m *MockPureTaskExecutor[C, T]) Execute(arg0 MutableContext, arg1 C, arg2 TaskAttributes, arg3 T) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Execute", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].(error)
return ret0
}
// Execute indicates an expected call of Execute.
func (mr *MockPureTaskExecutorMockRecorder[C, T]) Execute(arg0, arg1, arg2, arg3 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Execute", reflect.TypeOf((*MockPureTaskExecutor[C, T])(nil).Execute), arg0, arg1, arg2, arg3)
}
// MockSideEffectTaskDiscarder is a mock of SideEffectTaskDiscarder interface.
type MockSideEffectTaskDiscarder[T any] struct {
ctrl *gomock.Controller
recorder *MockSideEffectTaskDiscarderMockRecorder[T]
isgomock struct{}
}
// MockSideEffectTaskDiscarderMockRecorder is the mock recorder for MockSideEffectTaskDiscarder.
type MockSideEffectTaskDiscarderMockRecorder[T any] struct {
mock *MockSideEffectTaskDiscarder[T]
}
// NewMockSideEffectTaskDiscarder creates a new mock instance.
func NewMockSideEffectTaskDiscarder[T any](ctrl *gomock.Controller) *MockSideEffectTaskDiscarder[T] {
mock := &MockSideEffectTaskDiscarder[T]{ctrl: ctrl}
mock.recorder = &MockSideEffectTaskDiscarderMockRecorder[T]{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockSideEffectTaskDiscarder[T]) EXPECT() *MockSideEffectTaskDiscarderMockRecorder[T] {
func (m *MockSideEffectTaskHandler[C, T]) EXPECT() *MockSideEffectTaskHandlerMockRecorder[C, T] {
return m.recorder
}
// Discard mocks base method.
func (m *MockSideEffectTaskDiscarder[T]) Discard(ctx context.Context, ref ComponentRef, attrs TaskAttributes, task T) error {
func (m *MockSideEffectTaskHandler[C, T]) Discard(arg0 context.Context, arg1 ComponentRef, arg2 TaskAttributes, arg3 T) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Discard", ctx, ref, attrs, task)
ret := m.ctrl.Call(m, "Discard", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].(error)
return ret0
}
// Discard indicates an expected call of Discard.
func (mr *MockSideEffectTaskDiscarderMockRecorder[T]) Discard(ctx, ref, attrs, task any) *gomock.Call {
func (mr *MockSideEffectTaskHandlerMockRecorder[C, T]) Discard(arg0, arg1, arg2, arg3 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Discard", reflect.TypeOf((*MockSideEffectTaskDiscarder[T])(nil).Discard), ctx, ref, attrs, task)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Discard", reflect.TypeOf((*MockSideEffectTaskHandler[C, T])(nil).Discard), arg0, arg1, arg2, arg3)
}
// Execute mocks base method.
func (m *MockSideEffectTaskHandler[C, T]) Execute(arg0 context.Context, arg1 ComponentRef, arg2 TaskAttributes, arg3 T) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Execute", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].(error)
return ret0
}
// Execute indicates an expected call of Execute.
func (mr *MockSideEffectTaskHandlerMockRecorder[C, T]) Execute(arg0, arg1, arg2, arg3 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Execute", reflect.TypeOf((*MockSideEffectTaskHandler[C, T])(nil).Execute), arg0, arg1, arg2, arg3)
}
// Validate mocks base method.
func (m *MockSideEffectTaskHandler[C, T]) Validate(arg0 Context, arg1 C, arg2 TaskAttributes, arg3 T) (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Validate", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Validate indicates an expected call of Validate.
func (mr *MockSideEffectTaskHandlerMockRecorder[C, T]) Validate(arg0, arg1, arg2, arg3 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Validate", reflect.TypeOf((*MockSideEffectTaskHandler[C, T])(nil).Validate), arg0, arg1, arg2, arg3)
}
// sideEffectTaskHandler mocks base method.
func (m *MockSideEffectTaskHandler[C, T]) sideEffectTaskHandler() {
m.ctrl.T.Helper()
m.ctrl.Call(m, "sideEffectTaskHandler")
}
// sideEffectTaskHandler indicates an expected call of sideEffectTaskHandler.
func (mr *MockSideEffectTaskHandlerMockRecorder[C, T]) sideEffectTaskHandler() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "sideEffectTaskHandler", reflect.TypeOf((*MockSideEffectTaskHandler[C, T])(nil).sideEffectTaskHandler))
}
// MockPureTaskHandler is a mock of PureTaskHandler interface.
type MockPureTaskHandler[C any, T any] struct {
ctrl *gomock.Controller
recorder *MockPureTaskHandlerMockRecorder[C, T]
isgomock struct{}
}
// MockPureTaskHandlerMockRecorder is the mock recorder for MockPureTaskHandler.
type MockPureTaskHandlerMockRecorder[C any, T any] struct {
mock *MockPureTaskHandler[C, T]
}
// NewMockPureTaskHandler creates a new mock instance.
func NewMockPureTaskHandler[C any, T any](ctrl *gomock.Controller) *MockPureTaskHandler[C, T] {
mock := &MockPureTaskHandler[C, T]{ctrl: ctrl}
mock.recorder = &MockPureTaskHandlerMockRecorder[C, T]{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockPureTaskHandler[C, T]) EXPECT() *MockPureTaskHandlerMockRecorder[C, T] {
return m.recorder
}
// Execute mocks base method.
func (m *MockPureTaskHandler[C, T]) Execute(arg0 MutableContext, arg1 C, arg2 TaskAttributes, arg3 T) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Execute", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].(error)
return ret0
}
// Execute indicates an expected call of Execute.
func (mr *MockPureTaskHandlerMockRecorder[C, T]) Execute(arg0, arg1, arg2, arg3 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Execute", reflect.TypeOf((*MockPureTaskHandler[C, T])(nil).Execute), arg0, arg1, arg2, arg3)
}
// Validate mocks base method.
func (m *MockPureTaskHandler[C, T]) Validate(arg0 Context, arg1 C, arg2 TaskAttributes, arg3 T) (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Validate", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Validate indicates an expected call of Validate.
func (mr *MockPureTaskHandlerMockRecorder[C, T]) Validate(arg0, arg1, arg2, arg3 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Validate", reflect.TypeOf((*MockPureTaskHandler[C, T])(nil).Validate), arg0, arg1, arg2, arg3)
}
// pureTaskHandler mocks base method.
func (m *MockPureTaskHandler[C, T]) pureTaskHandler() {
m.ctrl.T.Helper()
m.ctrl.Call(m, "pureTaskHandler")
}
// pureTaskHandler indicates an expected call of pureTaskHandler.
func (mr *MockPureTaskHandlerMockRecorder[C, T]) pureTaskHandler() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "pureTaskHandler", reflect.TypeOf((*MockPureTaskHandler[C, T])(nil).pureTaskHandler))
}
// MockTaskValidator is a mock of TaskValidator interface.

View File

@@ -2,40 +2,18 @@
package chasm
import (
"context"
"go.uber.org/mock/gomock"
)
// mockDiscardableSideEffectExecutor wraps MockSideEffectTaskExecutor and adds SideEffectTaskDiscarder support for
// testing ExecuteSideEffectDiscardTask.
type mockDiscardableSideEffectExecutor struct {
*MockSideEffectTaskExecutor[any, *TestDiscardableSideEffectTask]
discardFn func(ctx context.Context, ref ComponentRef, attrs TaskAttributes, task *TestDiscardableSideEffectTask) error
}
func (m *mockDiscardableSideEffectExecutor) Discard(
ctx context.Context,
ref ComponentRef,
attrs TaskAttributes,
task *TestDiscardableSideEffectTask,
) error {
return m.discardFn(ctx, ref, attrs, task)
}
type TestLibrary struct {
UnimplementedLibrary
controller *gomock.Controller
mockSideEffectTaskValidator *MockTaskValidator[any, *TestSideEffectTask]
mockSideEffectTaskExecutor *MockSideEffectTaskExecutor[any, *TestSideEffectTask]
mockDiscardableSideEffectTaskValidator *MockTaskValidator[any, *TestDiscardableSideEffectTask]
mockDiscardableSideEffectExecutor *mockDiscardableSideEffectExecutor
mockOutboundSideEffectTaskValidator *MockTaskValidator[any, TestOutboundSideEffectTask]
mockOutboundSideEffectTaskExecutor *MockSideEffectTaskExecutor[any, TestOutboundSideEffectTask]
mockPureTaskValidator *MockTaskValidator[any, *TestPureTask]
mockPureTaskExecutor *MockPureTaskExecutor[any, *TestPureTask]
mockSideEffectTaskHandler *MockSideEffectTaskHandler[any, *TestSideEffectTask]
mockDiscardableSideEffectHandler *MockSideEffectTaskHandler[any, *TestDiscardableSideEffectTask]
mockOutboundSideEffectTaskHandler *MockSideEffectTaskHandler[any, TestOutboundSideEffectTask]
mockPureTaskHandler *MockPureTaskHandler[any, *TestPureTask]
}
func newTestLibrary(
@@ -44,16 +22,10 @@ func newTestLibrary(
return &TestLibrary{
controller: controller,
mockSideEffectTaskValidator: NewMockTaskValidator[any, *TestSideEffectTask](controller),
mockSideEffectTaskExecutor: NewMockSideEffectTaskExecutor[any, *TestSideEffectTask](controller),
mockDiscardableSideEffectTaskValidator: NewMockTaskValidator[any, *TestDiscardableSideEffectTask](controller),
mockDiscardableSideEffectExecutor: &mockDiscardableSideEffectExecutor{
MockSideEffectTaskExecutor: NewMockSideEffectTaskExecutor[any, *TestDiscardableSideEffectTask](controller),
},
mockOutboundSideEffectTaskValidator: NewMockTaskValidator[any, TestOutboundSideEffectTask](controller),
mockOutboundSideEffectTaskExecutor: NewMockSideEffectTaskExecutor[any, TestOutboundSideEffectTask](controller),
mockPureTaskValidator: NewMockTaskValidator[any, *TestPureTask](controller),
mockPureTaskExecutor: NewMockPureTaskExecutor[any, *TestPureTask](controller),
mockSideEffectTaskHandler: NewMockSideEffectTaskHandler[any, *TestSideEffectTask](controller),
mockDiscardableSideEffectHandler: NewMockSideEffectTaskHandler[any, *TestDiscardableSideEffectTask](controller),
mockOutboundSideEffectTaskHandler: NewMockSideEffectTaskHandler[any, TestOutboundSideEffectTask](controller),
mockPureTaskHandler: NewMockPureTaskHandler[any, *TestPureTask](controller),
}
}
@@ -78,24 +50,20 @@ func (l *TestLibrary) Tasks() []*RegistrableTask {
return []*RegistrableTask{
NewRegistrableSideEffectTask(
testSideEffectTaskName,
l.mockSideEffectTaskValidator,
l.mockSideEffectTaskExecutor,
l.mockSideEffectTaskHandler,
),
NewRegistrableSideEffectTask(
testDiscardableSideEffectTaskName,
l.mockDiscardableSideEffectTaskValidator,
l.mockDiscardableSideEffectExecutor,
l.mockDiscardableSideEffectHandler,
),
NewRegistrableSideEffectTask(
// NOTE this task is registered as a struct, instead of pointer to struct.
testOutboundSideEffectTaskName,
l.mockOutboundSideEffectTaskValidator,
l.mockOutboundSideEffectTaskExecutor,
l.mockOutboundSideEffectTaskHandler,
),
NewRegistrablePureTask(
testPureTaskName,
l.mockPureTaskValidator,
l.mockPureTaskExecutor,
l.mockPureTaskHandler,
),
}
}

View File

@@ -2633,7 +2633,7 @@ func isComponentTaskExpired(
// close).
func (n *Node) EachPureTask(
referenceTime time.Time,
callback func(executor NodePureTask, taskAttributes TaskAttributes, taskInstance any) (bool, error),
callback func(handler NodePureTask, taskAttributes TaskAttributes, taskInstance any) (bool, error),
) error {
chasmContext := NewContext(context.Background(), n)
@@ -3010,7 +3010,7 @@ func (n *Node) ExecutePureTask(
}
// ValidatePureTask runs a pure task's associated validator, returning true
// if the task is valid. Intended for use by standby executors as part of
// if the task is valid. Intended for use by standby handlers as part of
// EachPureTask's callback.
// This method assumes the node's value has already been prepared (hydrated).
func (n *Node) ValidatePureTask(
@@ -3027,7 +3027,7 @@ func (n *Node) ValidatePureTask(
// ValidateSideEffectTask runs a side effect task's associated validator,
// returning the deserialized task instance if the task is valid. Intended for
// use by standby executors.
// use by standby handlers.
//
// If validation succeeds but the task is invalid, nil is returned to signify the
// task can be skipped/deleted.
@@ -3137,12 +3137,6 @@ func (n *Node) ExecuteSideEffectDiscardTask(
if err != nil {
return err
}
if !rt.HasDiscardHandler() {
return softassert.UnexpectedInternalErr(
n.logger,
"ExecuteSideEffectDiscardTask called on executor without SideEffectTaskDiscarder",
fmt.Errorf("%s", rt.fqType()))
}
return n.invokeSideEffectTaskFn(ctx, rt, executionKey, chasmTask, validate, rt.sideEffectTaskDiscardFn)
}
@@ -3215,7 +3209,7 @@ func (n *Node) invokeSideEffectTaskFn(
componentPath: taskInfo.Path,
componentInitialVT: taskInfo.ComponentInitialVersionedTransition,
// Validate the Ref only once it is accessed by the task's executor.
// Validate the Ref only once it is accessed by the task's handler.
validationFn: makeValidationFn(registrableTask, validate, taskAttributes, taskValue),
}
@@ -3267,7 +3261,7 @@ func makeValidationFn(
return err
}
// Side effect's task validator is invoked inside the task executor,
// Side effect's task validator is invoked inside the task handler,
// so the panic wrapper ExecuteSideEffectTask() will cover this case.
// Call the TaskValidator.

View File

@@ -2240,11 +2240,11 @@ func (s *nodeSuite) TestCloseTransaction_InvalidateComponentTasks() {
_, err = root.Component(mutableContext, ComponentRef{})
s.NoError(err)
s.testLibrary.mockSideEffectTaskValidator.EXPECT().
s.testLibrary.mockSideEffectTaskHandler.EXPECT().
Validate(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(false, nil).Times(2)
s.testLibrary.mockOutboundSideEffectTaskValidator.EXPECT().
s.testLibrary.mockOutboundSideEffectTaskHandler.EXPECT().
Validate(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(true, nil).Times(1)
s.testLibrary.mockPureTaskValidator.EXPECT().
s.testLibrary.mockPureTaskHandler.EXPECT().
Validate(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(false, nil).Times(2)
mutation, err := root.CloseTransaction()
@@ -2317,7 +2317,7 @@ func (s *nodeSuite) TestCloseTransaction_NewComponentTasks() {
s.NoError(err)
// Add a valid side effect task.
s.testLibrary.mockSideEffectTaskValidator.EXPECT().
s.testLibrary.mockSideEffectTaskHandler.EXPECT().
Validate(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(true, nil).Times(1)
testComponent := c.(*TestComponent)
mutableContext.AddTask(testComponent, TaskAttributes{}, &TestSideEffectTask{
@@ -2326,7 +2326,7 @@ func (s *nodeSuite) TestCloseTransaction_NewComponentTasks() {
// Add an invalid outbound side effect task.
// the invalid task should not be created.
s.testLibrary.mockOutboundSideEffectTaskValidator.EXPECT().
s.testLibrary.mockOutboundSideEffectTaskHandler.EXPECT().
Validate(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(false, nil).Times(1)
mutableContext.AddTask(
testComponent,
@@ -2335,7 +2335,7 @@ func (s *nodeSuite) TestCloseTransaction_NewComponentTasks() {
)
// Add a valid pure task.
s.testLibrary.mockPureTaskValidator.EXPECT().
s.testLibrary.mockPureTaskHandler.EXPECT().
Validate(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(true, nil).Times(1)
mutableContext.AddTask(
testComponent,
@@ -2349,7 +2349,7 @@ func (s *nodeSuite) TestCloseTransaction_NewComponentTasks() {
// Add an invalid pure task.
// the invalid task should not be created.
s.testLibrary.mockPureTaskValidator.EXPECT().
s.testLibrary.mockPureTaskHandler.EXPECT().
Validate(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(false, nil).Times(1)
mutableContext.AddTask(
testComponent,
@@ -2362,7 +2362,7 @@ func (s *nodeSuite) TestCloseTransaction_NewComponentTasks() {
)
// Add a valid outbound side effect task to a sub-component.
s.testLibrary.mockOutboundSideEffectTaskValidator.EXPECT().
s.testLibrary.mockOutboundSideEffectTaskHandler.EXPECT().
Validate(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(true, nil).Times(1)
subComponent2 := testComponent.SubComponent2.Get(mutableContext)
mutableContext.AddTask(
@@ -2787,11 +2787,11 @@ func (s *nodeSuite) TestExecuteImmediatePureTask() {
)
// One valid task, one invalid task
s.testLibrary.mockPureTaskValidator.EXPECT().
s.testLibrary.mockPureTaskHandler.EXPECT().
Validate(gomock.Any(), gomock.Any(), gomock.Eq(taskAttributes), gomock.Any()).Return(false, nil).Times(1)
s.testLibrary.mockPureTaskValidator.EXPECT().
s.testLibrary.mockPureTaskHandler.EXPECT().
Validate(gomock.Any(), gomock.Any(), gomock.Eq(taskAttributes), gomock.Any()).Return(true, nil).Times(1)
s.testLibrary.mockPureTaskExecutor.EXPECT().
s.testLibrary.mockPureTaskHandler.EXPECT().
Execute(
gomock.AssignableToTypeOf(&mutableCtx{}),
gomock.Any(),
@@ -2942,8 +2942,8 @@ func (s *nodeSuite) TestEachPureTask() {
s.NotNil(root)
processedTaskData := [][]byte{}
err = root.EachPureTask(now.Add(time.Minute), func(executor NodePureTask, taskAttributes TaskAttributes, task any) (bool, error) {
s.NotNil(executor)
err = root.EachPureTask(now.Add(time.Minute), func(handler NodePureTask, taskAttributes TaskAttributes, task any) (bool, error) {
s.NotNil(handler)
s.NotNil(taskAttributes)
testPureTask, ok := task.(*TestPureTask)
@@ -3013,7 +3013,7 @@ func (s *nodeSuite) TestExecutePureTask() {
ctx := context.Background()
expectExecute := func(result error) {
s.testLibrary.mockPureTaskExecutor.EXPECT().
s.testLibrary.mockPureTaskHandler.EXPECT().
Execute(
gomock.AssignableToTypeOf(&mutableCtx{}),
gomock.AssignableToTypeOf(&TestComponent{}),
@@ -3023,7 +3023,7 @@ func (s *nodeSuite) TestExecutePureTask() {
}
expectValidate := func(retValue bool, errValue error) {
s.testLibrary.mockPureTaskValidator.EXPECT().
s.testLibrary.mockPureTaskHandler.EXPECT().
Validate(gomock.Any(), gomock.Any(), gomock.Eq(taskAttributes), gomock.Any()).Return(retValue, errValue).Times(1)
}
@@ -3076,7 +3076,7 @@ func (s *nodeSuite) TestValidatePureTask() {
ctx := context.Background()
expectValidate := func(retValue bool, errValue error) {
s.testLibrary.mockPureTaskValidator.EXPECT().
s.testLibrary.mockPureTaskHandler.EXPECT().
Validate(gomock.Any(), gomock.Any(), gomock.Eq(taskAttributes), gomock.Any()).Return(retValue, errValue).Times(1)
}
@@ -3181,7 +3181,7 @@ func (s *nodeSuite) TestExecuteSideEffectTask() {
}
expectValidate := func(valid bool, validationErr error) {
backendValidtionFnCalled = false
s.testLibrary.mockSideEffectTaskValidator.EXPECT().Validate(
s.testLibrary.mockSideEffectTaskHandler.EXPECT().Validate(
gomock.Any(),
gomock.Any(),
gomock.Any(),
@@ -3189,7 +3189,7 @@ func (s *nodeSuite) TestExecuteSideEffectTask() {
).Return(valid, validationErr).Times(1)
}
expectExecute := func(result error) {
s.testLibrary.mockSideEffectTaskExecutor.EXPECT().
s.testLibrary.mockSideEffectTaskHandler.EXPECT().
Execute(
gomock.Any(),
gomock.Any(),
@@ -3312,16 +3312,18 @@ func (s *nodeSuite) TestExecuteSideEffectDiscardTask() {
return nil
}
s.testLibrary.mockDiscardableSideEffectTaskValidator.EXPECT().Validate(
s.testLibrary.mockDiscardableSideEffectHandler.EXPECT().Validate(
gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
).Return(true, nil).Times(1)
s.testLibrary.mockDiscardableSideEffectExecutor.discardFn = func(
s.testLibrary.mockDiscardableSideEffectHandler.EXPECT().Discard(
gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
).DoAndReturn(func(
_ context.Context, ref ComponentRef, _ TaskAttributes, _ *TestDiscardableSideEffectTask,
) error {
s.NotNil(ref.validationFn)
_, err := root.Component(chasmContext, ref)
return err
}
}).Times(1)
err := root.ExecuteSideEffectDiscardTask(ctx, executionKey, chasmTask, dummyValidationFn)
s.NoError(err)
@@ -3332,15 +3334,17 @@ func (s *nodeSuite) TestExecuteSideEffectDiscardTask() {
s.Run("InvalidTask", func() {
root, chasmTask, executionKey, ctx, chasmContext := setup()
s.testLibrary.mockDiscardableSideEffectTaskValidator.EXPECT().Validate(
s.testLibrary.mockDiscardableSideEffectHandler.EXPECT().Validate(
gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
).Return(false, nil).Times(1)
s.testLibrary.mockDiscardableSideEffectExecutor.discardFn = func(
s.testLibrary.mockDiscardableSideEffectHandler.EXPECT().Discard(
gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
).DoAndReturn(func(
_ context.Context, ref ComponentRef, _ TaskAttributes, _ *TestDiscardableSideEffectTask,
) error {
_, err := root.Component(chasmContext, ref)
return err
}
}).Times(1)
err := root.ExecuteSideEffectDiscardTask(ctx, executionKey, chasmTask, func(_ NodeBackend, _ Context, _ Component) error { return nil })
s.ErrorAs(err, new(*serviceerror.NotFound))
@@ -3350,15 +3354,17 @@ func (s *nodeSuite) TestExecuteSideEffectDiscardTask() {
root, chasmTask, executionKey, ctx, chasmContext := setup()
validationErr := errors.New("validation error")
s.testLibrary.mockDiscardableSideEffectTaskValidator.EXPECT().Validate(
s.testLibrary.mockDiscardableSideEffectHandler.EXPECT().Validate(
gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
).Return(false, validationErr).Times(1)
s.testLibrary.mockDiscardableSideEffectExecutor.discardFn = func(
s.testLibrary.mockDiscardableSideEffectHandler.EXPECT().Discard(
gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
).DoAndReturn(func(
_ context.Context, ref ComponentRef, _ TaskAttributes, _ *TestDiscardableSideEffectTask,
) error {
_, err := root.Component(chasmContext, ref)
return err
}
}).Times(1)
err := root.ExecuteSideEffectDiscardTask(
ctx, executionKey, chasmTask, func(_ NodeBackend, _ Context, _ Component) error { return nil })
@@ -3374,11 +3380,13 @@ func (s *nodeSuite) TestExecuteSideEffectDiscardTask() {
return nil
}
s.testLibrary.mockDiscardableSideEffectTaskValidator.EXPECT().Validate(
s.testLibrary.mockDiscardableSideEffectHandler.EXPECT().Validate(
gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
).Return(true, nil).Times(1)
discardErr := errors.New("discard error")
s.testLibrary.mockDiscardableSideEffectExecutor.discardFn = func(
s.testLibrary.mockDiscardableSideEffectHandler.EXPECT().Discard(
gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
).DoAndReturn(func(
_ context.Context, ref ComponentRef, _ TaskAttributes, _ *TestDiscardableSideEffectTask,
) error {
s.NotNil(ref.validationFn)
@@ -3386,7 +3394,7 @@ func (s *nodeSuite) TestExecuteSideEffectDiscardTask() {
return err
}
return discardErr
}
}).Times(1)
err := root.ExecuteSideEffectDiscardTask(ctx, executionKey, chasmTask, dummyValidationFn)
s.ErrorIs(err, discardErr)
@@ -3394,23 +3402,6 @@ func (s *nodeSuite) TestExecuteSideEffectDiscardTask() {
})
}
func (s *nodeSuite) TestHasDiscardHandler() {
// The discardable side effect task has an executor that implements SideEffectTaskDiscarder.
discardableTask, ok := s.registry.TaskByID(testDiscardableSideEffectTaskTypeID)
s.True(ok)
s.True(discardableTask.HasDiscardHandler())
// The regular side effect task does not.
regularTask, ok := s.registry.TaskByID(testSideEffectTaskTypeID)
s.True(ok)
s.False(regularTask.HasDiscardHandler())
// Pure tasks do not.
pureTask, ok := s.registry.TaskByID(testPureTaskTypeID)
s.True(ok)
s.False(pureTask.HasDiscardHandler())
}
func (s *nodeSuite) TestValidateSideEffectTask() {
taskInfo := &persistencespb.ChasmTaskInfo{
ComponentInitialVersionedTransition: &persistencespb.VersionedTransition{
@@ -3447,7 +3438,7 @@ func (s *nodeSuite) TestValidateSideEffectTask() {
ctx := NewEngineContext(context.Background(), mockEngine)
expectValidate := func(componentType any, retValue bool, errValue error) {
s.testLibrary.mockSideEffectTaskValidator.EXPECT().
s.testLibrary.mockSideEffectTaskHandler.EXPECT().
Validate(
gomock.AssignableToTypeOf((*immutableCtx)(nil)),
gomock.AssignableToTypeOf(componentType),

View File

@@ -330,7 +330,9 @@ func (v *Visibility) generateTask(
)
}
type visibilityTaskHandler struct{}
type visibilityTaskHandler struct {
SideEffectTaskHandlerBase[*persistencespb.ChasmVisibilityTaskData]
}
var defaultVisibilityTaskHandler = &visibilityTaskHandler{}
@@ -350,5 +352,5 @@ func (v *visibilityTaskHandler) Execute(
_ *persistencespb.ChasmVisibilityTaskData,
) error {
//nolint:forbidigo
panic("chasm visibilityTaskExecutor should not be called directly")
panic("chasm visibilityTaskHandler should not be called directly")
}

View File

@@ -2,8 +2,8 @@ package history
import (
"context"
"errors"
"go.temporal.io/api/serviceerror"
enumsspb "go.temporal.io/server/api/enums/v1"
"go.temporal.io/server/chasm"
"go.temporal.io/server/client"
@@ -83,9 +83,9 @@ func executeChasmSideEffectTask(
}
// discardChasmSideEffectTask handles discard of a CHASM side effect task on standby. It first checks if the execution
// still exists on the source (active) cluster — if gone, it silently drops the task by return nil. If the execution
// still exists and the task's executor implements SideEffectTaskDiscarder, it calls the discard handler. Otherwise, it
// returns ErrTaskDiscarded with a warning log.
// still exists on the source (active) cluster — if gone, it silently drops the task by returning nil. If the execution
// still exists, it calls the handler's Discard method. If Discard returns ErrTaskDiscarded (the default from
// SideEffectTaskHandlerBase), it logs a warning and returns consts.ErrTaskDiscarded.
func discardChasmSideEffectTask(
ctx context.Context,
engine chasm.Engine,
@@ -110,15 +110,6 @@ func discardChasmSideEffectTask(
return nil
}
rt, ok := registry.TaskByID(task.Info.TypeId)
if !ok {
return serviceerror.NewInternal("unknown task type id")
}
if !rt.HasDiscardHandler() {
logger.Warn("Discarding standby CHASM task due to task being pending for too long.", tag.Task(task))
return consts.ErrTaskDiscarded
}
executionKey := chasm.ExecutionKey{
NamespaceID: task.NamespaceID,
BusinessID: task.WorkflowID,
@@ -140,10 +131,15 @@ func discardChasmSideEffectTask(
}
engineCtx := chasm.NewEngineContext(ctx, engine)
return tree.ExecuteSideEffectDiscardTask(
err := tree.ExecuteSideEffectDiscardTask(
engineCtx,
executionKey,
task,
validate,
)
if errors.Is(err, chasm.ErrTaskDiscarded) {
logger.Warn("Discarding standby CHASM task due to task being pending for too long.", tag.Task(task))
return consts.ErrTaskDiscarded
}
return err
}

View File

@@ -6,8 +6,8 @@ import (
"go.temporal.io/server/chasm"
)
// discardableTaskTestLibrary is a minimal CHASM library that registers a side-effect task whose executor implements
// SideEffectTaskDiscarder, used for testing discard paths in standby task executors.
// discardableTaskTestLibrary is a minimal CHASM library that registers a side-effect task whose handler has a custom
// Discard implementation, used for testing discard paths in standby task executors.
type discardableTaskTestLibrary struct {
chasm.UnimplementedLibrary
}
@@ -18,32 +18,31 @@ func (l *discardableTaskTestLibrary) Tasks() []*chasm.RegistrableTask {
return []*chasm.RegistrableTask{
chasm.NewRegistrableSideEffectTask(
"discard_task",
&discardableTestTaskValidator{},
&discardableTestTaskExecutor{},
&discardableTestTaskHandler{},
),
}
}
type discardableTestTask struct{}
type discardableTestTaskValidator struct{}
type discardableTestTaskHandler struct {
chasm.SideEffectTaskHandlerBase[*discardableTestTask]
}
func (v *discardableTestTaskValidator) Validate(_ chasm.Context, _ any, _ chasm.TaskAttributes, _ *discardableTestTask) (bool, error) {
func (e *discardableTestTaskHandler) Validate(_ chasm.Context, _ any, _ chasm.TaskAttributes, _ *discardableTestTask) (bool, error) {
return true, nil
}
type discardableTestTaskExecutor struct{}
func (e *discardableTestTaskExecutor) Execute(_ context.Context, _ chasm.ComponentRef, _ chasm.TaskAttributes, _ *discardableTestTask) error {
func (e *discardableTestTaskHandler) Execute(_ context.Context, _ chasm.ComponentRef, _ chasm.TaskAttributes, _ *discardableTestTask) error {
return nil
}
func (e *discardableTestTaskExecutor) Discard(_ context.Context, _ chasm.ComponentRef, _ chasm.TaskAttributes, _ *discardableTestTask) error {
func (e *discardableTestTaskHandler) Discard(_ context.Context, _ chasm.ComponentRef, _ chasm.TaskAttributes, _ *discardableTestTask) error {
return nil
}
// nonDiscardableTaskTestLibrary is a minimal CHASM library that registers a side-effect task whose executor does NOT
// implement SideEffectTaskDiscarder.
// nonDiscardableTaskTestLibrary is a minimal CHASM library that registers a side-effect task whose handler uses the
// default Discard from SideEffectTaskHandlerBase (returns ErrTaskDiscarded).
type nonDiscardableTaskTestLibrary struct {
chasm.UnimplementedLibrary
}
@@ -54,22 +53,21 @@ func (l *nonDiscardableTaskTestLibrary) Tasks() []*chasm.RegistrableTask {
return []*chasm.RegistrableTask{
chasm.NewRegistrableSideEffectTask(
"non_discard_task",
&nonDiscardableTestTaskValidator{},
&nonDiscardableTestTaskExecutor{},
&nonDiscardableTestTaskHandler{},
),
}
}
type nonDiscardableTestTask struct{}
type nonDiscardableTestTaskValidator struct{}
type nonDiscardableTestTaskHandler struct {
chasm.SideEffectTaskHandlerBase[*nonDiscardableTestTask]
}
func (v *nonDiscardableTestTaskValidator) Validate(_ chasm.Context, _ any, _ chasm.TaskAttributes, _ *nonDiscardableTestTask) (bool, error) {
func (e *nonDiscardableTestTaskHandler) Validate(_ chasm.Context, _ any, _ chasm.TaskAttributes, _ *nonDiscardableTestTask) (bool, error) {
return true, nil
}
type nonDiscardableTestTaskExecutor struct{}
func (e *nonDiscardableTestTaskExecutor) Execute(_ context.Context, _ chasm.ComponentRef, _ chasm.TaskAttributes, _ *nonDiscardableTestTask) error {
func (e *nonDiscardableTestTaskHandler) Execute(_ context.Context, _ chasm.ComponentRef, _ chasm.TaskAttributes, _ *nonDiscardableTestTask) error {
return nil
}

View File

@@ -350,8 +350,12 @@ func (s *outboundQueueStandbyTaskExecutorSuite) TestExecute_ChasmTask_Discard()
})
s.Run("WithoutHandler", func() {
executor, executable := setupDiscard(&nonDiscardableTaskTestLibrary{}, "non_discard_task", func(_ *historyi.MockChasmTree) {})
// Without a discard handler, discardChasmSideEffectTask returns ErrTaskDiscarded directly.
executor, executable := setupDiscard(&nonDiscardableTaskTestLibrary{}, "non_discard_task", func(tree *historyi.MockChasmTree) {
// The default Discard (from SideEffectTaskHandlerBase) returns ErrTaskDiscarded.
tree.EXPECT().ExecuteSideEffectDiscardTask(
gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
).Return(chasm.ErrTaskDiscarded).Times(1)
})
result := executor.Execute(context.Background(), executable)
s.ErrorIs(result.ExecutionErr, consts.ErrTaskDiscarded)
s.False(result.ExecutedAsActive)

View File

@@ -2343,7 +2343,11 @@ func (s *timerQueueStandbyTaskExecutorSuite) TestExecuteChasmSideEffectTimerTask
})
s.Run("WithoutHandler", func() {
executor, task := setupDiscard(&nonDiscardableTaskTestLibrary{}, "non_discard_task", func(_ *historyi.MockChasmTree) {})
executor, task := setupDiscard(&nonDiscardableTaskTestLibrary{}, "non_discard_task", func(tree *historyi.MockChasmTree) {
tree.EXPECT().ExecuteSideEffectDiscardTask(
gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
).Return(chasm.ErrTaskDiscarded).Times(1)
})
resp := executor.Execute(context.Background(), s.newTaskExecutable(task))
s.NotNil(resp)
s.ErrorIs(resp.ExecutionErr, consts.ErrTaskDiscarded)

View File

@@ -1421,7 +1421,11 @@ func (s *transferQueueStandbyTaskExecutorSuite) TestExecuteChasmSideEffectTransf
})
s.Run("WithoutHandler", func() {
executor, task := setupDiscard(&nonDiscardableTaskTestLibrary{}, "non_discard_task", func(_ *historyi.MockChasmTree) {})
executor, task := setupDiscard(&nonDiscardableTaskTestLibrary{}, "non_discard_task", func(tree *historyi.MockChasmTree) {
tree.EXPECT().ExecuteSideEffectDiscardTask(
gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
).Return(chasm.ErrTaskDiscarded).Times(1)
})
resp := executor.Execute(context.Background(), s.newTaskExecutable(task))
s.NotNil(resp)
s.ErrorIs(resp.ExecutionErr, consts.ErrTaskDiscarded)