Skip to content

test(pipeline): orchestrator × executor concurrency matrix (ENG-298)#100

Merged
brian-arnold merged 3 commits intorevive_asyncfrom
eywalker/eng-298-review-pr-99-concurrency-regression-fix-validate
Mar 24, 2026
Merged

test(pipeline): orchestrator × executor concurrency matrix (ENG-298)#100
brian-arnold merged 3 commits intorevive_asyncfrom
eywalker/eng-298-review-pr-99-concurrency-regression-fix-validate

Conversation

@kurodo3
Copy link
Copy Markdown
Contributor

@kurodo3 kurodo3 Bot commented Mar 23, 2026

Summary

Closes ENG-298 — Review PR #99 concurrency regression fix and validate orchestrator × executor matrix.

PR #99 Review

PR #99 (fix(function_node): restore within-node concurrent packet execution) restores asyncio.TaskGroup-based concurrency in FunctionNode.async_execute. The diff was reviewed and found correct:

  • ✅ Semaphore pattern is sound: sem.acquire() before tg.create_task(...), sem.release() in finally
  • ✅ Default-argument capture (t: TagProtocol = tag) avoids closure variable capture bugs
  • _process_one_db helper defined once outside the loop — no closure aliasing
  • getattr(self._function_pod, "node_config", NodeConfig()) is safe (attribute always present)
  • asyncio.TaskGroup propagates task exceptions correctly
  • ✅ Existing node-level tests (31) pass with the fix applied

What this PR adds

A new test file tests/test_pipeline/test_orchestrator_executor_matrix.py covering all four combinations at the pipeline level:

Sync function (def) Async function (async def)
Sync orchestrator (1) sequential baseline (2) graceful no-deadlock bridging
Async orchestrator (3) thread-pool concurrency (4) maximum native concurrency

Each cell is tested for correctness and expected sequential/concurrent behaviour.

Regression guard (key addition)

TestAsyncOrchestratorFunctionTypeDifference is specifically designed to catch the PLT-930 class of regression, where async functions silently lose their concurrency benefit.

It uses a single-worker thread pool to isolate the function-type dimension:

  • async orch + sync fn (for-loop time.sleep): only 1 thread → packets forced sequential → ≥ 0.60s
  • async orch + async fn (asyncio.sleep): no thread needed → all N concurrent → < 0.30s

If async_execute ever loses its TaskGroup again, the async function serialises to ~0.80s and the fast-path assertion fires — directly catching the regression. The previous node-level tests only validated orchestrator type, not function type; this test validates both simultaneously.

Performance comparison tests

TestAsyncAsyncVsAsyncSync additionally shows async+async outperforms async+sync under thread-pool saturation (2 workers, 6 packets): the sync path needs multiple rounds (~0.3s) while the async path runs all coroutines in one go (~0.1s).

Test results

521 passed in 41.73s  (18 new matrix tests, 503 existing)

Test plan

  • test_orchestrator_executor_matrix.py — 18 tests, all pass
  • test_channels/test_node_async_execute.py — 22 tests, all pass
  • test_channels/test_copilot_review_issues.py — 9 tests, all pass
  • Full tests/test_channels/ + tests/test_pipeline/ suite — 521 passed
  • Regression guard verified: removing TaskGroup from async_execute causes TestAsyncOrchestratorFunctionTypeDifference to fail (0.80s vs < 0.30s threshold)

🤖 Generated with Claude Code

Add a comprehensive test suite covering all four combinations of pipeline
orchestrator (sync/async) and function executor (sync/async def):

  1. Sync orchestrator + sync function  — sequential baseline
  2. Sync orchestrator + async function — graceful no-deadlock bridging
  3. Async orchestrator + sync function — thread-pool concurrency
  4. Async orchestrator + async function — maximum native concurrency

Each combination is tested for correctness and either sequential or
concurrent behaviour. Cross-combination perf tests assert that async+async
and async+sync are measurably faster (>40%) than sync+sync for I/O-bound
workloads, validating the concurrency benefit restored by PR #99 (ENG-297).

Closes ENG-298.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 23, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@kurodo3 kurodo3 Bot changed the base branch from dev to revive_async March 23, 2026 23:11
@eywalker eywalker requested a review from brian-arnold March 23, 2026 23:15
kurodo3 Bot and others added 2 commits March 23, 2026 23:23
Demonstrate that async+async (asyncio.sleep) outperforms async+sync
(for-loop time.sleep) when the thread pool is saturated.

The sync function uses a for loop of time.sleep calls — unambiguously
blocking with no event-loop cooperation — and runs through
run_in_executor, consuming one thread slot per concurrent packet.
By capping the event loop's default executor at 2 workers while
dispatching 6 packets, the async+sync path serialises into multiple
rounds (~0.3 s) while async+async runs all coroutines concurrently
in a single sleep period (~0.1 s).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…sync orch

Add TestAsyncOrchestratorFunctionTypeDifference which directly tests that
the function type (sync vs async) produces a measurable performance
difference under the async orchestrator — the exact scenario that went
undetected in the PLT-930 regression.

With a single-worker thread pool:
- async+sync (for-loop time.sleep): only 1 thread → packets are truly
  sequential (~N × sleep)
- async+async (asyncio.sleep): no thread needed → all N packets run
  concurrently (~1 × sleep)

If async_execute loses its TaskGroup concurrency, the async function
also becomes sequential (~0.8s), exceeding the < 0.30s threshold and
failing this test — directly catching the regression.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@brian-arnold
Copy link
Copy Markdown
Collaborator

These tests look great and are a nice addition to PR #99 !

@brian-arnold brian-arnold merged commit 8c47b74 into revive_async Mar 24, 2026
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.

1 participant