Address soundness hole in cycle prevention

This commit is contained in:
TechnoPorg committed 2026-01-29 06:56:17 +01:00
1 parent 4ad88bcd68
commit f82205baf4
80 files changed
+730 -717

No files matched your search

+9 -9
View File
@@ -47,7 +47,7 @@ async fn test_concurrent_run_calls() {
assert_eq!(error_count, 4);
// Verify results are consistent from the successful run
for (i, task) in tasks.iter().enumerate() {
for (i, task) in tasks.into_iter().enumerate() {
assert_eq!(dag.get(task).unwrap(), i);
}
}
@@ -71,7 +71,7 @@ async fn test_concurrent_execution_and_building() {
let mut handles = JoinSet::new();
// Reader tasks
for task in &initial {
for task in initial {
let dag = Arc::clone(&dag);
let task_handle: dagx::TaskHandle<i32> = task.into();
handles.spawn(async move { dag.get(task_handle).unwrap() });
@@ -116,7 +116,7 @@ async fn test_dag_builder_thread_safety() {
let transform1 = dag
.add_task(task_fn(move |x: i32| async move { x * 10 }))
.depends_on(&source);
.depends_on(source);
let transform2 = dag
.add_task(task_fn(move |x: i32| async move { x + thread_id }))
@@ -165,7 +165,7 @@ async fn test_concurrent_get_operations() {
// Now access results concurrently
let mut handles = JoinSet::new();
for task in tasks.iter() {
for task in tasks.into_iter() {
let dag = Arc::clone(&dag);
let task_handle: dagx::TaskHandle<i32> = task.into();
handles.spawn(async move { dag.get(task_handle).unwrap() });
@@ -193,7 +193,7 @@ async fn test_no_race_in_dependency_tracking() {
let mut handles = JoinSet::new();
// Multiple threads create tasks depending on the same source
let source_handle: dagx::TaskHandle<i32> = (&source).into();
let source_handle: dagx::TaskHandle<i32> = source.into();
for i in 0..50 {
let dag = Arc::clone(&dag);
let source_clone = source_handle;
@@ -245,14 +245,14 @@ async fn test_high_concurrency_stress() {
// Add a task with a simple dependency
let source = dag.add_task(task_fn(move |_: ()| async move { i }));
dag.add_task(task_fn(move |x: i32| async move { x * 2 }))
.depends_on(&source);
.depends_on(source);
}
2 => {
// Add a more complex subgraph
let a = dag.add_task(task_fn(move |_: ()| async move { i }));
let b = dag.add_task(task_fn(move |_: ()| async move { i + 1 }));
dag.add_task(task_fn(move |(x, y): (i32, i32)| async move { x + y }))
.depends_on((&a, &b));
.depends_on((a, b));
}
_ => unreachable!(),
}
@@ -332,7 +332,7 @@ async fn test_atomic_dag_operations() {
let mut handles = JoinSet::new();
let success_count = Arc::new(AtomicUsize::new(0));
let base_handle: dagx::TaskHandle<i32> = (&base).into();
let base_handle: dagx::TaskHandle<i32> = base.into();
for i in 0..50 {
let dag = Arc::clone(&dag);
let base_clone = base_handle;
@@ -382,7 +382,7 @@ async fn test_no_deadlock_on_circular_waiting() {
handles.spawn(async move {
// Each thread creates a chain of tasks
let first = dag.add_task(task_fn(move |_: ()| async move { i }));
let mut chain = vec![(&first).into()];
let mut chain = vec![first.into()];
for j in 0..5 {
let next = dag
+7 -6
View File
@@ -1,7 +1,7 @@
//! Tests for execution correctness and parallelism
use crate::common::task_fn;
use dagx::{DagResult, DagRunner};
use dagx::{DagResult, DagRunner, TaskHandle};
use futures::FutureExt;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
@@ -32,7 +32,7 @@ async fn test_layers_execute_in_parallel() -> DagResult<()> {
// Layer 1: Tasks depending on layer 0
let layer1: Vec<_> = sources
.iter()
.into_iter()
.enumerate()
.map(|(i, source)| {
let timing = layer_timing.clone();
@@ -168,7 +168,7 @@ async fn test_completion_order_matches_dependencies() -> DagResult<()> {
x + y
}
}))
.depends_on((&a, &b))
.depends_on((a, b))
};
let d = {
@@ -216,7 +216,7 @@ async fn test_dependent_tasks_respect_ordering() -> DagResult<()> {
let execution_order = Arc::new(Mutex::new(Vec::new()));
// Create a chain: A -> B -> C -> D
let a = {
let a: TaskHandle<_> = {
let order = execution_order.clone();
dag.add_task(task_fn(move |_: ()| {
let order = order.clone();
@@ -226,7 +226,8 @@ async fn test_dependent_tasks_respect_ordering() -> DagResult<()> {
1
}
}))
};
}
.into();
let b = {
let order = execution_order.clone();
@@ -238,7 +239,7 @@ async fn test_dependent_tasks_respect_ordering() -> DagResult<()> {
x + 1
}
}))
.depends_on(&a)
.depends_on(a)
};
let c = {
+1 -1
View File
@@ -29,7 +29,7 @@ async fn test_parallelism_proof_1000_tasks() -> DagResult<()> {
let elapsed = start.elapsed();
// Verify all tasks completed
for (i, task) in tasks.iter().enumerate() {
for (i, task) in tasks.into_iter().enumerate() {
assert_eq!(dag.get(task)?, i as u32);
}
+2 -2
View File
@@ -35,7 +35,7 @@ async fn test_spawner_actually_spawns_tasks() -> DagResult<()> {
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Verify all tasks were spawned
for (i, task) in tasks.iter().enumerate() {
for (i, task) in tasks.into_iter().enumerate() {
assert_eq!(dag.get(task)?, i as i32);
}
@@ -81,7 +81,7 @@ async fn test_tasks_not_running_inline() -> DagResult<()> {
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Verify results
for (i, task) in tasks.iter().enumerate() {
for (i, task) in tasks.into_iter().enumerate() {
assert_eq!(dag.get(task)?, i as i32);
}
+13 -13
View File
@@ -1,7 +1,7 @@
//! Tests for multi-threading and concurrent execution
use crate::common::task_fn;
use dagx::{DagResult, DagRunner};
use dagx::{DagResult, DagRunner, TaskHandle};
use futures::FutureExt;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
@@ -44,7 +44,7 @@ async fn test_tasks_run_on_different_threads() -> DagResult<()> {
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Verify all tasks completed
for task in tasks.iter() {
for task in tasks.into_iter() {
let _ = dag.get(task)?;
}
@@ -113,7 +113,7 @@ async fn test_concurrent_execution_with_atomic_counter() -> DagResult<()> {
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Verify all tasks completed
for (i, task) in tasks.iter().enumerate() {
for (i, task) in tasks.into_iter().enumerate() {
assert_eq!(dag.get(task)?, i);
}
@@ -141,7 +141,7 @@ async fn test_diamond_parallel_execution() -> DagResult<()> {
let concurrent_middle = Arc::new(AtomicUsize::new(0));
let source = dag.add_task(task_fn(|_: ()| async { 100 }));
let source: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 100 })).into();
// These should run in parallel
let parallel1 = {
@@ -157,7 +157,7 @@ async fn test_diamond_parallel_execution() -> DagResult<()> {
x * 2
}
}))
.depends_on(&source)
.depends_on(source)
};
let parallel2 = {
@@ -174,7 +174,7 @@ async fn test_diamond_parallel_execution() -> DagResult<()> {
x * 3
}
}))
.depends_on(&source)
.depends_on(source)
};
let sink = dag
@@ -196,7 +196,7 @@ async fn test_parallel_branches_with_dependencies() -> DagResult<()> {
let branch_progress = Arc::new(Mutex::new(Vec::new()));
// Source node
let source = dag.add_task(task_fn(|_: ()| async { 10 }));
let source: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 10 })).into();
// Branch 1: source -> b1_1 -> b1_2
let b1_1 = {
@@ -213,7 +213,7 @@ async fn test_parallel_branches_with_dependencies() -> DagResult<()> {
x * 2
}
}))
.depends_on(&source)
.depends_on(source)
};
let b1_2 = {
@@ -248,7 +248,7 @@ async fn test_parallel_branches_with_dependencies() -> DagResult<()> {
x * 3
}
}))
.depends_on(&source)
.depends_on(source)
};
let b2_2 = {
@@ -320,7 +320,7 @@ async fn test_wide_fanout_parallel_execution() -> DagResult<()> {
let max_concurrent = Arc::new(AtomicUsize::new(0));
let current_concurrent = Arc::new(AtomicUsize::new(0));
let source = dag.add_task(task_fn(|_: ()| async { 42 }));
let source: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 42 })).into();
// Create 50 tasks that all depend on the source
let dependents: Vec<_> = (0..50)
@@ -356,7 +356,7 @@ async fn test_wide_fanout_parallel_execution() -> DagResult<()> {
x + i
}
}))
.depends_on(&source)
.depends_on(source)
})
.collect();
@@ -388,7 +388,7 @@ async fn test_massive_parallel_fanout() -> DagResult<()> {
let max_concurrent = Arc::new(AtomicUsize::new(0));
let current = Arc::new(AtomicUsize::new(0));
let source = dag.add_task(task_fn(|_: ()| async { 1 }));
let source: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 1 })).into();
// Create 1000 tasks all depending on the same source
let tasks: Vec<_> = (0..1000)
@@ -426,7 +426,7 @@ async fn test_massive_parallel_fanout() -> DagResult<()> {
x + i
}
}))
.depends_on(&source)
.depends_on(source)
})
.collect();
+7 -7
View File
@@ -2,7 +2,7 @@
use crate::common::task_fn;
use dagx::{DagResult, DagRunner};
use dagx::{DagResult, DagRunner, TaskHandle};
use futures::FutureExt;
use std::sync::Arc;
use std::sync::Mutex;
@@ -32,7 +32,7 @@ async fn test_parallel_execution_speedup() -> DagResult<()> {
let elapsed = start.elapsed();
// Verify all tasks completed
for (i, task) in tasks.iter().enumerate() {
for (i, task) in tasks.into_iter().enumerate() {
assert_eq!(dag.get(task)?, i);
}
@@ -84,15 +84,15 @@ async fn test_sync_tasks_dont_block_async_runtime() -> DagResult<()> {
let start_times = Arc::new(Mutex::new(Vec::new()));
// Mix of sync and async tasks
let source = dag.add_task(task_fn(|_: ()| async { 10 }));
let source: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 10 })).into();
// Sync tasks
let sync1 = dag.add_task(SyncTask).depends_on(&source);
let sync2 = dag.add_task(SyncTask).depends_on(&source);
let sync1 = dag.add_task(SyncTask).depends_on(source);
let sync2 = dag.add_task(SyncTask).depends_on(source);
// Async tasks
let async1 = dag.add_task(AsyncTask).depends_on(&source);
let async2 = dag.add_task(AsyncTask).depends_on(&source);
let async1 = dag.add_task(AsyncTask).depends_on(source);
let async2 = dag.add_task(AsyncTask).depends_on(source);
// Track timing
let timing_task = {