FLOGO-18450: Concurrent execution of parallel transition branches#218
Merged
Merged
Conversation
Activities on parallel transitions (fan-out branches) previously executed
sequentially even though the flow models them as parallel. This adds opt-in
concurrent execution of ready branches, gated behind the existing
FLOGO_FLOW_CONCURRENT_TASK_EXECUTION env flag (default off).
When the flag is off the sequential DoStep loop is byte-for-byte unchanged, so
no existing flow regresses unless explicitly opted in. When on, FlowAction.Run
drives a new IndependentInstance.RunConcurrent worker pool that runs ready tasks
concurrently while preserving join semantics (a task runs only after all its
incoming links resolve) and order-independent results.
- support/env.go: GetConcurrentExecution(); instance/util.go
IsConcurrentTaskExcutionEnabled delegates to it (one source of truth, shared
with the change-tracker lock).
- Two-lock design on IndependentInstance: coarse stateLock (traversal/scheduling,
taskInsts/linkInsts/subflows) + attrsLock RWMutex (shared scope + returnData),
both nil in sequential mode. Lock order: stateLock -> changeTracker -> attrsLock.
- stepID/wiCounter/subflowCtr promoted to atomics.
- Per-task cancellable context (TaskInst.evalCtx) so context-aware activities
abort on sibling failure.
- Drain-then-fail: first unhandled branch error cancels siblings, the pool waits
for in-flight branches to return, then the global error handler fires once.
- execTask and the new execTaskConcurrent share extracted evalTaskBehavior +
handleEvalResult helpers (removes duplication).
- Tests: unit + integration (parallelism, join-once, drain-then-fail, sequential
parity), ~87% coverage of the new code; clean under -race. Also fixed
pre-existing map-order-flaky TestGetErrorObject_* (def.Tasks()[0] ->
def.GetTask("LogStart")).
- Benchmarks + a when-to-enable guide (instance/PARALLEL_BENCHMARKS.md).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
8448d26 to
eb89aa8
Compare
There was a problem hiding this comment.
Pull request overview
Adds an opt-in concurrent scheduler for flow execution so parallel transition (fan-out) branches can run simultaneously, reducing end-to-end latency for independent branches while aiming to preserve join semantics.
Changes:
- Introduces a concurrent worker-pool drain path (
IndependentInstance.RunConcurrent) and wires it intoFlowAction.Runbehind an env flag. - Adds concurrency safety mechanisms (per-instance locks, atomic counters, per-task cancellable context) and refactors shared task-eval helpers.
- Adds unit/integration tests and benchmarks covering concurrency behavior, join behavior, cancellation on failure, and parity checks.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| support/env.go | Adds env-flag helper for enabling concurrent branch execution. |
| support/env_test.go | Adds unit tests for the new env-flag helper. |
| instance/util.go | Switches concurrency-flag reading to the shared support helper. |
| instance/taskinst.go | Adds per-task context override to support sibling-cancellation in concurrent mode. |
| instance/taskinst_test.go | Fixes flakiness by selecting tasks by ID rather than map/array order. |
| instance/parallel_test.go | Adds white-box unit tests for concurrency locks, latching, helpers, and atomic counters. |
| instance/parallel_integration_test.go | Adds black-box integration tests exercising RunConcurrent end-to-end. |
| instance/parallel_bench_test.go | Adds benchmarks comparing sequential vs concurrent execution overhead and gains. |
| instance/instance.go | Adds locking around shared attrs/return data for concurrent safety. |
| instance/instance_ser.go | Updates subflow counter handling to use atomic storage. |
| instance/ind_instance.go | Implements the concurrent worker-pool scheduler and concurrency coordination fields/locks. |
| action.go | Wires concurrent execution into the flow action run loop and logs when enabled. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
spatil4-tibco
approved these changes
Jul 7, 2026
…lock-order comment) - RunConcurrent: increment stepCtr while holding coordMu (before Unlock) so the max-step safeguard is enforced strictly, matching the sequential loop; workers can no longer pop past maxStepCount. - execTaskConcurrent: clear taskInst.evalCtx on exit so a TaskInst never retains a canceled group context after the pool tears down concurCtx; GoContext/GetGoContext fall back to the flow context on later reads. - Correct the lock-hierarchy comment to stateLock -> changeTracker -> attrsLock (leaf), matching the actual order (the change tracker calls GetReturnData under its own lock). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e box Production code uses trace.TagDefs/ParseTagDefs (added in #219) which only exist in core v1.6.20-rc.1, but go.mod still pinned v1.6.17, so `go build ./...` failed with "undefined: trace.TagDefs". Bumped core via `go get` + `go mod tidy`; tidy also pruned now-unused transitive deps (x/net, go-redis, redsync, hashicorp/*, xxhash, go-rendezvous) that the newer core no longer requires. Verified build on windows/linux/darwin. Also fixed a pre-existing broken reference in action_test.go, previously masked by the TagDefs build failure: the dead RestartRequest test fixture referenced flow/support.Interceptor (a type removed long ago); pointed it at core/engine/support.Interceptor to match the canonical tester.RestartRequest so the flow package's tests compile. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…teSkip shadowing) TestTaskBehaviour fails on `assert.True(t, skip)`. This is a pre-existing bug on master, unrelated to FLOGO-18450 — it was hidden because model/simple could not compile against the old pinned core (missing trace.TagDefs) and is exposed now that the module builds. Add an inline comment documenting the root cause (TaskBehavior.Skip's named return `propagateSkip` shadows the package-level var and is never assigned, so Skip always returns false; and the package var is init-cached so the test's os.Setenv is inert) and how to fix it. No production behavior change here, per decision to track the fix separately. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
core v1.6.20 is now published (it is @latest and contains trace.TagDefs). Switch the pin from the v1.6.20-rc.1 release candidate to the stable release via `go get github.com/project-flogo/core@v1.6.20` + `go mod tidy`. No dependency-graph change beyond the version/hash; build verified on windows/linux/darwin. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
abhide-tibco
approved these changes
Jul 8, 2026
vnalawad-tibco
approved these changes
Jul 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds opt-in concurrent execution of activities on parallel transition branches (fan-out) in the flow action. Branches modeled as parallel now run at the same time instead of one activity being processed at a time.
Why
The flow drains work sequentially, so independent branches were serialized — total latency equaled the sum of branch times. Concurrent execution reduces this to roughly the slowest branch, especially impactful for I/O-bound branches (delay / REST / DB).
Design & safety
FLOGO_FLOW_CONCURRENT_TASK_EXECUTIONenv flag (default off). When off, the sequentialDoSteploop is byte-for-byte unchanged, so no existing flow regresses unless explicitly opted in.FlowAction.Rundrives a newIndependentInstance.RunConcurrentworker pool (min(GOMAXPROCS, 32)workers) that runs ready tasks concurrently while preserving join semantics (a task runs only after all its incoming links resolve) and order-independent results.support/env.go:GetConcurrentExecution();instance/util.goIsConcurrentTaskExcutionEnableddelegates to it (one source of truth, shared with the change-tracker lock).IndependentInstance: coarsestateLock(traversal/scheduling, taskInsts/linkInsts/subflows) +attrsLockRWMutex (shared scope + returnData), both nil in sequential mode. Lock order:stateLock -> changeTracker -> attrsLock.stepID/wiCounter/subflowCtrpromoted to atomics.TaskInst.evalCtx) so context-aware activities abort on sibling failure.execTaskand the newexecTaskConcurrentshare extractedevalTaskBehavior+handleEvalResulthelpers (removes duplication).Testing
go test -race.TestGetErrorObject_*(def.Tasks()[0]->def.GetTask("LogStart")).Performance
instance/PARALLEL_BENCHMARKS.md. Crossover ≈ 50–100 µs of work per branch; recommend enabling only for 2+ branches each doing blocking I/O or ≳ ~100 µs of CPU.🤖 Generated with Claude Code