Files
tasks/chasm/lib/nexusoperation/cancellation_statemachine.go
Roey BermanandGitHub 1b972b2f28 Nexus workflow operation cancellation for CHASM (#9915)
## What changed?

Full implementation of the cancellation path for workflow Nexus
operations on CHASM.
Including:
- State machine logic
- Component logic
- Workflow event handlers
- Integration into workflows
- Executors

I also ended up doing quite a bit of refactoring to reuse as much of the
code as possible across the cancellation and invocation executors.
Also added missing test coverage for the invocation executors.

I tried to structure this into separate commits that make reviews easy
but ended up with a lot of fixes in later commits that touched code in
earlier commits. Best to review in one piece.
2026-04-13 10:48:26 -07:00

125 lines
4.3 KiB
Go

package nexusoperation
import (
failurepb "go.temporal.io/api/failure/v1"
"go.temporal.io/server/chasm"
"go.temporal.io/server/chasm/lib/nexusoperation/gen/nexusoperationpb/v1"
"go.temporal.io/server/common/backoff"
"google.golang.org/protobuf/types/known/timestamppb"
)
// EventCancellationScheduled is triggered when cancellation is meant to be scheduled for the first time - immediately
// after it has been requested.
type EventCancellationScheduled struct {
// Destination is the endpoint name for the cancellation task.
// Must be provided by the caller because ParentPtr is not available during inline creation.
Destination string
}
var TransitionCancellationScheduled = chasm.NewTransition(
[]nexusoperationpb.CancellationStatus{nexusoperationpb.CANCELLATION_STATUS_UNSPECIFIED},
nexusoperationpb.CANCELLATION_STATUS_SCHEDULED,
func(c *Cancellation, ctx chasm.MutableContext, event EventCancellationScheduled) error {
c.Attempt++
ctx.AddTask(c, chasm.TaskAttributes{
Destination: event.Destination,
}, &nexusoperationpb.CancellationTask{
Attempt: c.Attempt,
})
return nil
},
)
// EventCancellationRescheduled is triggered when cancellation is meant to be rescheduled after backing off from a
// previous attempt.
type EventCancellationRescheduled struct {
}
var transitionCancellationRescheduled = chasm.NewTransition(
[]nexusoperationpb.CancellationStatus{nexusoperationpb.CANCELLATION_STATUS_BACKING_OFF},
nexusoperationpb.CANCELLATION_STATUS_SCHEDULED,
func(c *Cancellation, ctx chasm.MutableContext, event EventCancellationRescheduled) error {
c.Attempt++
c.NextAttemptScheduleTime = nil
ctx.AddTask(c, chasm.TaskAttributes{
Destination: c.Operation.Get(ctx).GetEndpoint(),
}, &nexusoperationpb.CancellationTask{
Attempt: c.Attempt,
})
return nil
},
)
// EventCancellationAttemptFailed is triggered when a cancellation attempt is failed with a retryable error.
type EventCancellationAttemptFailed struct {
Failure *failurepb.Failure
RetryPolicy backoff.RetryPolicy
}
var transitionCancellationAttemptFailed = chasm.NewTransition(
[]nexusoperationpb.CancellationStatus{nexusoperationpb.CANCELLATION_STATUS_SCHEDULED},
nexusoperationpb.CANCELLATION_STATUS_BACKING_OFF,
func(c *Cancellation, ctx chasm.MutableContext, event EventCancellationAttemptFailed) error {
currentTime := ctx.Now(c)
c.LastAttemptCompleteTime = timestamppb.New(currentTime)
c.LastAttemptFailure = event.Failure
nextDelay := event.RetryPolicy.ComputeNextDelay(0, int(c.Attempt), nil)
nextAttemptScheduleTime := currentTime.Add(nextDelay)
c.NextAttemptScheduleTime = timestamppb.New(nextAttemptScheduleTime)
ctx.AddTask(c, chasm.TaskAttributes{
ScheduledTime: nextAttemptScheduleTime,
}, &nexusoperationpb.CancellationBackoffTask{
Attempt: c.Attempt,
})
return nil
},
)
// EventCancellationFailed is triggered when a cancellation attempt is failed with a non retryable error.
type EventCancellationFailed struct {
Failure *failurepb.Failure
}
var TransitionCancellationFailed = chasm.NewTransition(
[]nexusoperationpb.CancellationStatus{
// We can immediately transition to failed since we don't know how to send a cancellation request for an
// unstarted operation.
// TODO: This doesn't seem to happen in either the HSM or CHASM implementations.
nexusoperationpb.CANCELLATION_STATUS_UNSPECIFIED,
nexusoperationpb.CANCELLATION_STATUS_SCHEDULED,
},
nexusoperationpb.CANCELLATION_STATUS_FAILED,
func(c *Cancellation, ctx chasm.MutableContext, event EventCancellationFailed) error {
currentTime := ctx.Now(c)
c.LastAttemptCompleteTime = timestamppb.New(currentTime)
c.LastAttemptFailure = event.Failure
// Terminal state - no tasks to emit.
return nil
},
)
// EventCancellationSucceeded is triggered when a cancellation attempt succeeds.
type EventCancellationSucceeded struct {
}
var TransitionCancellationSucceeded = chasm.NewTransition(
[]nexusoperationpb.CancellationStatus{nexusoperationpb.CANCELLATION_STATUS_SCHEDULED},
nexusoperationpb.CANCELLATION_STATUS_SUCCEEDED,
func(c *Cancellation, ctx chasm.MutableContext, event EventCancellationSucceeded) error {
currentTime := ctx.Now(c)
c.LastAttemptCompleteTime = timestamppb.New(currentTime)
c.LastAttemptFailure = nil
// Terminal state - no tasks to emit.
return nil
},
)