fix(race-condition-workflow-switching): another race condition between registry and workflow stores (#1247)

* fix(race-condition-workflow-switching): another race condition between regitry and workflow stores"

* fix initial load race cond + cleanup

* fix initial load issue + simplify
This commit is contained in:
Vikhyath Mondreti
2025-09-04 18:02:00 -07:00
committed by GitHub
parent 0f7dfe084a
commit 57c98d86ba
3 changed files with 29 additions and 46 deletions

View File

@@ -867,48 +867,41 @@ const WorkflowContent = React.memo(() => {
[project, isPointInLoopNodeWrapper, getNodes]
)
// Track when workflow is fully ready for rendering
// Initialize workflow when it exists in registry and isn't active
useEffect(() => {
const currentId = params.workflowId as string
if (!currentId || !workflows[currentId]) return
// Reset workflow ready state when workflow changes
if (activeWorkflowId !== currentId) {
setIsWorkflowReady(false)
return
// Clear diff and set as active
const { clearDiff } = useWorkflowDiffStore.getState()
clearDiff()
setActiveWorkflow(currentId)
}
}, [params.workflowId, workflows, activeWorkflowId, setActiveWorkflow])
// Check if we have the necessary data to render the workflow
const hasActiveWorkflow = activeWorkflowId === currentId
const hasWorkflowInRegistry = Boolean(workflows[currentId])
const isNotLoading = !isLoading
// Track when workflow is ready for rendering
useEffect(() => {
const currentId = params.workflowId as string
// Workflow is ready when:
// 1. We have an active workflow that matches the URL
// 2. The workflow exists in the registry
// 3. Workflows are not currently loading
if (hasActiveWorkflow && hasWorkflowInRegistry && isNotLoading) {
setIsWorkflowReady(true)
} else {
setIsWorkflowReady(false)
}
const shouldBeReady =
activeWorkflowId === currentId && Boolean(workflows[currentId]) && !isLoading
setIsWorkflowReady(shouldBeReady)
}, [activeWorkflowId, params.workflowId, workflows, isLoading])
// Init workflow
// Handle navigation and validation
useEffect(() => {
const validateAndNavigate = async () => {
const workflowIds = Object.keys(workflows)
const currentId = params.workflowId as string
// Check if workflows have been initially loaded at least once
// This prevents premature navigation decisions on page refresh
if (!hasWorkflowsInitiallyLoaded()) {
logger.info('Waiting for initial workflow load...')
return
}
// Wait for both initialization and workflow loading to complete
if (isLoading) {
logger.info('Workflows still loading, waiting...')
// Wait for initial load to complete before making navigation decisions
if (!hasWorkflowsInitiallyLoaded() || isLoading) {
return
}
@@ -948,24 +941,10 @@ const WorkflowContent = React.memo(() => {
router.replace(`/workspace/${currentWorkflow.workspaceId}/w/${currentId}`)
return
}
// Get current active workflow state
const { activeWorkflowId } = useWorkflowRegistry.getState()
if (activeWorkflowId !== currentId) {
// Clear workflow diff store when switching workflows
const { clearDiff } = useWorkflowDiffStore.getState()
clearDiff()
setActiveWorkflow(currentId)
} else {
// Don't reset variables cache if we're not actually switching workflows
setActiveWorkflow(currentId)
}
}
validateAndNavigate()
}, [params.workflowId, workflows, isLoading, setActiveWorkflow, createWorkflow, router])
}, [params.workflowId, workflows, isLoading, workspaceId, router])
// Transform blocks and loops into ReactFlow nodes
const nodes = useMemo(() => {

View File

@@ -7,7 +7,7 @@ import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
export default function WorkflowsPage() {
const router = useRouter()
const { workflows, isLoading, loadWorkflows } = useWorkflowRegistry()
const { workflows, isLoading, loadWorkflows, setActiveWorkflow } = useWorkflowRegistry()
const [hasInitialized, setHasInitialized] = useState(false)
const params = useParams()
@@ -45,9 +45,14 @@ export default function WorkflowsPage() {
// If we have valid workspace workflows, redirect to the first one
if (workspaceWorkflows.length > 0) {
router.replace(`/workspace/${workspaceId}/w/${workspaceWorkflows[0]}`)
// Ensure the workflow is set as active before redirecting
// This prevents the empty canvas issue on first login
const firstWorkflowId = workspaceWorkflows[0]
setActiveWorkflow(firstWorkflowId).then(() => {
router.replace(`/workspace/${workspaceId}/w/${firstWorkflowId}`)
})
}
}, [hasInitialized, isLoading, workflows, workspaceId, router])
}, [hasInitialized, isLoading, workflows, workspaceId, router, setActiveWorkflow])
// Always show loading state until redirect happens
// There should always be a default workflow, so we never show "no workflows found"

View File

@@ -484,8 +484,6 @@ export const useWorkflowRegistry = create<WorkflowRegistry>()(
future: [],
},
}
// Subblock values will be initialized by initializeFromWorkflow below
} else {
// If no state in DB, use empty state - server should have created start block
workflowState = {
@@ -535,11 +533,12 @@ export const useWorkflowRegistry = create<WorkflowRegistry>()(
}))
}
// Update all stores atomically to prevent race conditions
// Set activeWorkflowId and workflow state together
set({ activeWorkflowId: id, error: null })
useWorkflowStore.setState(workflowState)
useSubBlockStore.getState().initializeFromWorkflow(id, (workflowState as any).blocks || {})
set({ activeWorkflowId: id, error: null })
window.dispatchEvent(
new CustomEvent('active-workflow-changed', {
detail: { workflowId: id },