Skip to content

Commit

Permalink
feat(sdk-dotnet): add exception hablers in wf thread (#1325)
Browse files Browse the repository at this point in the history
  • Loading branch information
KarlaCarvajal authored Feb 24, 2025
1 parent a0484cf commit 9c68b34
Show file tree
Hide file tree
Showing 2 changed files with 188 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,59 +44,96 @@ void EntryPointAction(WorkflowThread wf)

var compiledWfThread = workflowThread.Compile();

var expectedSpec = new ThreadSpec();
var entrypoint = new Node
{
Entrypoint = new EntrypointNode(),
OutgoingEdges =
var expectedSpec = GetExpectedThreadSpec(
new FailureHandlerDef
{
new Edge { SinkNodeName = "1-fail-TASK" }
}
};
HandlerSpecName = "exn-handler-1-fail-TASK-FAILURE_TYPE_ERROR",
AnyFailureOfType = FailureHandlerDef.Types.LHFailureType.FailureTypeError
});

var failTask = new Node
var expectedNumberOfNodes = numberOfEntrypointNodes + numberOfExitNodes + numberOfTasks;
Assert.Equal(expectedNumberOfNodes, compiledWfThread.Nodes.Count);
Assert.Equal(expectedSpec, compiledWfThread);
}

[Fact]
public void WfThread_WithSpecificError_ShouldCompileErrorHandling()
{
var numberOfExitNodes = 1;
var numberOfEntrypointNodes = 1;
var numberOfTasks = 2;
var workflowName = "TestWorkflow";
var mockParentWorkflow = new Mock<Sdk.Workflow.Spec.Workflow>(workflowName, _action);

void EntryPointAction(WorkflowThread wf)
{
Task = new TaskNode
{
TaskDefId = new TaskDefId { Name = "fail" }
},
OutgoingEdges = { new Edge { SinkNodeName = "2-my-task-TASK" } },
FailureHandlers =
{
new FailureHandlerDef
NodeOutput node = wf.Execute("fail");
wf.HandleError(
node,
LHErrorType.Timeout,
handler =>
{
HandlerSpecName = "exn-handler-1-fail-TASK-FAILURE_TYPE_ERROR",
AnyFailureOfType = FailureHandlerDef.Types.LHFailureType.FailureTypeError
handler.Execute("my-task");
}
}
};
);
wf.Execute("my-task");
}
var workflowThread = new WorkflowThread(mockParentWorkflow.Object, EntryPointAction);

var myTask = new Node
{
Task = new TaskNode
var compiledWfThread = workflowThread.Compile();

var expectedSpec = GetExpectedThreadSpec(
new FailureHandlerDef
{
TaskDefId = new TaskDefId { Name = "my-task" }
},
OutgoingEdges = { new Edge { SinkNodeName = "3-exit-EXIT" } }
};
HandlerSpecName = "exn-handler-1-fail-TASK-TIMEOUT",
SpecificFailure = "TIMEOUT"
});

var exitNode = new Node
var expectedNumberOfNodes = numberOfEntrypointNodes + numberOfExitNodes + numberOfTasks;
Assert.Equal(expectedNumberOfNodes, compiledWfThread.Nodes.Count);
Assert.Equal(expectedSpec, compiledWfThread);
}

[Fact]
public void WfThread_WithExceptionName_ShouldCompileExceptionHandling()
{
var numberOfExitNodes = 1;
var numberOfEntrypointNodes = 1;
var numberOfTasks = 2;
var workflowName = "TestWorkflow";
var mockParentWorkflow = new Mock<Sdk.Workflow.Spec.Workflow>(workflowName, _action);

void EntryPointAction(WorkflowThread wf)
{
Exit = new ExitNode()
};
NodeOutput node = wf.Execute("fail");
wf.HandleException(
node,
"any-business-exception",
handler =>
{
handler.Execute("my-task");
}
);
wf.Execute("my-task");
}
var workflowThread = new WorkflowThread(mockParentWorkflow.Object, EntryPointAction);

