Skip to content

Commit 32009f0

Browse files
committed
prevent the spawning of child workflows within steps
1 parent 74e381d commit 32009f0

File tree

2 files changed

+47
-0
lines changed

2 files changed

+47
-0
lines changed

dbos/workflow.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,11 @@ func (c *dbosContext) RunAsWorkflow(_ DBOSContext, fn WorkflowFunc, input any, o
607607
parentWorkflowState, ok := c.Value(workflowStateKey).(*workflowState)
608608
isChildWorkflow := ok && parentWorkflowState != nil
609609

610+
// Prevent spawning child workflows from within a step
611+
if isChildWorkflow && parentWorkflowState.isWithinStep {
612+
return nil, newStepExecutionError(parentWorkflowState.workflowID, params.workflowName, "cannot spawn child workflow from within a step")
613+
}
614+
610615
if isChildWorkflow {
611616
// Advance step ID if we are a child workflow
612617
parentWorkflowState.NextStepID()

dbos/workflows_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -831,6 +831,48 @@ func TestChildWorkflow(t *testing.T) {
831831
t.Fatalf("expected child result '%s', got '%s'", result, childResult)
832832
}
833833
})
834+
835+
t.Run("ChildWorkflowCannotBeSpawnedFromStep", func(t *testing.T) {
836+
// Child workflow for testing
837+
childWf := func(dbosCtx DBOSContext, input string) (string, error) {
838+
return "child-result", nil
839+
}
840+
RegisterWorkflow(dbosCtx, childWf)
841+
842+
// Step that tries to spawn a child workflow - this should fail
843+
stepThatSpawnsChild := func(ctx context.Context, input string) (string, error) {
844+
dbosCtx := ctx.(DBOSContext)
845+
_, err := RunAsWorkflow(dbosCtx, childWf, input)
846+
if err != nil {
847+
return "", err
848+
}
849+
return "should-not-reach", nil
850+
}
851+
852+
// Workflow that calls the step
853+
parentWf := func(ctx DBOSContext, input string) (string, error) {
854+
return RunAsStep(ctx, func(context context.Context) (string, error) {
855+
return stepThatSpawnsChild(context, input)
856+
})
857+
}
858+
RegisterWorkflow(dbosCtx, parentWf)
859+
860+
// Execute the workflow - should fail when step tries to spawn child workflow
861+
handle, err := RunAsWorkflow(dbosCtx, parentWf, "test-input")
862+
require.NoError(t, err, "failed to start parent workflow")
863+
864+
// Expect the workflow to fail
865+
_, err = handle.GetResult()
866+
require.Error(t, err, "expected error when spawning child workflow from step, but got none")
867+
868+
// Check the error type and message
869+
dbosErr, ok := err.(*DBOSError)
870+
require.True(t, ok, "expected error to be of type *DBOSError, got %T", err)
871+
require.Equal(t, StepExecutionError, dbosErr.Code, "expected error code to be StepExecutionError, got %v", dbosErr.Code)
872+
873+
expectedMessagePart := "cannot spawn child workflow from within a step"
874+
require.Contains(t, err.Error(), expectedMessagePart, "expected error message to contain %q, but got %q", expectedMessagePart, err.Error())
875+
})
834876
}
835877

836878
// Idempotency workflows moved to test functions

0 commit comments

Comments
 (0)