Skip to content
This repository was archived by the owner on Jan 16, 2026. It is now read-only.

Conversation

@cn-kali-team
Copy link
Contributor

Motivation

Currently, the Graph::start method in dagrs exposes only a synchronous API, which internally creates and runs a new Tokio runtime:

pub fn start(&mut self) -> Result<(), GraphError> {
    tokio::runtime::Runtime::new()
        .unwrap()
        .block_on(async { self.run().await })
}

This design works well for simple, standalone, or testing scenarios where no async runtime is already running.

Problem

However, when using dagrs in a context where a Tokio runtime already exists—such as within an async main function, an async service framework, or other async applications—calling Graph::start will panic at runtime with an error like:

thread 'main' panicked at 'Cannot start a runtime from within a runtime'

This makes dagrs incompatible with a growing number of modern Rust applications that rely on async/await and long-lived runtimes.

Why an Async Interface Is Needed

  • Prevents Nested Runtime Panic: Offering a native async fn interface allows dagrs to execute in any async context, avoiding nested tokio runtime panics altogether.
  • Improved Integration: Many applications (web servers, schedulers, pipelines, etc.) already run inside a Tokio runtime. An async interface seamlessly integrates dagrs into these environments.
  • Performance and Flexibility: Using .await enables non-blocking scheduling, granting the broader application full control over task orchestration, cancellation, and concurrency limits.
  • Better Testing and Composition: Async test frameworks and composed async workflows require DAG execution to be async for granular, parallel, and non-blocking testing or scheduling.
  • Future-proofing: The Rust ecosystem is moving increasingly toward async-first APIs and infrastructures. Supporting both sync and async interfaces maximizes dagrs' applicability and robustness.
  • Consistent API Design: Exposing both async fn run and fn start mirrors best practices in Rust async ecosystem (see: [reqwest], [sqlx]), enabling maximum usability for different user groups.

Proposed Solution

  • Expose a public async fn run_async(&mut self) (or simply async fn run) API.
  • Let start() remain for sync users, but make it a thin wrapper that simply spins up a runtime and calls .run_async().await.
  • Update documentation to reflect best practice for async vs sync use.

@genedna genedna requested a review from Copilot December 30, 2025 02:12
@genedna
Copy link
Member

genedna commented Dec 30, 2025

@codex review

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds an async interface to the Graph execution API, enabling dagrs to work within existing async runtimes without causing "nested runtime" panics. Previously, calling Graph::start() from within an async context would fail because it internally creates a new Tokio runtime.

  • Introduces async_start() method as the async-native interface for graph execution
  • Refactors start() to become a thin wrapper that creates a runtime and calls async_start()
  • Moves the runtime creation logic from inside the execution path to the synchronous wrapper

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

.block_on(async { self.async_start().await })
}
/// This function is used for the execution of a single dag with async.
pub async fn async_start(&mut self) -> Result<(), GraphError> {
Copy link

Copilot AI Dec 30, 2025

Choose a reason for hiding this comment

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

The method name async_start doesn't follow Rust async naming conventions. Standard practice in the Rust ecosystem is to avoid async_ prefixes for async methods. For example, reqwest uses send() (async) vs send() (sync isn't provided), and tokio's APIs don't use async_ prefixes.

Consider renaming this method to simply run() (making it public) since it's the async version, and the internal run() method could be renamed to something like run_internal() or execute_graph() to avoid the name conflict. This would align with the PR description's suggestion of exposing "async fn run" and is more idiomatic.

Copilot uses AI. Check for mistakes.
cn-kali-team and others added 3 commits December 30, 2025 10:27
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@genedna genedna merged commit dd13143 into dagrs-dev:main Dec 31, 2025
6 checks passed
@191220029 191220029 mentioned this pull request Dec 31, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants