Depend on futures-util instead of futures

This commit is contained in:
TechnoPorg committed 2026-02-11 22:44:01 +01:00
1 parent f342e33f16
commit a75defa5a6
79 files changed
+447 -380

No files matched your search

+4 -4
View File
@@ -22,7 +22,7 @@ use tracing::debug;
///
/// ```no_run
/// # use dagx::{task, DagRunner, Task};
/// # use futures::FutureExt;
/// #
/// // Using tuple struct for simple constants
/// struct Constant(i32);
///
@@ -57,7 +57,7 @@ use tracing::debug;
/// let b = b.depends_on(a);
/// // Now b is a TaskHandle<i32>
///
/// dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
/// dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await.unwrap();
/// assert_eq!(dag.get(b).unwrap(), 20);
/// # };
/// ```
@@ -81,7 +81,7 @@ impl<'a, Tk: Task, Deps> TaskBuilder<'a, Tk, Deps> {
///
/// ```no_run
/// # use dagx::{task, DagRunner, Task};
/// # use futures::FutureExt;
/// #
/// // Tuple struct
/// struct Value(i32);
///
@@ -118,7 +118,7 @@ impl<'a, Tk: Task, Deps> TaskBuilder<'a, Tk, Deps> {
/// // Multiple dependencies: tuple form
/// let sum = dag.add_task(Add).depends_on((&x, y));
///
/// dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
/// dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await.unwrap();
/// # };
/// ```
pub fn depends_on<D>(self, deps: D) -> TaskHandle<Tk::Output>
+1 -1
View File
@@ -63,7 +63,7 @@ impl std::fmt::Display for DagError {
f,
"Result not found for task #{}.\n\
\n\
Call dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).",
Call dag.run(|fut| async move {{ tokio::spawn(fut).await.unwrap() }}).",
task_id
)
}
+1 -1
View File
@@ -51,7 +51,7 @@ fn test_dag_error_display_result_not_found() {
assert!(display.contains("Result not found"));
assert!(display.contains("task #7"));
assert!(display.contains("Call dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))"));
assert!(display.contains("Call dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })"));
}
#[test]
+14 -15
View File
@@ -24,7 +24,6 @@
//!
//! ```no_run
//! use dagx::{task, DagRunner, Task, TaskHandle};
//! use futures::FutureExt; // for FutureExt::map
//!
//! // Source task with read-only state (tuple struct)
//! struct Value(i32);
@@ -53,7 +52,7 @@
//! let sum = dag.add_task(Add).depends_on((x, &y));
//!
//! // Execute and retrieve results
//! dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
//! dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await.unwrap();
//! assert_eq!(dag.get(sum).unwrap(), 5);
//! # };
//! ```
@@ -291,7 +290,7 @@
//!
//! ```no_run
//! use dagx::{task, DagRunner, Task};
//! use futures::FutureExt;
//!
//!
//! // Source task (tuple struct)
//! struct Value(i32);
@@ -324,7 +323,7 @@
//! let plus1 = dag.add_task(Add(1)).depends_on(&base);
//! let times2 = dag.add_task(Scale(2)).depends_on(&base);
//!
//! dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
//! dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await.unwrap();
//!
//! assert_eq!(dag.get(plus1).unwrap(), 11);
//! assert_eq!(dag.get(times2).unwrap(), 20);
@@ -337,7 +336,7 @@
//!
//! ```no_run
//! use dagx::{task, DagRunner, Task};
//! use futures::FutureExt;
//!
//!
//! // Source task for String (tuple struct)
//! struct Name(String);
@@ -381,7 +380,7 @@
//! let active = dag.add_task(Active(true));
//! let result = dag.add_task(FormatUser).depends_on((name, age, active));
//!
//! dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
//! dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await.unwrap();
//!
//! assert_eq!(dag.get(result).unwrap(), "User: Alice, Age: 30, Active: true");
//! # };
@@ -393,7 +392,7 @@
//!
//! ```no_run
//! use dagx::{task, DagRunner, Task};
//! use futures::FutureExt;
//!
//!
//! // Source task (tuple struct)
//! struct Value(i32);
@@ -434,7 +433,7 @@
//! // Layer 3: Final result
//! let total = dag.add_task(Add).depends_on((&sum_xy, &prod_yz)); // 5 + 15 = 20
//!
//! dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
//! dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await.unwrap();
//!
//! assert_eq!(dag.get(total).unwrap(), 20);
//! # };
@@ -447,7 +446,7 @@
//!
//! ```no_run
//! # use dagx::{task, DagRunner, Task};
//! # use futures::FutureExt;
//! #
//!
//! struct User {
//! name: String,
@@ -476,7 +475,7 @@
//! let user = dag.add_task(CreateUser);
//! let formatted = dag.add_task(FormatUser).depends_on(user);
//!
//! dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
//! dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await.unwrap();
//!
//! assert_eq!(dag.get(formatted).unwrap(), "Alice is 30 years old");
//! # };
@@ -533,7 +532,7 @@
//!
//! ```no_run
//! # use dagx::{task, DagRunner, Task};
//! # use futures::FutureExt;
//! #
//! # struct Value(i32);
//! # #[task]
//! # impl Value {
@@ -543,11 +542,11 @@
//! # let dag = DagRunner::new();
//! # let node = dag.add_task(Value(42));
//! // Simple approach with .unwrap()
//! dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
//! dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await.unwrap();
//! let result = dag.get(node).unwrap();
//!
//! // Or handle errors explicitly
//! match dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await {
//! match dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await {
//! Ok(_) => println!("DAG executed successfully"),
//! Err(e) => eprintln!("DAG execution failed: {}", e),
//! }
@@ -631,7 +630,7 @@
//!
//! ```
//! # use dagx::{task, DagRunner, Task};
//! # use futures::FutureExt;
//! #
//!
//! // ✅ CORRECT: Just output Vec<String>, framework wraps in Arc internally
//! struct FetchData;
@@ -661,7 +660,7 @@
//! let task2 = dag.add_task(ProcessData).depends_on(&data);
//! let task3 = dag.add_task(ProcessData).depends_on(&data);
//!
//! dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
//! dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await.unwrap();
//! # };
//! ```
//!
+13 -12
View File
@@ -9,14 +9,13 @@ use std::any::Any;
use std::collections::{HashMap, HashSet, VecDeque};
use std::future::Future;
use std::panic::AssertUnwindSafe;
use std::pin::Pin;
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};
use futures::future::BoxFuture;
use futures::stream::FuturesUnordered;
use futures::{FutureExt, StreamExt, TryFutureExt};
use futures_util::{stream::FuturesUnordered, FutureExt, StreamExt, TryFutureExt};
use parking_lot::Mutex;
#[cfg(feature = "tracing")]
@@ -28,6 +27,8 @@ use crate::node::{ExecutableNode, TypedNode};
use crate::task::Task;
use crate::types::{NodeId, Pending, TaskHandle};
type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
// Guard to ensure run_lock is released even on early return or panic
struct RunGuard<'a> {
lock: &'a AtomicBool,
@@ -56,7 +57,7 @@ impl<'a> Drop for RunGuard<'a> {
///
/// ```no_run
/// # use dagx::{task, DagRunner, Task};
/// # use futures::FutureExt;
/// #
/// // Task with state constructed via ::new()
/// struct LoadValue { value: i32 }
///
@@ -85,7 +86,7 @@ impl<'a> Drop for RunGuard<'a> {
/// let y = dag.add_task(LoadValue::new(3));
/// let sum = dag.add_task(Add).depends_on((x, y));
///
/// dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
/// dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await.unwrap();
///
/// assert_eq!(dag.get(sum).unwrap(), 5);
/// # };
@@ -150,7 +151,7 @@ impl DagRunner {
///
/// ```no_run
/// # use dagx::{task, DagRunner, Task};
/// # use futures::FutureExt;
/// #
/// // Task with state - shows you construct with specific value
/// struct LoadValue {
/// initial: i32,
@@ -192,7 +193,7 @@ impl DagRunner {
/// // Construct task with offset of 1
/// let inc = dag.add_task(AddOffset::new(1)).depends_on(base);
///
/// dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
/// dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await.unwrap();
/// assert_eq!(dag.get(inc).unwrap(), 11);
/// # };
/// ```
@@ -236,7 +237,7 @@ impl DagRunner {
///
/// - `spawner`: A function that spawns futures on the async runtime
/// and returns a handle to the task. This is the only way to run tasks on separate threads. Examples:
/// - Tokio: `|fut| { tokio::spawn(fut).map(Result::unwrap) }`
/// - Tokio: `|fut| { tokio::spawn(fut).await.unwrap() }`
/// - Smol: `|fut| { smol::spawn(fut) }`
/// - Async-std: `|fut| { async_std::task::spawn(fut) }`
/// - Single-threaded: `|fut| fut`
@@ -256,7 +257,7 @@ impl DagRunner {
///
/// ```
/// # use dagx::{task, DagRunner, Task};
/// # use futures::FutureExt;
/// #
/// // Tuple struct
/// struct Value(i32);
///
@@ -280,7 +281,7 @@ impl DagRunner {
/// let b = dag.add_task(Value(2));
/// let sum = dag.add_task(Add).depends_on((a, b));
///
/// dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap(); // Executes all tasks
/// dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await.unwrap(); // Executes all tasks
/// # };
/// ```
#[inline]
@@ -531,7 +532,7 @@ impl DagRunner {
///
/// ```no_run
/// # use dagx::{task, DagRunner, Task};
/// # use futures::FutureExt;
/// #
/// struct Configuration {
/// setting: i32,
/// }
@@ -553,7 +554,7 @@ impl DagRunner {
/// // Construct task with specific setting value
/// let task = dag.add_task(Configuration::new(42));
///
/// dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
/// dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await.unwrap();
///
/// assert_eq!(dag.get(task).unwrap(), 42);
/// # };
+9 -7
View File
@@ -4,8 +4,6 @@ use crate::error::DagError;
use crate::runner::DagRunner;
use crate::types::TaskHandle;
use futures::FutureExt;
// Initialize tracing subscriber for tests (idempotent)
#[cfg(feature = "tracing")]
fn init_tracing() {
@@ -69,7 +67,7 @@ async fn test_get_wrong_type() {
let dag = DagRunner::new();
let handle = dag.add_task(TestTask { value: 42 });
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
.unwrap();
@@ -144,12 +142,14 @@ async fn test_concurrent_run_protection() {
// Start two runs concurrently
let handle1 = tokio::spawn(async move {
barrier1.wait().await;
dag1.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await
dag1.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
});
let handle2 = tokio::spawn(async move {
barrier2.wait().await;
dag2.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await
dag2.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
});
// One should succeed, one should fail (concurrent execution not supported)
@@ -197,7 +197,7 @@ async fn test_invalid_node_id_in_get() {
_phantom: std::marker::PhantomData,
};
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
.unwrap();
@@ -250,7 +250,9 @@ async fn test_task_panic_in_multi_task_layer() {
let _good_task = dag.add_task(GoodTask).depends_on(source);
// Run the DAG - the panic should be caught
let result = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
let result = dag
.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await;
// The run should fail due to the panic
// Note: Tokio catches panics in spawned tasks, so the behavior depends on the runtime
+2 -2
View File
@@ -23,7 +23,7 @@ pub struct NodeId(pub usize);
///
/// ```no_run
/// # use dagx::{task, DagRunner, Task};
/// # use futures::FutureExt;
/// #
/// # struct LoadValue { value: i32 }
/// # impl LoadValue { pub fn new(v: i32) -> Self { Self { value: v } } }
/// # #[task]
@@ -34,7 +34,7 @@ pub struct NodeId(pub usize);
/// let dag = DagRunner::new();
/// let node = dag.add_task(LoadValue::new(42));
///
/// dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
/// dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await.unwrap();
///
/// assert_eq!(dag.get(node).unwrap(), 42);
/// # };
+5 -6
View File
@@ -3,7 +3,6 @@
//! These tests ensure we hit line 71 in the From<TaskBuilder> implementation
use crate::{task, DagRunner, TaskHandle};
use futures::FutureExt;
#[tokio::test]
async fn test_unit_type_taskbuilder_to_handle_conversion() {
@@ -31,7 +30,7 @@ async fn test_unit_type_taskbuilder_to_handle_conversion() {
// specifically covering line 71
let handle: TaskHandle<_> = builder.into();
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
.unwrap();
@@ -79,7 +78,7 @@ async fn test_unit_type_conversion_with_multiple_tasks() {
let second_handle: TaskHandle<_> = second_builder.into();
let third_handle: TaskHandle<bool> = third_builder.into();
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
.unwrap();
@@ -118,7 +117,7 @@ async fn test_unit_struct_with_state() {
// Explicit conversion - triggers line 71
let handle: TaskHandle<_> = builder.into();
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
.unwrap();
@@ -144,7 +143,7 @@ async fn test_taskbuilder_conversion_assignment() {
// This also uses From<TaskBuilder> implementation
let handle: TaskHandle<Vec<i32>> = builder.into();
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
.unwrap();
@@ -181,7 +180,7 @@ async fn test_mixed_unit_and_dependent_tasks() {
// Doubler needs dependencies, so we use it differently
let doubler = dag.add_task(Doubler).depends_on(source_handle);
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
.unwrap();