Skip to content

Commit 9263521

Browse files
improvement(table): debug logs at every cascade decision branch
1 parent 8706cca commit 9263521

2 files changed

Lines changed: 69 additions & 25 deletions

File tree

apps/sim/lib/table/deps.ts

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@
44
* can import it without pulling in `@sim/db` and other server-only deps.
55
*/
66

7+
import { createLogger } from '@sim/logger'
78
import type { RowData, RowExecutionMetadata, RowExecutions, TableRow, WorkflowGroup } from './types'
89

10+
const logger = createLogger('OptimisticCascade')
11+
912
/**
1013
* True when the cell has a worker actively reserved — `queued` / `running`,
1114
* or `pending` after the scheduler stamped a jobId. Single source of truth
@@ -93,28 +96,38 @@ export function optimisticallyScheduleNewlyEligibleGroups(
9396

9497
let next: RowExecutions | null = null
9598
for (const group of groups) {
96-
// autoRun=false groups don't auto-fire on dep-fill — leave them empty.
97-
if (group.autoRun === false) continue
98-
if (!areGroupDepsSatisfied(group, afterRow)) continue
99+
const tag = `[OptimisticCascade] row=${beforeRow.id} group=${group.id}`
100+
if (group.autoRun === false) {
101+
logger.debug(`${tag} → skip: autoRun=false`)
102+
continue
103+
}
104+
if (!areGroupDepsSatisfied(group, afterRow)) {
105+
logger.debug(`${tag} → skip: deps unmet on afterRow`)
106+
continue
107+
}
99108

100109
const exec = beforeRow.executions?.[group.id]
101-
// Don't overwrite an in-flight state.
102-
if (exec?.status === 'queued' || exec?.status === 'running') continue
103-
if (exec?.status === 'pending' && exec.jobId) continue
110+
if (exec?.status === 'queued' || exec?.status === 'running') {
111+
logger.debug(`${tag} → skip: already ${exec.status}`)
112+
continue
113+
}
114+
if (exec?.status === 'pending' && exec.jobId) {
115+
logger.debug(`${tag} → skip: pending+jobId (worker reserved)`)
116+
continue
117+
}
104118

105-
// "completed" with all outputs filled = genuinely done; don't re-fire.
106-
// "completed" with any output cleared = stale; treat as never-run.
107119
const isStaleCompleted = exec?.status === 'completed' && !areOutputsFilled(group, afterRow)
108-
109-
// Whether this group could fire is the union of:
110-
// - dep-fill: deps were unmet before, now met (the classic cascade)
111-
// - output-cleared: user wiped a previously-completed output
112-
// - retry: prior run ended in error / cancelled
113120
const wasSatisfied = areGroupDepsSatisfied(group, beforeRow)
114121
const becameSatisfied = !wasSatisfied
115122
const isRetryable = exec?.status === 'cancelled' || exec?.status === 'error'
116-
if (!becameSatisfied && !isStaleCompleted && !isRetryable && exec) continue
123+
if (!becameSatisfied && !isStaleCompleted && !isRetryable && exec) {
124+
logger.debug(`${tag} → skip: no fire reason (status=${exec?.status})`)
125+
continue
126+
}
117127

128+
logger.debug(
129+
`${tag} → flip to pending (becameSatisfied=${becameSatisfied} stale=${isStaleCompleted} retry=${isRetryable})`
130+
)
118131
if (next === null) next = { ...(beforeRow.executions ?? {}) }
119132
const pending: RowExecutionMetadata = {
120133
status: 'pending',

apps/sim/lib/table/workflow-columns.ts

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,22 +54,45 @@ export function isGroupEligible(
5454
): boolean {
5555
const isManualRun = opts?.isManualRun ?? false
5656
const mode = opts?.mode ?? 'all'
57-
if (group.autoRun === false && !isManualRun) return false
57+
const tag = `[Eligibility] row=${row.id} group=${group.id} manual=${isManualRun} mode=${mode}`
58+
59+
if (group.autoRun === false && !isManualRun) {
60+
logger.debug(`${tag} → skip: autoRun=false on auto-fire`)
61+
return false
62+
}
5863

5964
const exec = row.executions?.[group.id]
60-
if (isExecInFlight(exec)) return false
65+
if (isExecInFlight(exec)) {
66+
logger.debug(`${tag} → skip: in-flight (status=${exec?.status})`)
67+
return false
68+
}
6169
const status = exec?.status
6270

6371
const completedAndFilled = status === 'completed' && areOutputsFilled(group, row)
64-
if (!isManualRun && completedAndFilled) return false
65-
if (!isManualRun && (status === 'error' || status === 'cancelled')) return false
66-
// `mode: 'incomplete'` (manual): re-runs only rows whose outputs are missing
67-
// or whose last run failed. A genuinely completed cell (status + filled
68-
// outputs) is skipped; error / cancelled remain runnable.
69-
if (mode === 'incomplete' && completedAndFilled) return false
70-
71-
if (isManualRun && group.autoRun === false) return true
72-
return areGroupDepsSatisfied(group, row)
72+
if (!isManualRun && completedAndFilled) {
73+
logger.debug(`${tag} → skip: completed+filled on auto-fire`)
74+
return false
75+
}
76+
// Auto-fire skips `error` to avoid infinite-retry loops on a deterministic
77+
// failure. `cancelled` doesn't get the same treatment — cancellation is
78+
// user-initiated, and once an upstream dep re-fires the cascade, the user
79+
// almost certainly wants the chain to continue.
80+
if (!isManualRun && status === 'error') {
81+
logger.debug(`${tag} → skip: terminal status=error on auto-fire`)
82+
return false
83+
}
84+
if (mode === 'incomplete' && completedAndFilled) {
85+
logger.debug(`${tag} → skip: completed+filled on mode=incomplete`)
86+
return false
87+
}
88+
89+
if (isManualRun && group.autoRun === false) {
90+
logger.debug(`${tag} → eligible: manual on autoRun=false group (deps bypassed)`)
91+
return true
92+
}
93+
const depsOk = areGroupDepsSatisfied(group, row)
94+
logger.debug(`${tag}${depsOk ? 'eligible: deps satisfied' : 'skip: deps unmet'}`)
95+
return depsOk
7396
}
7497

7598
/**
@@ -107,6 +130,10 @@ export async function scheduleRunsForRows(
107130
const groups = groupIdFilter ? allGroups.filter((g) => groupIdFilter.has(g.id)) : allGroups
108131
if (groups.length === 0) return { triggered: 0 }
109132

133+
logger.debug(
134+
`[Cascade] scheduleRunsForRows table=${table.id} rows=${rows.length} groups=${groups.length} manual=${opts?.isManualRun ?? false} mode=${opts?.mode ?? 'all'} scoped=${groupIdFilter ? 'yes' : 'no'}`
135+
)
136+
110137
const orderedRows = rows.length <= 1 ? rows : [...rows].sort((a, b) => a.position - b.position)
111138

112139
const pendingRuns: RunGroupCellOptions[] = []
@@ -410,6 +437,10 @@ async function runWorkflowGroupsInternal(opts: {
410437
const targetGroups = groupIds ? allGroups.filter((g) => groupIds.includes(g.id)) : allGroups
411438
if (targetGroups.length === 0) return { triggered: 0 }
412439

440+
logger.info(
441+
`[Cascade] [${requestId}] runWorkflowColumn table=${tableId} groups=[${targetGroups.map((g) => g.id).join(',')}] rows=${rowIds ? `[${rowIds.join(',')}]` : 'all'} mode=${mode}`
442+
)
443+
413444
const filters = [eq(userTableRows.tableId, tableId), eq(userTableRows.workspaceId, workspaceId)]
414445
if (rowIds && rowIds.length > 0) {
415446
filters.push(inArray(userTableRows.id, rowIds))

0 commit comments

Comments
 (0)