-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
(Aws-Stepfunctions): (Issue with catch block in Custom State) #25798
Comments
Could you post the full example where you add the errorHandlerLambda to the definition? Also, why not use DynamoPutItem? |
I just gave you an example to recreate the issue. The custom step example is the one that is present in the doc. I just added the catch block to it.
The custom step in our state machine is used for starting a sagemaker training job. Though we started of by using the "SageMakerCreateTrainingJob", this did not support kms keys and hence we had to switch to a custom step and now, we want to add error handling to it and are facing difficulties with the catch block. |
@nivedita104 were you able to find a work-around for this? |
I had the same issue and I switched from using CustomState to new CfnStateMachine(this, 'MagicWorkflow', {
roleArn,
definition, // json definition that you can either write manually or export from AWS Workflow Studio
}) I would love to know if there's a better way. |
We're having exactly the same issue. We have a state machine the we'd like to define using chained CustomStates similar to the original example above but Catch is directing to another CustomState that it cannot find. |
I also ran into this issue, and found this workaround (Python): custom_state._add_choice(sfn.Condition.is_null("$"), handler_state) This is hacky, but it works because State.addChoice() basically has the same functionality as State._addCatch(), which is what subclasses call to add a catch handler to the state graph. I think the TypeScript equivalent would be this: customState.addChoice(sfn.Condition.is_null("$"), handlerState) You might also be able to call |
For TypeScript I got around this with this hack:
|
@mschwieb Does that actually add |
yes, |
Here's a reasonably minimal set of test cases to show the issue. Both tests with
import * as cdk from 'aws-cdk-lib';
import { Template, Match, Capture } from 'aws-cdk-lib/assertions';
import { AppendStack, InlineStack } from '../lib/cdk-ts-stack';
describe.each([InlineStack, AppendStack])("%p", (clazz) => {
it.each([true, false])("adds ErrorHandler to the definition when addNext=%p", (addNext) => {
// synth the app
const app = new cdk.App();
const stack = new clazz(app, "Stack", addNext);
const template = Template.fromStack(stack);
// capture the states definition
const statesCapture = new Capture();
template.hasResourceProperties("AWS::StepFunctions::StateMachine", {
DefinitionString: Match.serializedJson({
StartAt: "CustomState",
States: statesCapture,
})
})
// assert ErrorHandler was added to the definition
expect(statesCapture.asObject()).toHaveProperty("ErrorHandler");
})
})
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as sfn from 'aws-cdk-lib/aws-stepfunctions';
// add the catcher in the state definition
export class InlineStack extends cdk.Stack {
constructor(scope: Construct, id: string, addNext: boolean) {
super(scope, id);
let errorHandler = new sfn.Pass(this, "ErrorHandler");
// this could be any Task, Map, or Parallel which can't be represented with existing constructs
let customState = new sfn.CustomState(this, "CustomState", {
stateJson: {
Type: "Parallel",
Branches: [],
Catch: {
ErrorEquals: ["States.ALL"],
Next: errorHandler.id,
},
}
})
new sfn.StateMachine(this, "StateMachine", {
definitionBody: sfn.DefinitionBody.fromChainable(
// for the test, conditionally add ErrorHandler as the next state after CustomState
addNext ? customState.next(errorHandler) : customState
)
})
}
}
// add the catcher after defining the state by modifying private properties
export class AppendStack extends cdk.Stack {
constructor(scope: Construct, id: string, addNext: boolean) {
super(scope, id);
let errorHandler = new sfn.Pass(this, "ErrorHandler");
// this could be any Task, Map, or Parallel which can't be represented with existing constructs
let customState = new sfn.CustomState(this, "CustomState", {
stateJson: {
Type: "Parallel",
Branches: [],
}
})
// @ts-ignore
customState.stateJson = {
// @ts-ignore
...customState.stateJson,
Catch: {
ErrorEquals: ["States.ALL"],
Next: errorHandler.id,
}
}
new sfn.StateMachine(this, "StateMachine", {
definitionBody: sfn.DefinitionBody.fromChainable(
// for the test, conditionally add ErrorHandler as the next state after CustomState
addNext ? customState.next(errorHandler) : customState
)
})
}
}
|
I tried above solutions, but still did not solve my problem.
...
export class ChildStateMachine extends Construct {
readonly stateMachine : StateMachine;
constructor(scope: Construct, id: string, props: ... ) {
super(scope, id);
// My CustomState
const customState = new sfn.CustomState( ... )
// Define a state machine
this.stateMachine = new sfn.StateMachine(this, "ChildStateMachine", {
definitionBody: sfn.DefinitionBody.fromChainable(customState),
role: stateMachineRole,
timeout: Duration.hours(1),
});
}
}
...
export class ParentStateMachine extends Construct {
readonly stateMachine : StateMachine;
constructor(scope: Construct, id: string, props: ... ) {
super(scope, id);
// Include the state machine in a Task state with callback pattern
const task = new sfntasks.StepFunctionsStartExecution(this, 'ChildTask', {
stateMachine: childStateMachine,
integrationPattern: sfn.IntegrationPattern.RUN_JOB,
taskTimeout: sfn.Timeout.duration(Duration.hours(1)),
});
// add Catch
// errorHandlingLambdaInvoke is my sfntasks.LambdaInvoke which has sfn.Fail state in next
// Use your error in props errors.
task.addCatch(errorHandlingLambdaInvoke, {
errors: ['States.TaskFailed', ... ],
resultPath: '$.errorInfo',
});
// Define a second state machine with the Task state above
this.stateMachine = new sfn.StateMachine(this, 'ParentStateMachine', {
definition: task,
role: stateMachineRole,
timeout: Duration.hours(1),
});
}
}
...
const child = new ChildStateMachine( ... );
const parent = new ParentStateMachine(this, "ParentStateMachine", { child.stateMachine, ... };
... |
This worked for me Suppose there's a chain able const failurePath: IChainable = ...; And there exists a const customState = new CustomState(
scope,
"MyCustomState",
{
stateJson: {
...
"Catch": [
...,
"Next": failurePath.startState.stateId
],
...
}
}
); After all that, our failure path might not find its way into the final state machine definition body unless some other state calls the So we can just take advantage of the core // Sometimes the failure path is not added to the state machine definition, unless _addCatch is called
// https://github.com/aws/aws-cdk/issues/25798#issuecomment-1749916284
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
customState._addCatch(failurePath.startState) // intentionally omit the CatchProps because my CustomState already sets it up Still hacky, sadly... but arguably close to the valid solution. AWS folks, please just implement the |
I've run into the exact same issue. Thanks to everyone who has suggested workarounds, but I hate resorting to hacks. Would love to see more robust support in CDK. |
Adds a public `addCatch` method to the stepfunctions `CustomState` state. Closes #25798. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
|
Describe the bug
I have a use case where I am using a custom step in one of our state machines and in the catch block, i want to add a new step that is not previously referenced in the state machine. When I tried deploying this, the statemachine throws an error that
"Invalid State Machine Definition: 'MISSING_TRANSITION_TARGET: Missing 'Next' target: testLambda at /States/CreateTrainingJobWithou
tItemsDataset/Catch[0]/Next, MISSING_TRANSITION_TARGET: Missing 'Next' target: testLambda at /States/CreateTrainingJobWithItemsDataset/Catch[0]/Next' (Service: AWSSte
pFunctions; Status Code: 400; Error Code: InvalidDefinition; Request ID: 77eb25f8-02b7-49b3-8cdd-b1ff1921ec5f; Proxy: null)" (RequestToken: 92e222c1-409c-cfb0-558a-15
f75529c9c7, HandlerErrorCode: InvalidRequest)"
Here, testLambda was the step I introduced in the catch block of the custom step.
I checked the generated template it was not generating the "testLambda" step. Hence i tried to use the .addCatch() function call that we use with the other steps and i got an error that the .addCatch handler is not supported for custom steps.
What is the solution for this? Can we not add new steps for catch block in the custom steps or am i missing something here?
Expected Behavior
The step mentioned in the Next step of the catch block of the custom step props should also get generated in the cfn template.
Current Behavior
"Invalid State Machine Definition: 'MISSING_TRANSITION_TARGET: Missing 'Next' target: testLambda at /States/CreateTrainingJobWithou
tItemsDataset/Catch[0]/Next, MISSING_TRANSITION_TARGET: Missing 'Next' target: testLambda at /States/CreateTrainingJobWithItemsDataset/Catch[0]/Next' (Service: AWSSte
pFunctions; Status Code: 400; Error Code: InvalidDefinition; Request ID: 77eb25f8-02b7-49b3-8cdd-b1ff1921ec5f; Proxy: null)" (RequestToken: 92e222c1-409c-cfb0-558a-15
f75529c9c7, HandlerErrorCode: InvalidRequest)"
Reproduction Steps
Default Custom state example in docs:
Now, we add a new pass state and catch block to the custom step :
This will throw the error I mentioned above.
I cannot add the errorHandlerLambda to the step function definition as it will only be used as the next step in case the custom step fails.
Possible Solution
No response
Additional Information/Context
No response
CDK CLI Version
"aws-cdk-lib": "^2.60.0", CDKBuild = 4.x;
Framework Version
No response
Node.js Version
12.x
OS
Linux
Language
Typescript
Language Version
No response
Other information
No response
The text was updated successfully, but these errors were encountered: