Skip to content

Feature Workflow/DAG/Block Telemetrization #79

Merged
AymenFJA merged 10 commits into
mainfrom
feature/optimize_telem
May 13, 2026
Merged

Feature Workflow/DAG/Block Telemetrization #79
AymenFJA merged 10 commits into
mainfrom
feature/optimize_telem

Conversation

@AymenFJA

Copy link
Copy Markdown
Collaborator

This PR introduces best practice/standard approach to allow teleworking a worklfow/dag or set of workflows in asyncflow.

Added

  • workflow_scope()
  • _workflow_id_ctx ContextVar
  • New start_telemetry() parameters
  • Span enricher
  • _emit() workflow ID injection

Fixed

  • Block spans are now correctly parented under the session root span
  • Removed dead run_in_executor branch from execute_block()

Changed

  • execute_block() now uses nullcontext
  • execute_block() now automatically sets _workflow_id_ctx to the block UID

Docs

  • Updated telemetry.md:
    • Added workflow_scope() reference
    • Added 4-level span hierarchy diagram
    • Added explanation of asyncflow.workflow_id propagation
    • Corrected external backend forwarding examples

AymenFJA added 3 commits May 8, 2026 03:06
…extVar propagation

- Add _workflow_id_ctx module-level ContextVar — asyncio copies it into every create_task/gather branch automatically, so concurrent workflows remain isolated without any explicit passing
- Add workflow_scope() async context manager — sets the ContextVar, opens an OTel workflow span via span_scope() (or nullcontext() when telemetry is off), and yields the workflow_id; task spans created inside become structural OTel children
- Add execute_block() span scope — wraps each block execution in an OTel block span using its uid as asyncflow.workflow_id; removes dead run_in_executor branch (all block functions are async)
- Register a span enricher on TelemetryManager at start_telemetry() — copies asyncflow.workflow_id from event attributes into the OTel task span at creation time, decoupling RHAPSODY from this domain attribute
- Propagate workflow_id through all _emit() call sites (TaskCreated, TaskStarted, TaskCompleted, TaskFailed, TaskCanceled, TaskQueued, TaskSubmitted, asyncflow.TaskResolved) by stamping comp_desc[workflow_id] at registration time from the active ContextVar
- Add workflow_id kwarg to _emit() — injects asyncflow.workflow_id into event attributes when present, leaving the existing attribute dict untouched otherwise
- Replace contextmanager + custom _null_context() with stdlib nullcontext from contextlib

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces workflow grouping through the new workflow_scope context manager and enhances telemetry capabilities by allowing the injection of custom OpenTelemetry span processors, metric readers, and resources. It also ensures that tasks executed within a @flow.block automatically inherit the block's UID as their workflow ID. A potential issue was identified regarding the use of a module-level ContextVar for tracking workflow IDs, which could lead to context leakage if multiple WorkflowEngine instances are used concurrently within the same coroutine hierarchy; moving this to an instance attribute was suggested for better isolation.

Comment on lines +40 to +42
_workflow_id_ctx: ContextVar[str | None] = ContextVar(
"asyncflow_workflow_id", default=None
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Defining _workflow_id_ctx as a module-level ContextVar can lead to context leakage between different WorkflowEngine instances if they are used within the same coroutine hierarchy. For example, if two engines are used concurrently and one enters a workflow_scope, it will affect the workflow ID captured by the other engine's tasks because they share the same ContextVar key.

To ensure proper isolation, consider making _workflow_id_ctx an instance attribute of WorkflowEngine (initialized in __init__). Since all methods that access it (workflow_scope, _register_component, execute_block) are instance methods or have access to self, this would provide better isolation for multi-engine environments.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Typical/covered pattern:

await asyncio.gather(
    engine1_workflow(),   # own context copy — workflow_scope("wf-A") stays here
    engine2_workflow(),   # own context copy — completely isolated
)

Problematic pattern still valid (not covered):

async def problematic():
    async with engine1.workflow_scope("wf-A"):
        engine2.my_task()   # reads _workflow_id_ctx → gets "wf-A" — wrong

@AymenFJA AymenFJA changed the title Feature/optimize telem Feature Workflow Telemetrization May 12, 2026
@AymenFJA AymenFJA changed the title Feature Workflow Telemetrization Feature Workflow/DAG/Block Telemetrization May 12, 2026
@review-notebook-app

Copy link
Copy Markdown

Check out this pull request on  ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

@AymenFJA AymenFJA merged commit d33229e into main May 13, 2026
12 checks passed
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