expectedSpec.Nodes.Add("0-entrypoint-ENTRYPOINT", entrypoint);
expectedSpec.Nodes.Add("1-fail-TASK", failTask);
expectedSpec.Nodes.Add("2-my-task-TASK", myTask);
expectedSpec.Nodes.Add("3-exit-EXIT", exitNode);
var compiledWfThread = workflowThread.Compile();

var expectedSpec = GetExpectedThreadSpec(
new FailureHandlerDef
{
HandlerSpecName = "exn-handler-1-fail-TASK-any-business-exception",
SpecificFailure = "any-business-exception"
});

var expectedNumberOfNodes = numberOfEntrypointNodes + numberOfExitNodes + numberOfTasks;
Assert.Equal(expectedNumberOfNodes, compiledWfThread.Nodes.Count);
Assert.Equal(expectedSpec, compiledWfThread);
}

}
[Fact]
public void WfThread_WithSpecificError_ShouldCompileErrorHandling()
public void WfThread_WithoutExceptionName_ShouldCompileExceptionHandling()
{
var numberOfExitNodes = 1;
var numberOfEntrypointNodes = 1;
Expand All @@ -107,9 +144,8 @@ public void WfThread_WithSpecificError_ShouldCompileErrorHandling()
void EntryPointAction(WorkflowThread wf)
{
NodeOutput node = wf.Execute("fail");
wf.HandleError(
wf.HandleException(
node,
LHErrorType.Timeout,
handler =>
{
handler.Execute("my-task");
Expand All @@ -120,7 +156,57 @@ void EntryPointAction(WorkflowThread wf)
var workflowThread = new WorkflowThread(mockParentWorkflow.Object, EntryPointAction);

var compiledWfThread = workflowThread.Compile();

var expectedSpec = GetExpectedThreadSpec(
new FailureHandlerDef
{
HandlerSpecName = "exn-handler-1-fail-TASK",
AnyFailureOfType = FailureHandlerDef.Types.LHFailureType.FailureTypeException
});

var expectedNumberOfNodes = numberOfEntrypointNodes + numberOfExitNodes + numberOfTasks;
Assert.Equal(expectedNumberOfNodes, compiledWfThread.Nodes.Count);
Assert.Equal(expectedSpec, compiledWfThread);
}

[Fact]
public void WfThread_WithBusinessException_ShouldCompileAnyFailureHandling()
{
var numberOfExitNodes = 1;
var numberOfEntrypointNodes = 1;
var numberOfTasks = 2;
var workflowName = "TestWorkflow";
var mockParentWorkflow = new Mock<Sdk.Workflow.Spec.Workflow>(workflowName, _action);

void EntryPointAction(WorkflowThread wf)
{
NodeOutput node = wf.Execute("fail");
wf.HandleAnyFailure(
node,
handler =>
{
handler.Execute("my-task");
}
);
wf.Execute("my-task");
}
var workflowThread = new WorkflowThread(mockParentWorkflow.Object, EntryPointAction);

var compiledWfThread = workflowThread.Compile();

var expectedSpec = GetExpectedThreadSpec(
new FailureHandlerDef
{
HandlerSpecName = "exn-handler-1-fail-TASK-any-failure",
});

var expectedNumberOfNodes = numberOfEntrypointNodes + numberOfExitNodes + numberOfTasks;
Assert.Equal(expectedNumberOfNodes, compiledWfThread.Nodes.Count);
Assert.Equal(expectedSpec, compiledWfThread);
}

private ThreadSpec GetExpectedThreadSpec(FailureHandlerDef failureHandlerDef)
{
var expectedSpec = new ThreadSpec();
var entrypoint = new Node
{
Expand All @@ -140,11 +226,7 @@ void EntryPointAction(WorkflowThread wf)
OutgoingEdges = { new Edge { SinkNodeName = "2-my-task-TASK" } },
FailureHandlers =
{
new FailureHandlerDef
{
HandlerSpecName = "exn-handler-1-fail-TASK-TIMEOUT",
SpecificFailure = "TIMEOUT"
}
failureHandlerDef
}
};

Expand All @@ -167,8 +249,6 @@ void EntryPointAction(WorkflowThread wf)
expectedSpec.Nodes.Add("2-my-task-TASK", myTask);
expectedSpec.Nodes.Add("3-exit-EXIT", exitNode);

var expectedNumberOfNodes = numberOfEntrypointNodes + numberOfExitNodes + numberOfTasks;
Assert.Equal(expectedNumberOfNodes, compiledWfThread.Nodes.Count);
Assert.Equal(expectedSpec, compiledWfThread);
return expectedSpec;
}
}
62 changes: 60 additions & 2 deletions sdk-dotnet/LittleHorse.Sdk/Workflow/Spec/WorkflowThread.cs
Original file line number Diff line number Diff line change
Expand Up @@ -661,9 +661,10 @@ public void Fail(string failureName, string message)
Fail(null, failureName, message);
}

private FailureHandlerDef BuildFailureHandlerDef(NodeOutput node, string error, Action<WorkflowThread> handler)
private FailureHandlerDef BuildFailureHandlerDef(NodeOutput node, string error, Action<WorkflowThread> handler)
{
string threadName = $"exn-handler-{node.NodeName}-{error}";
string suffix = !string.IsNullOrEmpty(error) ? $"-{error}" : string.Empty;
string threadName = $"exn-handler-{node.NodeName}{suffix}";

threadName = _parent.AddSubThread(threadName, handler);

Expand All @@ -677,4 +678,61 @@ private void AddFailureHandlerDef(FailureHandlerDef handlerDef, NodeOutput node)

lastNode.FailureHandlers.Add(handlerDef);
}

/// <summary>
/// Attaches an Exception Handler to the specified NodeOutput, enabling it to handle specific
/// types of exceptions as defined by the 'exceptionName' parameter. If 'exceptionName' is null,
/// the handler will catch all exceptions.
/// </summary>
/// <param name="node">
/// The NodeOutput instance to which the Exception Handler will be attached.
/// </param>
/// <param name="exceptionName">
/// The name of the specific exception to handle. If set to null, the handler will catch all exceptions.
/// </param>
/// <param name="handler">
/// A ThreadFunction defining a ThreadSpec that specifies how to handle the exception.
/// </param>
public void HandleException(NodeOutput node, String exceptionName, Action<WorkflowThread> handler)
{
CheckIfWorkflowThreadIsActive();
var handlerDef = BuildFailureHandlerDef(node, exceptionName, handler);
handlerDef.SpecificFailure = exceptionName;
AddFailureHandlerDef(handlerDef, node);
}

/// <summary>
/// Attaches an Exception Handler to the specified NodeOutput, enabling it to handle any
/// types of exceptions.
/// </summary>
/// <param name="node">
/// The NodeOutput instance to which the Exception Handler will be attached.
/// </param>
/// <param name="handler">
/// A ThreadFunction defining a ThreadSpec that specifies how to handle the exception.
/// </param>
public void HandleException(NodeOutput node, Action<WorkflowThread> handler)
{
CheckIfWorkflowThreadIsActive();
var handlerDef = BuildFailureHandlerDef(node, null!, handler);
handlerDef.AnyFailureOfType = FailureHandlerDef.Types.LHFailureType.FailureTypeException;
AddFailureHandlerDef(handlerDef, node);
}

/// <summary>
/// Attaches a Failure Handler to the specified NodeOutput, allowing it manages any type of errors or exceptions.
/// types of exceptions.
/// </summary>
/// <param name="node">
/// The NodeOutput instance to which the Error Handler will be attached.
/// </param>
/// <param name="handler">
/// A ThreadFunction defining a ThreadSpec that specifies how to handle the error.
/// </param>
public void HandleAnyFailure(NodeOutput node, Action<WorkflowThread> handler)
{
CheckIfWorkflowThreadIsActive();
var handlerDef = BuildFailureHandlerDef(node, "any-failure", handler);
AddFailureHandlerDef(handlerDef, node);
}
}

0 comments on commit 9c68b34

Please sign in to comment.