Address soundness hole in cycle prevention
This commit is contained in:
1 parent
4ad88bcd68
commit
f82205baf4
80 files changed
+730
-717
No files matched your search
@@ -52,7 +52,7 @@ pub fn bench_dag_execution(c: &mut Criterion) {
|
||||
let t0 = dag.add_task(Value(0));
|
||||
let t1 = dag
|
||||
.add_task(task_fn(|x: i32| async move { x + 1 }))
|
||||
.depends_on(&t0);
|
||||
.depends_on(t0);
|
||||
let t2 = dag
|
||||
.add_task(task_fn(|x: i32| async move { x + 1 }))
|
||||
.depends_on(t1);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
//! Long sequential chain: 100 tasks in sequence
|
||||
|
||||
use criterion::Criterion;
|
||||
use dagx::TaskHandle;
|
||||
use futures::FutureExt;
|
||||
|
||||
pub fn bench_deep_chain(c: &mut Criterion) {
|
||||
@@ -17,10 +18,10 @@ pub fn bench_deep_chain(c: &mut Criterion) {
|
||||
|
||||
let dag = DagRunner::new();
|
||||
|
||||
let first = dag.add_task(task_fn(|_: ()| async { 0 }));
|
||||
let first: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 0 })).into();
|
||||
let mut prev = dag
|
||||
.add_task(task_fn(|x: i32| async move { x + 1 }))
|
||||
.depends_on(&first);
|
||||
.depends_on(first);
|
||||
|
||||
for i in 2..100 {
|
||||
prev = dag
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
//! Classic diamond: A → B,C → D
|
||||
|
||||
use criterion::Criterion;
|
||||
use dagx::TaskHandle;
|
||||
use futures::FutureExt;
|
||||
|
||||
pub fn bench_diamond_pattern(c: &mut Criterion) {
|
||||
@@ -17,13 +18,13 @@ pub fn bench_diamond_pattern(c: &mut Criterion) {
|
||||
|
||||
let dag = DagRunner::new();
|
||||
|
||||
let a = dag.add_task(task_fn(|_: ()| async { 10 }));
|
||||
let a: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 10 })).into();
|
||||
let b = dag
|
||||
.add_task(task_fn(|x: i32| async move { x * 2 }))
|
||||
.depends_on(&a);
|
||||
.depends_on(a);
|
||||
let c = dag
|
||||
.add_task(task_fn(|x: i32| async move { x + 5 }))
|
||||
.depends_on(&a);
|
||||
.depends_on(a);
|
||||
let d = dag
|
||||
.add_task(task_fn(|(x, y): (i32, i32)| async move { x + y }))
|
||||
.depends_on((&b, &c));
|
||||
|
||||
@@ -27,19 +27,19 @@ pub fn bench_etl_pipeline(c: &mut Criterion) {
|
||||
.add_task(task_fn(|data: Vec<i32>| async move {
|
||||
data.into_iter().map(|x| x * 2).collect::<Vec<_>>()
|
||||
}))
|
||||
.depends_on(&source1);
|
||||
.depends_on(source1);
|
||||
|
||||
let transform2 = dag
|
||||
.add_task(task_fn(|data: Vec<i32>| async move {
|
||||
data.into_iter().map(|x| x * 2).collect::<Vec<_>>()
|
||||
}))
|
||||
.depends_on(&source2);
|
||||
.depends_on(source2);
|
||||
|
||||
let transform3 = dag
|
||||
.add_task(task_fn(|data: Vec<i32>| async move {
|
||||
data.into_iter().map(|x| x * 2).collect::<Vec<_>>()
|
||||
}))
|
||||
.depends_on(&source3);
|
||||
.depends_on(source3);
|
||||
|
||||
// Merge all transformed data
|
||||
let merge = dag
|
||||
|
||||
@@ -20,7 +20,7 @@ pub fn bench_linear_pipeline(c: &mut Criterion) {
|
||||
let a = dag.add_task(task_fn(|_: ()| async { 1 }));
|
||||
let b = dag
|
||||
.add_task(task_fn(|x: i32| async move { x + 1 }))
|
||||
.depends_on(&a);
|
||||
.depends_on(a);
|
||||
let c = dag
|
||||
.add_task(task_fn(|x: i32| async move { x * 2 }))
|
||||
.depends_on(b);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
//! One source task feeding 100 dependent tasks
|
||||
|
||||
use criterion::Criterion;
|
||||
use dagx::TaskHandle;
|
||||
use futures::FutureExt;
|
||||
|
||||
pub fn bench_wide_fanout(c: &mut Criterion) {
|
||||
@@ -16,12 +17,12 @@ pub fn bench_wide_fanout(c: &mut Criterion) {
|
||||
use dagx::{task_fn, DagRunner};
|
||||
|
||||
let dag = DagRunner::new();
|
||||
let source = dag.add_task(task_fn(|_: ()| async { 42 }));
|
||||
let source: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 42 })).into();
|
||||
|
||||
// Create 100 tasks that all depend on source
|
||||
for i in 0..100 {
|
||||
dag.add_task(task_fn(move |x: i32| async move { x + i }))
|
||||
.depends_on(&source);
|
||||
.depends_on(source);
|
||||
}
|
||||
|
||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Diamond pattern benchmarks (fan-out then fan-in)
|
||||
|
||||
use criterion::Criterion;
|
||||
use dagx::{task_fn, DagRunner};
|
||||
use dagx::{task_fn, DagRunner, TaskHandle};
|
||||
use futures::FutureExt;
|
||||
|
||||
pub fn bench_diamond(c: &mut Criterion) {
|
||||
@@ -13,14 +13,15 @@ pub fn bench_diamond(c: &mut Criterion) {
|
||||
let dag = DagRunner::new();
|
||||
|
||||
for i in 0..50 {
|
||||
let source = dag.add_task(task_fn(move |_: ()| async move { i }));
|
||||
let source: TaskHandle<_> =
|
||||
dag.add_task(task_fn(move |_: ()| async move { i })).into();
|
||||
|
||||
let left = dag
|
||||
.add_task(task_fn(|x: i32| async move { x * 2 }))
|
||||
.depends_on(&source);
|
||||
.depends_on(source);
|
||||
let right = dag
|
||||
.add_task(task_fn(|x: i32| async move { x * 3 }))
|
||||
.depends_on(&source);
|
||||
.depends_on(source);
|
||||
|
||||
dag.add_task(task_fn(|(l, r): (i32, i32)| async move { l + r }))
|
||||
.depends_on((&left, &right));
|
||||
|
||||
+12
-10
@@ -1,7 +1,7 @@
|
||||
//! Fan-out pattern benchmarks (1 → N dependencies)
|
||||
|
||||
use criterion::Criterion;
|
||||
use dagx::{task_fn, DagRunner};
|
||||
use dagx::{task_fn, DagRunner, TaskHandle};
|
||||
use futures::FutureExt;
|
||||
|
||||
pub fn bench_fanout(c: &mut Criterion) {
|
||||
@@ -12,11 +12,11 @@ pub fn bench_fanout(c: &mut Criterion) {
|
||||
b.iter(|| {
|
||||
rt.block_on(async {
|
||||
let dag = DagRunner::new();
|
||||
let source = dag.add_task(task_fn(|_: ()| async { 42 }));
|
||||
let source: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 42 })).into();
|
||||
|
||||
for i in 0..100 {
|
||||
dag.add_task(task_fn(move |x: i32| async move { x + i }))
|
||||
.depends_on(&source);
|
||||
.depends_on(source);
|
||||
}
|
||||
|
||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||
@@ -31,17 +31,19 @@ pub fn bench_fanout(c: &mut Criterion) {
|
||||
b.iter(|| {
|
||||
rt.block_on(async {
|
||||
let dag = DagRunner::new();
|
||||
let source = dag.add_task(task_fn(|_: ()| async {
|
||||
// Create 1000 strings - framework wraps in Arc automatically
|
||||
(0..1000).map(|i| format!("Item {}", i)).collect::<Vec<_>>()
|
||||
}));
|
||||
let source: TaskHandle<_> = dag
|
||||
.add_task(task_fn(|_: ()| async {
|
||||
// Create 1000 strings - framework wraps in Arc automatically
|
||||
(0..1000).map(|i| format!("Item {}", i)).collect::<Vec<_>>()
|
||||
}))
|
||||
.into();
|
||||
|
||||
for i in 0..100 {
|
||||
dag.add_task(task_fn(move |data: Vec<String>| async move {
|
||||
// Each consumer gets data extracted from Arc
|
||||
data.iter().filter(|s| s.contains(&i.to_string())).count()
|
||||
}))
|
||||
.depends_on(&source);
|
||||
.depends_on(source);
|
||||
}
|
||||
|
||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||
@@ -61,12 +63,12 @@ pub fn bench_fanout(c: &mut Criterion) {
|
||||
|
||||
// Source produces a simple Copy type (usize)
|
||||
// Framework wraps in Arc<usize> internally
|
||||
let source = dag.add_task(task_fn(|_: ()| async { 42usize }));
|
||||
let source: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 42usize })).into();
|
||||
|
||||
// 100 dependents - Arc overhead visible on Copy types
|
||||
for i in 0..100 {
|
||||
dag.add_task(task_fn(move |x: usize| async move { x + i }))
|
||||
.depends_on(&source);
|
||||
.depends_on(source);
|
||||
}
|
||||
|
||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Configuration broadcast pattern benchmark - one config shared across many workers
|
||||
|
||||
use criterion::{BenchmarkId, Criterion};
|
||||
use dagx::{task_fn, DagRunner};
|
||||
use dagx::{task_fn, DagRunner, TaskHandle};
|
||||
use futures::FutureExt;
|
||||
use std::collections::HashMap;
|
||||
use std::hint::black_box;
|
||||
@@ -20,18 +20,20 @@ pub fn bench_config_broadcast(c: &mut Criterion) {
|
||||
let dag = DagRunner::new();
|
||||
|
||||
// Configuration source - framework wraps HashMap in Arc automatically
|
||||
let config = dag.add_task(task_fn(|_: ()| async {
|
||||
HashMap::from([
|
||||
("max_connections".to_string(), "100".to_string()),
|
||||
("timeout_ms".to_string(), "5000".to_string()),
|
||||
("batch_size".to_string(), "50".to_string()),
|
||||
("retry_count".to_string(), "3".to_string()),
|
||||
("worker_threads".to_string(), "4".to_string()),
|
||||
("buffer_size".to_string(), "8192".to_string()),
|
||||
("compression".to_string(), "true".to_string()),
|
||||
("log_level".to_string(), "info".to_string()),
|
||||
])
|
||||
}));
|
||||
let config: TaskHandle<_> = dag
|
||||
.add_task(task_fn(|_: ()| async {
|
||||
HashMap::from([
|
||||
("max_connections".to_string(), "100".to_string()),
|
||||
("timeout_ms".to_string(), "5000".to_string()),
|
||||
("batch_size".to_string(), "50".to_string()),
|
||||
("retry_count".to_string(), "3".to_string()),
|
||||
("worker_threads".to_string(), "4".to_string()),
|
||||
("buffer_size".to_string(), "8192".to_string()),
|
||||
("compression".to_string(), "true".to_string()),
|
||||
("log_level".to_string(), "info".to_string()),
|
||||
])
|
||||
}))
|
||||
.into();
|
||||
|
||||
// Workers receive and process based on shared config
|
||||
for worker_id in 0..workers {
|
||||
@@ -49,7 +51,7 @@ pub fn bench_config_broadcast(c: &mut Criterion) {
|
||||
// Simulate some computation
|
||||
black_box(worker_id * batch_size + timeout / 100)
|
||||
}))
|
||||
.depends_on(&config);
|
||||
.depends_on(config);
|
||||
}
|
||||
|
||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! ETL (Extract, Transform, Load) pipeline benchmark
|
||||
|
||||
use criterion::{BenchmarkId, Criterion};
|
||||
use dagx::{task_fn, DagRunner};
|
||||
use dagx::{task_fn, DagRunner, TaskHandle};
|
||||
use futures::FutureExt;
|
||||
|
||||
pub fn bench_etl_pipeline(c: &mut Criterion) {
|
||||
@@ -21,7 +21,7 @@ pub fn bench_etl_pipeline(c: &mut Criterion) {
|
||||
let dag = DagRunner::new();
|
||||
|
||||
// Extract: Load JSON-like data (framework wraps in Arc automatically)
|
||||
let extract = dag.add_task(task_fn(move |_: ()| async move {
|
||||
let extract: TaskHandle<_> = dag.add_task(task_fn(move |_: ()| async move {
|
||||
(0..count)
|
||||
.map(|i| {
|
||||
format!(
|
||||
@@ -32,7 +32,7 @@ pub fn bench_etl_pipeline(c: &mut Criterion) {
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}));
|
||||
})).into();
|
||||
|
||||
// Transform: Three parallel transformations
|
||||
|
||||
@@ -47,7 +47,7 @@ pub fn bench_etl_pipeline(c: &mut Criterion) {
|
||||
})
|
||||
.count()
|
||||
}))
|
||||
.depends_on(&extract);
|
||||
.depends_on(extract);
|
||||
|
||||
// T2: Extract scores
|
||||
let scores = dag
|
||||
@@ -60,7 +60,7 @@ pub fn bench_etl_pipeline(c: &mut Criterion) {
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}))
|
||||
.depends_on(&extract);
|
||||
.depends_on(extract);
|
||||
|
||||
// T3: Extract names
|
||||
let names = dag
|
||||
@@ -74,7 +74,7 @@ pub fn bench_etl_pipeline(c: &mut Criterion) {
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}))
|
||||
.depends_on(&extract);
|
||||
.depends_on(extract);
|
||||
|
||||
// Load: Aggregate all transformations
|
||||
dag.add_task(task_fn(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Memory efficiency benchmark with large data structures
|
||||
|
||||
use criterion::{BenchmarkId, Criterion};
|
||||
use dagx::{task_fn, DagRunner};
|
||||
use dagx::{task_fn, DagRunner, TaskHandle};
|
||||
use futures::FutureExt;
|
||||
use std::hint::black_box;
|
||||
|
||||
@@ -19,10 +19,12 @@ pub fn bench_memory_efficiency(c: &mut Criterion) {
|
||||
rt.block_on(async {
|
||||
let dag = DagRunner::new();
|
||||
|
||||
let source = dag.add_task(task_fn(move |_: ()| async move {
|
||||
// Create MB of data - framework wraps in Arc automatically
|
||||
vec![0u8; mb * 1024 * 1024]
|
||||
}));
|
||||
let source: TaskHandle<_> = dag
|
||||
.add_task(task_fn(move |_: ()| async move {
|
||||
// Create MB of data - framework wraps in Arc automatically
|
||||
vec![0u8; mb * 1024 * 1024]
|
||||
}))
|
||||
.into();
|
||||
|
||||
// 20 consumers share the data via automatic Arc wrapping
|
||||
for i in 0..20 {
|
||||
@@ -36,7 +38,7 @@ pub fn bench_memory_efficiency(c: &mut Criterion) {
|
||||
.sum();
|
||||
black_box(sample_sum)
|
||||
}))
|
||||
.depends_on(&source);
|
||||
.depends_on(source);
|
||||
}
|
||||
|
||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Mixed realistic patterns - fan-out, fan-in, and processing
|
||||
|
||||
use criterion::Criterion;
|
||||
use dagx::{task_fn, DagRunner};
|
||||
use dagx::{task_fn, DagRunner, TaskHandle};
|
||||
use futures::FutureExt;
|
||||
|
||||
pub fn bench_mixed_patterns(c: &mut Criterion) {
|
||||
@@ -13,28 +13,30 @@ pub fn bench_mixed_patterns(c: &mut Criterion) {
|
||||
let dag = DagRunner::new();
|
||||
|
||||
// Stage 1: Load data (framework wraps in Arc automatically)
|
||||
let data = dag.add_task(task_fn(|_: ()| async {
|
||||
(0..1000)
|
||||
.map(|i| format!("Record-{:04}", i))
|
||||
.collect::<Vec<_>>()
|
||||
}));
|
||||
let data: TaskHandle<_> = dag
|
||||
.add_task(task_fn(|_: ()| async {
|
||||
(0..1000)
|
||||
.map(|i| format!("Record-{:04}", i))
|
||||
.collect::<Vec<_>>()
|
||||
}))
|
||||
.into();
|
||||
|
||||
// Stage 2: Fan-out for parallel analysis
|
||||
let analysis1 = dag
|
||||
.add_task(task_fn(|d: Vec<String>| async move {
|
||||
d.iter().filter(|s| s.contains("00")).count()
|
||||
}))
|
||||
.depends_on(&data);
|
||||
.depends_on(data);
|
||||
|
||||
let analysis2 = dag
|
||||
.add_task(task_fn(|d: Vec<String>| async move {
|
||||
d.iter().map(|s| s.len()).sum::<usize>()
|
||||
}))
|
||||
.depends_on(&data);
|
||||
.depends_on(data);
|
||||
|
||||
let analysis3 = dag
|
||||
.add_task(task_fn(|d: Vec<String>| async move { d.len() }))
|
||||
.depends_on(&data);
|
||||
.depends_on(data);
|
||||
|
||||
// Stage 3: Fan-in to aggregate
|
||||
let summary = dag
|
||||
|
||||
Reference in new issue
Block a user