langgenius / langgenius/dify

[Refactor/Chore] Order workflow node-start persistence before dependent node execution

Open
#39,546 1 comment 1 reaction 0 assignees View on GitHub
Dominant language
TypeScript
Stars
156k
Forks
24.6k
Avg merge
20h 50m
Merged PRs (30d)
586

Description

### Self Checks

- [x] I have read the [Contributing Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md) and [Language Policy](https://github.com/langgenius/dify/issues/1542).
- [x] This is only for refactors or chores; if you would like to ask a question, please head to [Discussions](https://github.com/langgenius/dify/discussions/categories/general).
- [x] I have searched for existing issues [search for existing issues](https://github.com/langgenius/dify/issues), including closed ones.
- [x] I confirm that I am using English to submit this report, otherwise it will be closed.
- [x] 【中文用户 & Non English User】请使用英语提交,否则会被关闭 :)
- [x] Please do not modify this template :) and fill in all the required fields.

### Description

Establish a deterministic ordering guarantee between persistence of a workflow node's start event and execution of node logic that depends on the persisted `WorkflowNodeExecutionModel`.

The current Graphon execution sequence uses two independent threads:

1. `GraphWorker` obtains a `NodeRunStartedEvent` from the `node.run()` generator.
2. The worker puts that event into `event_queue`.
3. The worker immediately resumes the generator and enters the Agent v2 node body.
4. Independently, `GraphDispatcher` dequeues the start event, dispatches it to `WorkflowPersistenceLayer`, and persists the `WorkflowNodeExecutionModel`.

There is no happens-before relationship between steps 3 and 4. The Agent v2 node body reads the node execution row and uses it as the durable caller that owns the participant Binding pointer (`agent_workspace_binding_id`). If the worker advances first, that caller row does not exist yet and participant setup fails with `Workflow node execution caller is unavailable`.

`WorkflowPersistenceLayer` currently calls `save_synchronously()` for Agent v2 start events. This makes the database write synchronous once the dispatcher processes the event, but it does not make the worker wait for the dispatcher, so it does not close the ordering gap.

The required invariant is:

> Before node logic is allowed to depend on its persisted node-execution caller, creation and commit of that caller row must have completed successfully.

The implementation should establish this invariant explicitly at the execution/persistence boundary rather than relying on thread scheduling.

Acceptance criteria:

- Define an explicit ordering contract between node-start persistence and dependent node execution.
- Ensure the Agent v2 node cannot enter participant/session setup before its caller row is committed and visible.
- Propagate start-persistence failure without allocating a participant Binding whose caller pointer cannot be stored.
- Preserve the complete tenant, app, workflow, run, node, and execution identity constraints.
- Cover both orderings deterministically in tests: persistence completing first and the worker otherwise attempting to advance first.
- Do not use timing-based polling, sleeps, or an arbitrary visibility deadline as the ordering mechanism.

### Motivation

The existing event queue guarantees delivery order to the dispatcher, but it does not order dispatcher side effects before continued execution in the worker thread. Treating `NodeRunStartedEvent` persistence as an asynchronous observer side effect is therefore incompatible with Agent v2's need to use the persisted row as a caller during the same node execution.

The current bounded polling only reduces how often the race is observed. It cannot guarantee correctness under scheduler or database delays and obscures the missing synchronization contract.

### Additional Context

The behavior was exposed while validating the Agent working-environment refactor in #39480. The tracking scope is the GraphWorker-to-GraphDispatcher node-start persistence handoff. It does not propose a broader sandbox or working-environment lifecycle redesign.

Contributor guide

Open the contributing guide

Research direction

Start at the GraphWorker-to-GraphDispatcher handoff for NodeRunStartedEvent and inspect WorkflowPersistenceLayer's save_synchronously() path. Trace how Agent v2 participant/session setup reads WorkflowNodeExecutionModel and identify the existing bounded polling and related tests. Done means deterministic tests cover both persistence orderings, start failures propagate, and the caller row is committed before dependent execution proceeds.

Written by the indexing model from the issue text.

Assessment

Domain
backend, databases, distributed-systems
Issue type
Refactor
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.