Skip to content

Commit e7e2135

Browse files
committed
order of ops for validations
1 parent 0070433 commit e7e2135

File tree

5 files changed

+59
-12
lines changed

5 files changed

+59
-12
lines changed

apps/sim/executor/dag/builder.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -136,18 +136,18 @@ export class DAGBuilder {
136136
nodes: string[] | undefined,
137137
type: 'Loop' | 'Parallel'
138138
): void {
139-
const sentinelStartId =
140-
type === 'Loop' ? buildSentinelStartId(id) : buildParallelSentinelStartId(id)
141-
const sentinelStartNode = dag.nodes.get(sentinelStartId)
142-
143-
if (!sentinelStartNode) return
144-
145139
if (!nodes || nodes.length === 0) {
146140
throw new Error(
147141
`${type} has no blocks inside. Add at least one block to the ${type.toLowerCase()}.`
148142
)
149143
}
150144

145+
const sentinelStartId =
146+
type === 'Loop' ? buildSentinelStartId(id) : buildParallelSentinelStartId(id)
147+
const sentinelStartNode = dag.nodes.get(sentinelStartId)
148+
149+
if (!sentinelStartNode) return
150+
151151
const hasConnections = Array.from(sentinelStartNode.outgoingEdges.values()).some((edge) =>
152152
nodes.includes(extractBaseBlockId(edge.target))
153153
)

apps/sim/executor/execution/state.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ export interface ParallelScope {
2727
items?: any[]
2828
/** Error message if parallel validation failed (e.g., exceeded max branches) */
2929
validationError?: string
30+
/** Whether the parallel has an empty distribution and should be skipped */
31+
isEmpty?: boolean
3032
}
3133

3234
export class ExecutionState implements BlockStateController {

apps/sim/executor/orchestrators/loop.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -386,10 +386,10 @@ export class LoopOrchestrator {
386386
return true
387387
}
388388

389-
// forEach: skip if items array is empty
390389
if (scope.loopType === 'forEach') {
391390
if (!scope.items || scope.items.length === 0) {
392-
logger.info('ForEach loop has empty items, skipping loop body', { loopId })
391+
logger.info('ForEach loop has empty collection, skipping loop body', { loopId })
392+
this.state.setBlockOutput(loopId, { results: [] }, DEFAULTS.EXECUTION_TIME)
393393
return false
394394
}
395395
return true
@@ -399,6 +399,8 @@ export class LoopOrchestrator {
399399
if (scope.loopType === 'for') {
400400
if (scope.maxIterations === 0) {
401401
logger.info('For loop has 0 iterations, skipping loop body', { loopId })
402+
// Set empty output for the loop
403+
this.state.setBlockOutput(loopId, { results: [] }, DEFAULTS.EXECUTION_TIME)
402404
return false
403405
}
404406
return true

apps/sim/executor/orchestrators/node.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,17 @@ export class NodeExecutionOrchestrator {
158158
this.parallelOrchestrator.initializeParallelScope(ctx, parallelId, nodesInParallel)
159159
}
160160
}
161+
162+
const scope = this.parallelOrchestrator.getParallelScope(ctx, parallelId)
163+
if (scope?.isEmpty) {
164+
logger.info('Parallel has empty distribution, skipping parallel body', { parallelId })
165+
return {
166+
sentinelStart: true,
167+
shouldExit: true,
168+
selectedRoute: EDGE.PARALLEL_EXIT,
169+
}
170+
}
171+
161172
return { sentinelStart: true }
162173
}
163174

apps/sim/executor/orchestrators/parallel.ts

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,13 @@ export class ParallelOrchestrator {
6161

6262
let items: any[] | undefined
6363
let branchCount: number
64+
let isEmpty = false
6465

6566
try {
66-
const resolved = this.resolveBranchCount(ctx, parallelConfig)
67+
const resolved = this.resolveBranchCount(ctx, parallelConfig, parallelId)
6768
branchCount = resolved.branchCount
6869
items = resolved.items
70+
isEmpty = resolved.isEmpty ?? false
6971
} catch (error) {
7072
const errorMessage = `Parallel Items did not resolve: ${error instanceof Error ? error.message : String(error)}`
7173
logger.error(errorMessage, { parallelId, distribution: parallelConfig.distribution })
@@ -91,6 +93,34 @@ export class ParallelOrchestrator {
9193
throw new Error(branchError)
9294
}
9395

96+
// Handle empty distribution - skip parallel body
97+
if (isEmpty || branchCount === 0) {
98+
const scope: ParallelScope = {
99+
parallelId,
100+
totalBranches: 0,
101+
branchOutputs: new Map(),
102+
completedCount: 0,
103+
totalExpectedNodes: 0,
104+
items: [],
105+
isEmpty: true,
106+
}
107+
108+
if (!ctx.parallelExecutions) {
109+
ctx.parallelExecutions = new Map()
110+
}
111+
ctx.parallelExecutions.set(parallelId, scope)
112+
113+
// Set empty output for the parallel
114+
this.state.setBlockOutput(parallelId, { results: [] })
115+
116+
logger.info('Parallel scope initialized with empty distribution, skipping body', {
117+
parallelId,
118+
branchCount: 0,
119+
})
120+
121+
return scope
122+
}
123+
94124
const { entryNodes } = this.expander.expandParallel(this.dag, parallelId, branchCount, items)
95125

96126
const scope: ParallelScope = {
@@ -127,15 +157,17 @@ export class ParallelOrchestrator {
127157

128158
private resolveBranchCount(
129159
ctx: ExecutionContext,
130-
config: SerializedParallel
131-
): { branchCount: number; items?: any[] } {
160+
config: SerializedParallel,
161+
parallelId: string
162+
): { branchCount: number; items?: any[]; isEmpty?: boolean } {
132163
if (config.parallelType === 'count') {
133164
return { branchCount: config.count ?? 1 }
134165
}
135166

136167
const items = this.resolveDistributionItems(ctx, config)
137168
if (items.length === 0) {
138-
return { branchCount: config.count ?? 1 }
169+
logger.info('Parallel has empty distribution, skipping parallel body', { parallelId })
170+
return { branchCount: 0, items: [], isEmpty: true }
139171
}
140172

141173
return { branchCount: items.length, items }

0 commit comments

Comments
 (0)