Merge pull request #9 from TechnoPorg/push-kplnqxpsroms

perf: use passthrough hashers instead of standard library default
This commit is contained in:
TechnoPorg authored and GitHub committed 2026-02-11 23:15:27 +01:00
commit 5bfb284c82
36 files changed
+168 -103

No files matched your search

-1
View File
@@ -3,7 +3,6 @@
use criterion::Criterion;
use dagx::{task_fn, DagRunner};
pub fn bench_dag_execution(c: &mut Criterion) {
let rt = tokio::runtime::Runtime::new().unwrap();
-1
View File
@@ -3,7 +3,6 @@
use criterion::{BenchmarkId, Criterion};
use dagx::{task_fn, DagRunner};
pub fn bench_dag_scaling(c: &mut Criterion) {
let rt = tokio::runtime::Runtime::new().unwrap();
let mut group = c.benchmark_group("scaling");
-1
View File
@@ -5,7 +5,6 @@
use criterion::Criterion;
use dagx::TaskHandle;
pub fn bench_deep_chain(c: &mut Criterion) {
let rt = tokio::runtime::Runtime::new().unwrap();
let mut group = c.benchmark_group("deep_chain_100_tasks");
-1
View File
@@ -5,7 +5,6 @@
use criterion::Criterion;
use dagx::TaskHandle;
pub fn bench_diamond_pattern(c: &mut Criterion) {
let rt = tokio::runtime::Runtime::new().unwrap();
let mut group = c.benchmark_group("diamond_pattern");
-1
View File
@@ -4,7 +4,6 @@
use criterion::Criterion;
pub fn bench_etl_pipeline(c: &mut Criterion) {
let rt = tokio::runtime::Runtime::new().unwrap();
let mut group = c.benchmark_group("etl_pipeline");
-1
View File
@@ -4,7 +4,6 @@
use criterion::Criterion;
pub fn bench_large_scale(c: &mut Criterion) {
let rt = tokio::runtime::Runtime::new().unwrap();
let mut group = c.benchmark_group("large_scale_10k_tasks");
-1
View File
@@ -4,7 +4,6 @@
use criterion::Criterion;
pub fn bench_linear_pipeline(c: &mut Criterion) {
let rt = tokio::runtime::Runtime::new().unwrap();
let mut group = c.benchmark_group("linear_pipeline_5_tasks");
-1
View File
@@ -5,7 +5,6 @@
use criterion::Criterion;
use dagx::TaskHandle;
pub fn bench_wide_fanout(c: &mut Criterion) {
let rt = tokio::runtime::Runtime::new().unwrap();
let mut group = c.benchmark_group("wide_fanout_100_tasks");
-1
View File
@@ -3,7 +3,6 @@
use criterion::Criterion;
use dagx::{task_fn, DagRunner};
pub fn bench_10k_breakdown(c: &mut Criterion) {
let rt = tokio::runtime::Runtime::new().unwrap();
let mut group = c.benchmark_group("10k_tasks");
-1
View File
@@ -3,7 +3,6 @@
use criterion::Criterion;
use dagx::{task_fn, DagRunner, TaskHandle};
pub fn bench_diamond(c: &mut Criterion) {
let rt = tokio::runtime::Runtime::new().unwrap();
-1
View File
@@ -3,7 +3,6 @@
use criterion::Criterion;
use dagx::{task_fn, DagRunner, TaskHandle};
pub fn bench_fanout(c: &mut Criterion) {
let rt = tokio::runtime::Runtime::new().unwrap();
-1
View File
@@ -3,7 +3,6 @@
use criterion::{BenchmarkId, Criterion};
use dagx::{task_fn, DagRunner, TaskHandle};
pub fn bench_etl_pipeline(c: &mut Criterion) {
let rt = tokio::runtime::Runtime::new().unwrap();
let mut group = c.benchmark_group("etl_scaling");
-1
View File
@@ -3,7 +3,6 @@
use criterion::Criterion;
use dagx::{task_fn, DagRunner, TaskHandle};
pub fn bench_mixed_patterns(c: &mut Criterion) {
let rt = tokio::runtime::Runtime::new().unwrap();
-1
View File
@@ -32,7 +32,6 @@
use dagx::{task, DagRunner};
// Step 1: Define a source task (no dependencies)
//
// Source tasks produce initial data for the DAG.
-1
View File
@@ -57,7 +57,6 @@
use dagx::{task, DagRunner, TaskHandle};
// Source task with a value field
struct Source {
initial_value: i32,
-1
View File
@@ -63,7 +63,6 @@
use dagx::{task, DagRunner};
struct FetchName;
#[task]
+2 -1
View File
@@ -125,7 +125,8 @@ async fn main() -> DagResult<()> {
let total = dag.add_task(Aggregate).depends_on((sum1, sum2, sum3, sum4));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
let result = dag.get(total)?;
let elapsed = start.elapsed();
+2 -2
View File
@@ -68,7 +68,6 @@
use dagx::{task, DagResult, DagRunner};
// Extract: Load data from different sources
struct LoadData {
source: &'static str,
@@ -202,7 +201,8 @@ async fn main() -> DagResult<()> {
// Execute the entire ETL pipeline
println!("Executing pipeline...\n");
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
// Display final report
println!("\n{}", dag.get(report)?);
+2 -2
View File
@@ -57,7 +57,6 @@
use dagx::{task, DagResult, DagRunner};
// Task that validates and processes input
struct ValidateAndDouble {
value: i32,
@@ -138,7 +137,8 @@ async fn main() -> DagResult<()> {
.depends_on((&cat1, &cat2, &cat3));
// Execute
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
// Display results
println!("\nFinal summary: {}", dag.get(summary)?);
-1
View File
@@ -36,7 +36,6 @@
use dagx::{task, DagRunner, TaskHandle};
#[derive(Debug)]
struct User {
name: String,
-1
View File
@@ -77,7 +77,6 @@
use dagx::{task, DagRunner};
// Data source tasks
struct LoadValue1;
-1
View File
@@ -116,7 +116,6 @@
use dagx::{task, DagRunner};
// Source task that provides a string input
struct StringSource(&'static str);
+2 -2
View File
@@ -176,7 +176,7 @@ fn test_deps_tuple_large_tuples() {
let node_ids = (&handles[0], &handles[1], &handles[2], &handles[3]).to_node_ids();
assert_eq!(node_ids.len(), 4);
for (i, node_id) in node_ids.iter().enumerate().take(4) {
assert_eq!(*node_id, NodeId(i));
assert_eq!(*node_id, NodeId(i as u32));
}
// Test 8-tuple
@@ -193,7 +193,7 @@ fn test_deps_tuple_large_tuples() {
.to_node_ids();
assert_eq!(node_ids.len(), 8);
for (i, node_id) in node_ids.iter().enumerate().take(8) {
assert_eq!(*node_id, NodeId(i));
assert_eq!(*node_id, NodeId(i as u32));
}
}
+3 -6
View File
@@ -6,19 +6,16 @@
#[derive(Debug, Clone, PartialEq)]
pub enum DagError {
/// Invalid dependency: task does not exist
InvalidDependency { task_id: usize },
InvalidDependency { task_id: u32 },
/// Type mismatch in task dependencies
TypeMismatch {
expected: &'static str,
found: &'static str,
},
/// Task panicked during execution
TaskPanicked {
task_id: usize,
panic_message: String,
},
TaskPanicked { task_id: u32, panic_message: String },
/// Result not found for task
ResultNotFound { task_id: usize },
ResultNotFound { task_id: u32 },
/// Attempted to run the DAG while it's already running
ConcurrentExecution,
}
+46 -13
View File
@@ -8,6 +8,7 @@
use std::any::Any;
use std::collections::{HashMap, HashSet, VecDeque};
use std::future::Future;
use std::hash::{BuildHasher, Hasher};
use std::panic::AssertUnwindSafe;
use std::pin::Pin;
use std::sync::{
@@ -40,6 +41,35 @@ impl<'a> Drop for RunGuard<'a> {
}
}
#[derive(Default, Clone)]
pub(crate) struct PassThroughHasher {
hash: u64,
}
impl Hasher for PassThroughHasher {
fn finish(&self) -> u64 {
self.hash
}
fn write_u32(&mut self, i: u32) {
self.hash = i as u64;
}
fn write(&mut self, _bytes: &[u8]) {
panic!("PassThroughHasher used on invalid type");
}
}
impl BuildHasher for PassThroughHasher {
type Hasher = PassThroughHasher;
fn build_hasher(&self) -> Self::Hasher {
PassThroughHasher::default()
}
}
pub(crate) type PassThroughHashMap<K, V> = HashMap<K, V, PassThroughHasher>;
/// Build and execute a typed DAG of tasks.
///
/// A `DagRunner` is the main orchestrator for building and executing a directed acyclic graph
@@ -100,10 +130,11 @@ impl<'a> Drop for RunGuard<'a> {
/// Arc enables efficient sharing during fanout without cloning data.
pub struct DagRunner {
pub(crate) nodes: Mutex<Vec<Option<Box<dyn ExecutableNode + Sync>>>>,
pub(crate) outputs: Mutex<HashMap<NodeId, std::sync::Arc<dyn std::any::Any + Send + Sync>>>,
pub(crate) edges: Mutex<HashMap<NodeId, Vec<NodeId>>>, // node -> dependencies
pub(crate) dependents: Mutex<HashMap<NodeId, Vec<NodeId>>>, // node -> tasks that depend on it
pub(crate) next_id: Mutex<usize>,
pub(crate) outputs:
Mutex<PassThroughHashMap<NodeId, std::sync::Arc<dyn std::any::Any + Send + Sync>>>,
pub(crate) edges: Mutex<PassThroughHashMap<NodeId, Vec<NodeId>>>, // node -> dependencies
pub(crate) dependents: Mutex<PassThroughHashMap<NodeId, Vec<NodeId>>>, // node -> tasks that depend on it
pub(crate) next_id: Mutex<u32>,
pub(crate) run_lock: AtomicBool, // Ensures only one run() at a time
}
@@ -126,9 +157,9 @@ impl DagRunner {
pub fn new() -> Self {
Self {
nodes: Mutex::new(Vec::new()),
outputs: Mutex::new(HashMap::new()),
edges: Mutex::new(HashMap::new()),
dependents: Mutex::new(HashMap::new()),
outputs: Mutex::new(HashMap::default()),
edges: Mutex::new(HashMap::default()),
dependents: Mutex::new(HashMap::default()),
next_id: Mutex::new(0),
run_lock: AtomicBool::new(false),
}
@@ -314,16 +345,18 @@ impl DagRunner {
// Build topological layers
let layers = self.compute_layers()?;
let total_tasks = layers.iter().map(|l| l.len()).sum::<usize>();
#[cfg(feature = "tracing")]
debug!(
layer_count = layers.len(),
total_tasks = layers.iter().map(|l| l.len()).sum::<usize>(),
"computed topological layers"
total_tasks, "computed topological layers"
);
let edges = self.edges.lock().clone();
let mut outputs: HashMap<NodeId, Arc<dyn Any + Send + Sync>> = HashMap::new();
let mut outputs: PassThroughHashMap<NodeId, Arc<dyn Any + Send + Sync>> =
HashMap::with_capacity_and_hasher(total_tasks, PassThroughHasher::default());
let mut first_error = None;
for layer in layers {
@@ -368,7 +401,7 @@ impl DagRunner {
// Take ownership of the node
let node = {
let mut nodes_lock = self.nodes.lock();
nodes_lock[node_id.0].take()
nodes_lock[node_id.0 as usize].take()
};
if let Some(node) = node {
@@ -432,7 +465,7 @@ impl DagRunner {
// Take ownership of the node
let node = {
let mut nodes_lock = self.nodes.lock();
nodes_lock[node_id.0].take()
nodes_lock[node_id.0 as usize].take()
};
if let Some(node) = node {
let dependencies: Vec<_> = edges[&node_id]
@@ -586,7 +619,7 @@ impl DagRunner {
#[cfg(feature = "tracing")]
debug!("computing topological layers");
let mut in_degree: HashMap<NodeId, usize> = HashMap::new();
let mut in_degree: PassThroughHashMap<NodeId, usize> = HashMap::default();
let mut layers = Vec::new();
// Calculate in-degrees: for each node, count how many dependencies it has
+1 -1
View File
@@ -8,7 +8,7 @@ use crate::task::Task;
/// Opaque node identifier
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct NodeId(pub usize);
pub struct NodeId(pub u32);
/// Opaque, typed token for a node's output.
///
+18 -9
View File
@@ -20,7 +20,8 @@ async fn test_1000_level_deep_chain() -> DagResult<()> {
.depends_on(current);
}
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
assert_eq!(dag.get(current)?, 999);
Ok(())
@@ -50,7 +51,8 @@ async fn test_deep_chain_with_branches() -> DagResult<()> {
}
}
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
assert_eq!(dag.get(main_chain)?, 499);
@@ -108,7 +110,8 @@ async fn test_multiple_parallel_deep_chains() -> DagResult<()> {
&chain_ends[7],
));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
// Each chain: start + 500
// Chain 0: 0 + 500 = 500
@@ -149,7 +152,8 @@ async fn test_deep_chain_execution_order() -> DagResult<()> {
.depends_on(current);
}
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
assert_eq!(order.load(Ordering::SeqCst), 100);
assert_eq!(dag.get(current)?, 99);
@@ -179,7 +183,8 @@ async fn test_deep_binary_tree() -> DagResult<()> {
let root = build_tree(&dag, 10, 1);
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
// Sum of all values in a complete binary tree with values 1024..2047
// This is sum from 2^10 to 2^11-1 = 1024 * 1023 / 2 + 1024 * 512 = 1047552
@@ -209,7 +214,8 @@ async fn test_fibonacci_chain() -> DagResult<()> {
prev1 = next;
}
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
// 50th Fibonacci number (0-indexed) = 7778742049
assert_eq!(dag.get(prev1)?, 7778742049);
@@ -232,7 +238,8 @@ async fn test_deep_chain_with_accumulator() -> DagResult<()> {
.depends_on(current);
}
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
let result = dag.get(current)?;
assert_eq!(result, 20100); // sum of 1..=200
@@ -270,7 +277,8 @@ async fn test_zigzag_dependencies() -> DagResult<()> {
.add_task(task_fn::<(i32, i32), _, _>(|(a, b): (&i32, &i32)| a + b))
.depends_on((&chain_a, &chain_b));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
let result = dag.get(final_task)?;
assert!(result > 100);
@@ -293,7 +301,8 @@ async fn test_10000_level_chain_stress() -> DagResult<()> {
.depends_on(current);
}
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
assert_eq!(dag.get(current)?, 9_999);
Ok(())
+10 -5
View File
@@ -23,7 +23,8 @@ async fn test_10000_independent_tasks() -> DagResult<()> {
})
.collect();
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
// Verify all tasks completed
assert_eq!(counter.load(Ordering::Relaxed), 10_000);
@@ -100,7 +101,8 @@ async fn test_5000_node_pyramid() -> DagResult<()> {
.add_task(task_fn::<(i32, i32), _, _>(|(a, b): (&i32, &i32)| a + b))
.depends_on((&current_layer[0], &current_layer[1]));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
// We created 1024 ones and 1024 zeros at the bottom
// The sum should propagate up to 1024
@@ -209,7 +211,8 @@ async fn test_wide_dag_1000_sources_1000_sinks() -> DagResult<()> {
})
.collect();
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
// Verify some sinks completed correctly
let result0 = dag.get(sinks[0])?;
@@ -258,7 +261,8 @@ async fn test_10000_node_linear_chain_segments() -> DagResult<()> {
chain_ends.push(current);
}
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
// All 10,000 tasks should have executed
assert_eq!(counter.load(Ordering::Relaxed), 10_000);
@@ -297,7 +301,8 @@ async fn test_100000_nodes_stress() -> DagResult<()> {
.collect();
}
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
assert_eq!(completed.load(Ordering::Relaxed), 100_000);
Ok(())
+16 -8
View File
@@ -37,7 +37,8 @@ async fn test_zero_sized_types_throughout() -> DagResult<()> {
}))
.depends_on(t2);
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
assert_eq!(dag.get(t1)?, Empty);
assert_eq!(dag.get(t2)?, 42);
@@ -72,7 +73,8 @@ async fn test_large_value_types() -> DagResult<()> {
}))
.depends_on(t1);
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
assert_eq!(dag.get(t2)?, 1042);
@@ -95,7 +97,8 @@ async fn test_unit_type_chains() -> DagResult<()> {
.add_task(task_fn::<(), _, _>(|_: ()| "done"))
.depends_on((t3,));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
dag.get(t1)?; // Verify no error
dag.get(t2)?;
@@ -145,7 +148,8 @@ async fn test_string_types_variety() -> DagResult<()> {
))
.depends_on((t1, t2, t3, t4, t5, t6));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
let result = dag.get(combined)?;
assert!(result.contains("static str"));
@@ -178,7 +182,8 @@ async fn test_generic_type_constraints() -> DagResult<()> {
))
.depends_on((t1, t2, t3));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
let result = dag.get(combined)?;
assert!(result.contains("Wrapper(42)"));
@@ -204,7 +209,8 @@ async fn test_reference_wrapper_types() -> DagResult<()> {
.add_task(task_fn::<i32, _, _>(|&val: &i32| val * 2))
.depends_on(t1);
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
assert_eq!(dag.get(t2)?, 200);
@@ -231,7 +237,8 @@ async fn test_array_types() -> DagResult<()> {
))
.depends_on((t1, t2));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
assert_eq!(dag.get(t3)?, 55);
@@ -270,7 +277,8 @@ async fn test_enum_variants() -> DagResult<()> {
))
.depends_on((t1, t2, t3, t4));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
assert!(dag.get(combined)?);
+22 -11
View File
@@ -12,7 +12,8 @@ async fn test_empty_dag_execution() -> DagResult<()> {
// Test that an empty DAG can be created and run without issues
let dag = DagRunner::new();
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
// Should complete successfully with no tasks
Ok(())
@@ -25,7 +26,8 @@ async fn test_single_task_no_deps() -> DagResult<()> {
let task = dag.add_task(task_fn::<(), _, _>(|_: ()| 42));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
assert_eq!(dag.get(task)?, 42);
Ok(())
@@ -44,7 +46,8 @@ async fn test_single_task_unit_input_output() -> DagResult<()> {
exec.fetch_add(1, Ordering::SeqCst);
}));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
dag.get(task)?; // Just verify it doesn't error
assert_eq!(executed.load(Ordering::SeqCst), 1);
@@ -61,7 +64,8 @@ async fn test_zero_sized_type_task() -> DagResult<()> {
let task = dag.add_task(task_fn::<(), _, _>(|_: ()| ZeroSized));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
assert_eq!(dag.get(task)?, ZeroSized);
Ok(())
@@ -73,7 +77,8 @@ async fn test_empty_dag_multiple_runs() -> DagResult<()> {
let dag = DagRunner::new();
for _ in 0..10 {
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
}
Ok(())
@@ -88,7 +93,8 @@ async fn test_dag_with_only_source_tasks() -> DagResult<()> {
.map(|i| dag.add_task(task_fn::<(), _, _>(move |_: ()| i)))
.collect();
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
for (i, task) in tasks.into_iter().enumerate() {
assert_eq!(dag.get(task)?, i);
@@ -114,7 +120,8 @@ async fn test_single_self_contained_stateful_task() -> DagResult<()> {
let dag = DagRunner::new();
let task = dag.add_task(Counter { value: 21 });
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
assert_eq!(dag.get(task)?, 42);
Ok(())
@@ -128,11 +135,13 @@ async fn test_alternating_empty_and_filled_dag_runs() -> DagResult<()> {
if i % 2 == 0 {
// Empty DAG
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
} else {
// DAG with a task
let task = dag.add_task(task_fn::<(), _, _>(move |_: ()| i));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
assert_eq!(dag.get(task)?, i);
}
}
@@ -152,7 +161,8 @@ async fn test_dag_with_never_type_simulation() -> DagResult<()> {
// This task completes immediately but returns a Result containing "never" type
let task = dag.add_task(task_fn::<(), _, _>(|_: ()| Result::<i32, Never>::Ok(42)));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
assert_eq!(dag.get(task)?, Ok(42));
Ok(())
@@ -175,7 +185,8 @@ async fn test_minimal_task_with_minimal_async_work() -> DagResult<()> {
let task = dag.add_task(YieldTask);
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
assert_eq!(dag.get(task)?, 1337);
Ok(())
+8 -4
View File
@@ -79,7 +79,8 @@ async fn test_dependency_resolution_order() -> DagResult<()> {
.depends_on(d)
};
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
assert_eq!(dag.get(e)?, 5);
@@ -113,7 +114,8 @@ async fn test_transitive_dependencies() -> DagResult<()> {
.add_task(task_fn::<i32, _, _>(|&x: &i32| x + 5))
.depends_on(b);
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
assert_eq!(dag.get(a)?, 10);
assert_eq!(dag.get(b)?, 20);
@@ -136,7 +138,8 @@ async fn test_deep_chain_50_levels() -> DagResult<()> {
handles.push(next);
}
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
assert_eq!(dag.get(handles.last().unwrap())?, 50);
Ok(())
@@ -155,7 +158,8 @@ async fn test_deep_chain_200_levels() -> DagResult<()> {
.depends_on(current);
}
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
assert_eq!(dag.get(current)?, 201);
Ok(())
+10 -6
View File
@@ -3,7 +3,6 @@
use crate::common::task_fn;
use dagx::{DagResult, DagRunner, TaskHandle};
#[tokio::test]
async fn test_single_dependency() -> DagResult<()> {
let dag = DagRunner::new();
@@ -13,7 +12,8 @@ async fn test_single_dependency() -> DagResult<()> {
.add_task(task_fn::<i32, _, _>(|&x: &i32| x + 1))
.depends_on(source);
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
assert_eq!(dag.get(dependent)?, 43);
Ok(())
@@ -44,7 +44,8 @@ async fn test_dependency_data_flow() -> DagResult<()> {
.add_task(task_fn::<(i32, i32), _, _>(|(s, p): (&i32, &i32)| s + p))
.depends_on((&sum, &product));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
// Note: sum and product are not sinks (final_result depends on them)
// Only final_result is a sink and can be retrieved
@@ -68,7 +69,8 @@ async fn test_dependencies_with_different_types() -> DagResult<()> {
))
.depends_on((int_source, string_source, bool_source));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
assert_eq!(dag.get(combined)?, "hello: 42 (true)");
Ok(())
@@ -89,7 +91,8 @@ async fn test_shared_dependencies() -> DagResult<()> {
})
.collect();
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
// Verify all dependents got the correct value from shared
for (i, handle) in dependents.iter().enumerate() {
@@ -108,7 +111,8 @@ async fn test_multiple_source_nodes() -> DagResult<()> {
.map(|i| dag.add_task(task_fn::<(), _, _>(move |_: ()| i * 10)))
.collect();
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
for (i, source) in sources.into_iter().enumerate() {
assert_eq!(dag.get(source)?, i as i32 * 10);
+14 -8
View File
@@ -3,7 +3,6 @@
use crate::common::task_fn;
use dagx::{DagResult, DagRunner};
#[tokio::test]
async fn test_two_dependencies() -> DagResult<()> {
let dag = DagRunner::new();
@@ -14,7 +13,8 @@ async fn test_two_dependencies() -> DagResult<()> {
.add_task(task_fn::<(i32, i32), _, _>(|(x, y): (&i32, &i32)| x + y))
.depends_on((a, b));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
assert_eq!(dag.get(sum)?, 30);
Ok(())
@@ -33,7 +33,8 @@ async fn test_three_dependencies() -> DagResult<()> {
))
.depends_on((a, b, c));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
assert_eq!(dag.get(sum)?, 6);
Ok(())
@@ -53,7 +54,8 @@ async fn test_four_dependencies() -> DagResult<()> {
))
.depends_on((&deps[0], &deps[1], &deps[2], &deps[3]));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
assert_eq!(dag.get(sum)?, 10); // 1+2+3+4
Ok(())
@@ -73,7 +75,8 @@ async fn test_five_dependencies() -> DagResult<()> {
))
.depends_on((&deps[0], &deps[1], &deps[2], &deps[3], &deps[4]));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
assert_eq!(dag.get(sum)?, 15); // 1+2+3+4+5
Ok(())
@@ -93,7 +96,8 @@ async fn test_six_dependencies() -> DagResult<()> {
))
.depends_on((&deps[0], &deps[1], &deps[2], &deps[3], &deps[4], &deps[5]));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
assert_eq!(dag.get(sum)?, 21); // 1+2+3+4+5+6
Ok(())
@@ -117,7 +121,8 @@ async fn test_seven_dependencies() -> DagResult<()> {
&deps[0], &deps[1], &deps[2], &deps[3], &deps[4], &deps[5], &deps[6],
));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
assert_eq!(dag.get(sum)?, 28); // 1+2+3+4+5+6+7
Ok(())
@@ -141,7 +146,8 @@ async fn test_eight_dependencies() -> DagResult<()> {
&deps[0], &deps[1], &deps[2], &deps[3], &deps[4], &deps[5], &deps[6], &deps[7],
));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
assert_eq!(dag.get(sum)?, 36); // Sum of 1..=8
Ok(())
+4 -1
View File
@@ -31,7 +31,10 @@ async fn test_concurrent_run_calls() {
for _ in 0..5 {
let dag = Arc::clone(&dag);
handles.spawn(async move { dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await });
handles.spawn(async move {
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
});
}
// Only one run should succeed, others should fail with concurrent execution error
+6 -3
View File
@@ -87,7 +87,8 @@ async fn test_layers_execute_in_parallel() -> DagResult<()> {
.depends_on((&layer1[0], &layer1[1], &layer1[2]))
};
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
assert_eq!(dag.get(final_task)?, 33); // (0+1) + (10+1) + (20+1)
@@ -213,7 +214,8 @@ async fn test_completion_order_matches_dependencies() -> DagResult<()> {
.depends_on(c)
};
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
assert_eq!(dag.get(d)?, 6); // (1 + 2) * 2
@@ -313,7 +315,8 @@ async fn test_dependent_tasks_respect_ordering() -> DagResult<()> {
.depends_on(c)
};
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
// Verify results
assert_eq!(dag.get(a)?, 1);
+2 -2
View File
@@ -2,7 +2,6 @@
use dagx::{DagResult, DagRunner};
struct Value(i32);
#[dagx::task]
impl Value {
@@ -27,7 +26,8 @@ async fn runtime_integration_tokio() -> DagResult<()> {
let b = dag.add_task(Value(20));
let sum = dag.add_task(Add).depends_on((a, b));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
assert_eq!(dag.get(sum)?, 30);
Ok(())