Skip to content

FLOGO-18450: Concurrent execution of parallel transition branches#218

Merged
awakchau-tibco merged 7 commits into
masterfrom
flogo-18450-concurrent-parallel-branches
Jul 8, 2026
Merged

FLOGO-18450: Concurrent execution of parallel transition branches#218
awakchau-tibco merged 7 commits into
masterfrom
flogo-18450-concurrent-parallel-branches

Conversation

@awakchau-tibco

Copy link
Copy Markdown
Collaborator

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

  • Gated behind the existing FLOGO_FLOW_CONCURRENT_TASK_EXECUTION env flag (default off). When 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 (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.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: the 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).

Testing

  • Unit + integration tests (parallelism, join-fires-once, drain-then-fail, sequential parity), ~87% coverage of the new code; clean under go test -race.
  • Fixed pre-existing map-order-flaky TestGetErrorObject_* (def.Tasks()[0] -> def.GetTask("LogStart")).

Performance

  • Benchmarks + a when-to-enable guide in 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

awakchau-tibco and others added 3 commits June 30, 2026 15:33
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>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 into FlowAction.Run behind 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.

Comment thread support/env.go
Comment thread instance/ind_instance.go
Comment thread instance/ind_instance.go
Comment thread instance/ind_instance.go
awakchau-tibco and others added 4 commits July 7, 2026 23:12
…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>
@awakchau-tibco awakchau-tibco merged commit 0683f40 into master Jul 8, 2026
@awakchau-tibco awakchau-tibco deleted the flogo-18450-concurrent-parallel-branches branch July 8, 2026 07:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants