chore: add logging under tracing feature
This commit is contained in:
11 files changed
+719
-8
No files matched your search
Generated
+120
@@ -448,6 +448,8 @@ dependencies = [
|
|||||||
"pollster",
|
"pollster",
|
||||||
"smol",
|
"smol",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tracing",
|
||||||
|
"tracing-subscriber",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -690,6 +692,12 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy_static"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.176"
|
version = "0.2.176"
|
||||||
@@ -721,6 +729,15 @@ dependencies = [
|
|||||||
"value-bag",
|
"value-bag",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "matchers"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
|
||||||
|
dependencies = [
|
||||||
|
"regex-automata",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.7.6"
|
version = "2.7.6"
|
||||||
@@ -747,6 +764,15 @@ dependencies = [
|
|||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nu-ansi-term"
|
||||||
|
version = "0.50.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.19"
|
version = "0.2.19"
|
||||||
@@ -1052,6 +1078,15 @@ dependencies = [
|
|||||||
"serde_core",
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sharded-slab"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signal-hook-registry"
|
name = "signal-hook-registry"
|
||||||
version = "1.4.6"
|
version = "1.4.6"
|
||||||
@@ -1112,6 +1147,15 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thread_local"
|
||||||
|
version = "1.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinytemplate"
|
name = "tinytemplate"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
@@ -1148,12 +1192,79 @@ dependencies = [
|
|||||||
"syn 2.0.106",
|
"syn 2.0.106",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing"
|
||||||
|
version = "0.1.41"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
|
||||||
|
dependencies = [
|
||||||
|
"pin-project-lite",
|
||||||
|
"tracing-attributes",
|
||||||
|
"tracing-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-attributes"
|
||||||
|
version = "0.1.30"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.106",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-core"
|
||||||
|
version = "0.1.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
"valuable",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-log"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"tracing-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-subscriber"
|
||||||
|
version = "0.3.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5"
|
||||||
|
dependencies = [
|
||||||
|
"matchers",
|
||||||
|
"nu-ansi-term",
|
||||||
|
"once_cell",
|
||||||
|
"regex-automata",
|
||||||
|
"sharded-slab",
|
||||||
|
"smallvec",
|
||||||
|
"thread_local",
|
||||||
|
"tracing",
|
||||||
|
"tracing-core",
|
||||||
|
"tracing-log",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.19"
|
version = "1.0.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
|
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "valuable"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "value-bag"
|
name = "value-bag"
|
||||||
version = "1.11.1"
|
version = "1.11.1"
|
||||||
@@ -1273,6 +1384,15 @@ version = "0.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
|
checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.59.0"
|
version = "0.59.0"
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ keywords = ["dag", "async", "task", "parallel", "executor"]
|
|||||||
categories = ["asynchronous", "concurrency", "data-structures"]
|
categories = ["asynchronous", "concurrency", "data-structures"]
|
||||||
exclude = [".claude/", ".jj/", "dagx-spec.md"]
|
exclude = [".claude/", ".jj/", "dagx-spec.md"]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
tracing = ["dep:tracing"]
|
||||||
|
|
||||||
[lints.rust]
|
[lints.rust]
|
||||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tarpaulin)', 'cfg(tarpaulin_include)'] }
|
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tarpaulin)', 'cfg(tarpaulin_include)'] }
|
||||||
|
|
||||||
@@ -24,6 +28,7 @@ unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tarpaulin)', 'cfg(tarpauli
|
|||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
parking_lot = "0.12"
|
parking_lot = "0.12"
|
||||||
dagx-macros = { version = "0.2.2", path = "./dagx-macros" }
|
dagx-macros = { version = "0.2.2", path = "./dagx-macros" }
|
||||||
|
tracing = { version = "0.1", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { version = "1", features = ["macros", "rt-multi-thread", "time", "sync"] }
|
tokio = { version = "1", features = ["macros", "rt-multi-thread", "time", "sync"] }
|
||||||
@@ -37,6 +42,8 @@ futures-executor = { version = "0.3", features = ["thread-pool"] }
|
|||||||
# Comparison benchmarks
|
# Comparison benchmarks
|
||||||
dagrs = { version = "0.5", default-features = false }
|
dagrs = { version = "0.5", default-features = false }
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
|
# Tracing support for tests and examples
|
||||||
|
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "dagx_bench"
|
name = "dagx_bench"
|
||||||
|
|||||||
@@ -20,6 +20,61 @@ A minimal, type-safe, runtime-agnostic async DAG (Directed Acyclic Graph) execut
|
|||||||
- **Flexible task patterns**: Supports stateless, read-only, and mutable state tasks
|
- **Flexible task patterns**: Supports stateless, read-only, and mutable state tasks
|
||||||
- **Simple API**: Just `#[task]`, `DagRunner`, `TaskHandle`, and `TaskBuilder`
|
- **Simple API**: Just `#[task]`, `DagRunner`, `TaskHandle`, and `TaskBuilder`
|
||||||
- **Comprehensive error handling**: Result-based errors with actionable messages
|
- **Comprehensive error handling**: Result-based errors with actionable messages
|
||||||
|
- **Optional tracing**: Zero-cost observability via optional `tracing` feature flag
|
||||||
|
|
||||||
|
## Optional Tracing Support
|
||||||
|
|
||||||
|
dagx provides optional observability through the `tracing` crate with **zero runtime overhead when disabled**. The tracing instrumentation is conditionally compiled using feature flags, meaning when the feature is off, the logging code doesn't exist in the compiled binary at all.
|
||||||
|
|
||||||
|
### Enabling Tracing
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
dagx = { version = "0.2", features = ["tracing"] }
|
||||||
|
tracing-subscriber = "0.3"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Usage Example
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use tracing_subscriber::{fmt, EnvFilter};
|
||||||
|
|
||||||
|
// Initialize tracing subscriber
|
||||||
|
fmt()
|
||||||
|
.with_env_filter(
|
||||||
|
EnvFilter::try_from_default_env()
|
||||||
|
.unwrap_or_else(|_| EnvFilter::new("dagx=info"))
|
||||||
|
)
|
||||||
|
.init();
|
||||||
|
|
||||||
|
// Your DAG code here - tracing will log execution details
|
||||||
|
```
|
||||||
|
|
||||||
|
### Log Levels
|
||||||
|
|
||||||
|
- **INFO**: DAG execution start/completion
|
||||||
|
- **DEBUG**: Task additions, dependency wiring, layer computation
|
||||||
|
- **TRACE**: Individual task execution (inline vs spawned), detailed execution flow
|
||||||
|
- **ERROR**: Panics, cycles, type errors
|
||||||
|
|
||||||
|
Run with different levels:
|
||||||
|
```bash
|
||||||
|
RUST_LOG=dagx=info cargo run # High-level execution info
|
||||||
|
RUST_LOG=dagx=debug cargo run # Task and layer details
|
||||||
|
RUST_LOG=dagx=trace cargo run # All execution details
|
||||||
|
```
|
||||||
|
|
||||||
|
See [`examples/tracing_example.rs`](examples/tracing_example.rs) for a complete working example.
|
||||||
|
|
||||||
|
### Zero-Cost Guarantee
|
||||||
|
|
||||||
|
When the `tracing` feature is disabled (the default), there is **literally 0ns overhead**:
|
||||||
|
- Logging code is removed at compile time via `#[cfg(feature = "tracing")]`
|
||||||
|
- No branches, no function calls, nothing
|
||||||
|
- The `tracing` crate isn't even linked
|
||||||
|
- Benchmarks verify identical performance with/without the feature
|
||||||
|
|
||||||
|
This follows the same pattern used by Tokio, Hyper, and other performance-critical Rust async libraries.
|
||||||
|
|
||||||
## Design Philosophy: Primitives as Scheduler
|
## Design Philosophy: Primitives as Scheduler
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,168 @@
|
|||||||
|
//! # Tracing Example
|
||||||
|
//!
|
||||||
|
//! This example demonstrates how to use the optional tracing feature to gain
|
||||||
|
//! visibility into DAG execution for debugging and performance analysis.
|
||||||
|
//!
|
||||||
|
//! ## Running This Example
|
||||||
|
//!
|
||||||
|
//! ```bash
|
||||||
|
//! # With default log level (info)
|
||||||
|
//! cargo run --example tracing_example --features tracing
|
||||||
|
//!
|
||||||
|
//! # With debug level (shows more details)
|
||||||
|
//! RUST_LOG=dagx=debug cargo run --example tracing_example --features tracing
|
||||||
|
//!
|
||||||
|
//! # With trace level (shows all execution details)
|
||||||
|
//! RUST_LOG=dagx=trace cargo run --example tracing_example --features tracing
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## What You'll See
|
||||||
|
//!
|
||||||
|
//! - Task additions to the DAG
|
||||||
|
//! - Dependency wiring between tasks
|
||||||
|
//! - Topological layer computation
|
||||||
|
//! - Layer-by-layer execution
|
||||||
|
//! - Inline vs spawned task execution
|
||||||
|
//! - Completion status
|
||||||
|
//!
|
||||||
|
//! ## Log Levels
|
||||||
|
//!
|
||||||
|
//! - **INFO**: High-level DAG execution start/completion
|
||||||
|
//! - **DEBUG**: Task additions, dependency wiring, layer computation
|
||||||
|
//! - **TRACE**: Individual task execution details (inline vs spawned)
|
||||||
|
//! - **ERROR**: Errors, panics, cycle detection
|
||||||
|
//!
|
||||||
|
|
||||||
|
use dagx::{task, DagRunner, Task};
|
||||||
|
use tracing_subscriber::{fmt, EnvFilter};
|
||||||
|
|
||||||
|
// Simple source task
|
||||||
|
struct Value(i32);
|
||||||
|
|
||||||
|
#[task]
|
||||||
|
impl Value {
|
||||||
|
async fn run(&self) -> i32 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Computation task
|
||||||
|
struct Add;
|
||||||
|
|
||||||
|
#[task]
|
||||||
|
impl Add {
|
||||||
|
async fn run(a: &i32, b: &i32) -> i32 {
|
||||||
|
a + b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Another computation task
|
||||||
|
struct Multiply;
|
||||||
|
|
||||||
|
#[task]
|
||||||
|
impl Multiply {
|
||||||
|
async fn run(a: &i32, b: &i32) -> i32 {
|
||||||
|
a * b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
// Initialize tracing subscriber with environment filter
|
||||||
|
// Use RUST_LOG environment variable to control log level
|
||||||
|
fmt()
|
||||||
|
.with_env_filter(
|
||||||
|
EnvFilter::try_from_default_env()
|
||||||
|
.unwrap_or_else(|_| EnvFilter::new("dagx=info")),
|
||||||
|
)
|
||||||
|
.init();
|
||||||
|
|
||||||
|
println!("=== DAG Execution with Tracing ===\n");
|
||||||
|
println!("Run with RUST_LOG=dagx=debug or RUST_LOG=dagx=trace for more details\n");
|
||||||
|
|
||||||
|
// Example 1: Simple diamond pattern
|
||||||
|
println!("Example 1: Diamond Pattern");
|
||||||
|
println!("---------------------------");
|
||||||
|
{
|
||||||
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
|
// Layer 0: Source
|
||||||
|
let source = dag.add_task(Value(10));
|
||||||
|
|
||||||
|
// Layer 1: Two parallel computations
|
||||||
|
let left = dag.add_task(Add).depends_on((&source, &source));
|
||||||
|
let right = dag.add_task(Multiply).depends_on((&source, &source));
|
||||||
|
|
||||||
|
// Layer 2: Combine results
|
||||||
|
let result = dag.add_task(Add).depends_on((&left, &right));
|
||||||
|
|
||||||
|
dag.run(|fut| {
|
||||||
|
tokio::spawn(fut);
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
println!("\nResult: {}", dag.get(result).unwrap());
|
||||||
|
println!("(Expected: 10+10 = 20, 10*10 = 100, 20+100 = 120)\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example 2: Linear chain (demonstrates inline execution)
|
||||||
|
println!("\nExample 2: Linear Chain (Inline Execution)");
|
||||||
|
println!("-------------------------------------------");
|
||||||
|
{
|
||||||
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
|
// Create a linear chain - each layer has only one task
|
||||||
|
// This should trigger inline execution optimization
|
||||||
|
let a = dag.add_task(Value(1));
|
||||||
|
let b = dag.add_task(Add).depends_on((&a, &a));
|
||||||
|
let c = dag.add_task(Multiply).depends_on((&b, &b));
|
||||||
|
let d = dag.add_task(Add).depends_on((&c, &c));
|
||||||
|
|
||||||
|
dag.run(|fut| {
|
||||||
|
tokio::spawn(fut);
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
println!("\nResult: {}", dag.get(d).unwrap());
|
||||||
|
println!("(Expected: 1+1 = 2, 2*2 = 4, 4+4 = 8)\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example 3: Wide pattern (demonstrates spawned execution)
|
||||||
|
println!("\nExample 3: Wide Pattern (Spawned Execution)");
|
||||||
|
println!("--------------------------------------------");
|
||||||
|
{
|
||||||
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
|
// Layer 0: Multiple sources
|
||||||
|
let v1 = dag.add_task(Value(1));
|
||||||
|
let v2 = dag.add_task(Value(2));
|
||||||
|
let v3 = dag.add_task(Value(3));
|
||||||
|
let v4 = dag.add_task(Value(4));
|
||||||
|
|
||||||
|
// Layer 1: Multiple parallel computations
|
||||||
|
let sum12 = dag.add_task(Add).depends_on((&v1, &v2));
|
||||||
|
let sum23 = dag.add_task(Add).depends_on((&v2, &v3));
|
||||||
|
let sum34 = dag.add_task(Add).depends_on((&v3, &v4));
|
||||||
|
|
||||||
|
// Layer 2: Final combination
|
||||||
|
let sum_all = dag.add_task(Add).depends_on((&sum12, &sum23));
|
||||||
|
let final_result = dag.add_task(Add).depends_on((&sum_all, &sum34));
|
||||||
|
|
||||||
|
dag.run(|fut| {
|
||||||
|
tokio::spawn(fut);
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
println!("\nResult: {}", dag.get(final_result).unwrap());
|
||||||
|
println!("(Expected: (1+2)+(2+3) = 8, 8+(3+4) = 15)\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("\n=== Tracing Example Complete ===");
|
||||||
|
println!("\nTry running with different log levels:");
|
||||||
|
println!(" RUST_LOG=dagx=info - High-level execution info");
|
||||||
|
println!(" RUST_LOG=dagx=debug - Task additions and layer details");
|
||||||
|
println!(" RUST_LOG=dagx=trace - All execution details including inline/spawned");
|
||||||
|
}
|
||||||
@@ -8,6 +8,9 @@ use crate::runner::DagRunner;
|
|||||||
use crate::task::Task;
|
use crate::task::Task;
|
||||||
use crate::types::{NodeId, TaskHandle};
|
use crate::types::{NodeId, TaskHandle};
|
||||||
|
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
use tracing::debug;
|
||||||
|
|
||||||
/// Marker trait to identify the unit type at compile time.
|
/// Marker trait to identify the unit type at compile time.
|
||||||
///
|
///
|
||||||
/// This is used to enforce that tasks with non-unit inputs MUST call `.depends_on()`,
|
/// This is used to enforce that tasks with non-unit inputs MUST call `.depends_on()`,
|
||||||
@@ -132,6 +135,14 @@ impl<'a, Tk: Task, Deps> TaskBuilder<'a, Tk, Deps> {
|
|||||||
// Register dependencies in the DAG
|
// Register dependencies in the DAG
|
||||||
let dep_ids = deps.to_node_ids();
|
let dep_ids = deps.to_node_ids();
|
||||||
|
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
debug!(
|
||||||
|
task_id = self.id.0,
|
||||||
|
dependency_ids = ?dep_ids.iter().map(|id| id.0).collect::<Vec<_>>(),
|
||||||
|
dependency_count = dep_ids.len(),
|
||||||
|
"wiring task dependencies"
|
||||||
|
);
|
||||||
|
|
||||||
let mut edges = self.dag.edges.lock();
|
let mut edges = self.dag.edges.lock();
|
||||||
let mut dependents = self.dag.dependents.lock();
|
let mut dependents = self.dag.dependents.lock();
|
||||||
|
|
||||||
|
|||||||
+57
@@ -655,6 +655,63 @@
|
|||||||
//! Run `cargo bench` to see comprehensive benchmarks including basic operations, scaling
|
//! Run `cargo bench` to see comprehensive benchmarks including basic operations, scaling
|
||||||
//! characteristics, common patterns (fan-out, diamond), and realistic workloads.
|
//! characteristics, common patterns (fan-out, diamond), and realistic workloads.
|
||||||
//!
|
//!
|
||||||
|
//! # Optional Tracing Support
|
||||||
|
//!
|
||||||
|
//! dagx provides optional observability through the `tracing` crate with **zero runtime overhead
|
||||||
|
//! when disabled**. The tracing instrumentation is conditionally compiled using feature flags.
|
||||||
|
//!
|
||||||
|
//! ## Enabling Tracing
|
||||||
|
//!
|
||||||
|
//! Add the `tracing` feature to your `Cargo.toml`:
|
||||||
|
//!
|
||||||
|
//! ```toml
|
||||||
|
//! [dependencies]
|
||||||
|
//! dagx = { version = "0.2", features = ["tracing"] }
|
||||||
|
//! tracing-subscriber = "0.3"
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Then initialize a tracing subscriber in your application:
|
||||||
|
//!
|
||||||
|
//! ```no_run
|
||||||
|
//! use tracing_subscriber::{fmt, EnvFilter};
|
||||||
|
//!
|
||||||
|
//! fmt()
|
||||||
|
//! .with_env_filter(
|
||||||
|
//! EnvFilter::try_from_default_env()
|
||||||
|
//! .unwrap_or_else(|_| EnvFilter::new("dagx=info"))
|
||||||
|
//! )
|
||||||
|
//! .init();
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Log Levels
|
||||||
|
//!
|
||||||
|
//! - **INFO**: DAG execution start/completion
|
||||||
|
//! - **DEBUG**: Task additions, dependency wiring, layer computation
|
||||||
|
//! - **TRACE**: Individual task execution (inline vs spawned), detailed execution flow
|
||||||
|
//! - **ERROR**: Panics, cycles, type errors
|
||||||
|
//!
|
||||||
|
//! Control log level with the `RUST_LOG` environment variable:
|
||||||
|
//!
|
||||||
|
//! ```bash
|
||||||
|
//! RUST_LOG=dagx=info cargo run # High-level execution info
|
||||||
|
//! RUST_LOG=dagx=debug cargo run # Task and layer details
|
||||||
|
//! RUST_LOG=dagx=trace cargo run # All execution details
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Zero-Cost Guarantee
|
||||||
|
//!
|
||||||
|
//! When the `tracing` feature is disabled (the default), there is **literally 0ns overhead**:
|
||||||
|
//! - Logging code is removed at compile time via `#[cfg(feature = "tracing")]`
|
||||||
|
//! - No branches, no function calls, nothing in the compiled binary
|
||||||
|
//! - The `tracing` crate isn't even linked
|
||||||
|
//! - Benchmarks verify identical performance with/without the feature
|
||||||
|
//!
|
||||||
|
//! This follows the same zero-cost pattern used by Tokio, Hyper, and other performance-critical
|
||||||
|
//! Rust async libraries.
|
||||||
|
//!
|
||||||
|
//! See [`examples/tracing_example.rs`](https://github.com/swaits/dagx/blob/main/examples/tracing_example.rs)
|
||||||
|
//! for a complete working example.
|
||||||
|
//!
|
||||||
|
|
||||||
#![allow(private_bounds, private_interfaces)]
|
#![allow(private_bounds, private_interfaces)]
|
||||||
|
|
||||||
|
|||||||
+81
-8
@@ -17,6 +17,9 @@ use futures::future::BoxFuture;
|
|||||||
use futures::{FutureExt, StreamExt};
|
use futures::{FutureExt, StreamExt};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
use tracing::{debug, error, info, trace};
|
||||||
|
|
||||||
use crate::builder::TaskBuilder;
|
use crate::builder::TaskBuilder;
|
||||||
use crate::error::{DagError, DagResult};
|
use crate::error::{DagError, DagResult};
|
||||||
use crate::extract::ExtractInput;
|
use crate::extract::ExtractInput;
|
||||||
@@ -197,6 +200,14 @@ impl DagRunner {
|
|||||||
Tk::Output: 'static + Clone,
|
Tk::Output: 'static + Clone,
|
||||||
{
|
{
|
||||||
let id = self.alloc_id();
|
let id = self.alloc_id();
|
||||||
|
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
debug!(
|
||||||
|
task_id = id.0,
|
||||||
|
task_type = std::any::type_name::<Tk>(),
|
||||||
|
"adding task to DAG"
|
||||||
|
);
|
||||||
|
|
||||||
let node = TypedNode::new(id, task);
|
let node = TypedNode::new(id, task);
|
||||||
self.nodes.lock().push(Some(Box::new(node)));
|
self.nodes.lock().push(Some(Box::new(node)));
|
||||||
self.edges.lock().insert(id, Vec::new());
|
self.edges.lock().insert(id, Vec::new());
|
||||||
@@ -266,10 +277,13 @@ impl DagRunner {
|
|||||||
/// This eliminates Mutex contention on outputs and enables true streaming execution.
|
/// This eliminates Mutex contention on outputs and enables true streaming execution.
|
||||||
/// Type erasure occurs only at the ExecutableNode trait boundary - channels are created
|
/// Type erasure occurs only at the ExecutableNode trait boundary - channels are created
|
||||||
/// with full type information and type-erased just before passing to execute_with_channels.
|
/// with full type information and type-erased just before passing to execute_with_channels.
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(skip(self, spawner)))]
|
||||||
pub async fn run<S>(&self, spawner: S) -> DagResult<()>
|
pub async fn run<S>(&self, spawner: S) -> DagResult<()>
|
||||||
where
|
where
|
||||||
S: Fn(BoxFuture<'static, ()>),
|
S: Fn(BoxFuture<'static, ()>),
|
||||||
{
|
{
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
info!("starting DAG execution");
|
||||||
// Acquire run lock to prevent concurrent executions
|
// Acquire run lock to prevent concurrent executions
|
||||||
// Use atomic compare_exchange to check if already running
|
// Use atomic compare_exchange to check if already running
|
||||||
if self
|
if self
|
||||||
@@ -277,6 +291,9 @@ impl DagRunner {
|
|||||||
.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
|
.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
|
||||||
.is_err()
|
.is_err()
|
||||||
{
|
{
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
error!("DAG is already running - concurrent execution not supported");
|
||||||
|
|
||||||
return Err(DagError::CycleDetected {
|
return Err(DagError::CycleDetected {
|
||||||
nodes: vec![],
|
nodes: vec![],
|
||||||
description: "DAG is already running - concurrent execution not supported"
|
description: "DAG is already running - concurrent execution not supported"
|
||||||
@@ -292,6 +309,13 @@ impl DagRunner {
|
|||||||
// Build topological layers
|
// Build topological layers
|
||||||
let layers = self.compute_layers()?;
|
let layers = self.compute_layers()?;
|
||||||
|
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
debug!(
|
||||||
|
layer_count = layers.len(),
|
||||||
|
total_tasks = layers.iter().map(|l| l.len()).sum::<usize>(),
|
||||||
|
"computed topological layers"
|
||||||
|
);
|
||||||
|
|
||||||
let edges = self.edges.lock().clone();
|
let edges = self.edges.lock().clone();
|
||||||
|
|
||||||
// Create all channels upfront
|
// Create all channels upfront
|
||||||
@@ -337,7 +361,19 @@ impl DagRunner {
|
|||||||
)>();
|
)>();
|
||||||
|
|
||||||
// Execute layer by layer
|
// Execute layer by layer
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
let mut layer_idx = 0;
|
||||||
|
|
||||||
for layer in layers {
|
for layer in layers {
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
{
|
||||||
|
debug!(
|
||||||
|
layer = layer_idx,
|
||||||
|
task_count = layer.len(),
|
||||||
|
"executing layer"
|
||||||
|
);
|
||||||
|
layer_idx += 1;
|
||||||
|
}
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// PERFORMANCE OPTIMIZATION: Inline execution for single-task layers
|
// PERFORMANCE OPTIMIZATION: Inline execution for single-task layers
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -366,6 +402,13 @@ impl DagRunner {
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
if layer.len() == 1 {
|
if layer.len() == 1 {
|
||||||
let node_id = layer[0];
|
let node_id = layer[0];
|
||||||
|
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
trace!(
|
||||||
|
task_id = node_id.0,
|
||||||
|
"executing task inline (single-task layer optimization)"
|
||||||
|
);
|
||||||
|
|
||||||
let out_tx = output_tx.clone();
|
let out_tx = output_tx.clone();
|
||||||
|
|
||||||
// Take ownership of the node
|
// Take ownership of the node
|
||||||
@@ -392,16 +435,25 @@ impl DagRunner {
|
|||||||
.await
|
.await
|
||||||
.unwrap_or_else(|panic_payload| {
|
.unwrap_or_else(|panic_payload| {
|
||||||
// Convert panic to error
|
// Convert panic to error
|
||||||
|
let panic_message = if let Some(s) = panic_payload.downcast_ref::<&str>()
|
||||||
|
{
|
||||||
|
s.to_string()
|
||||||
|
} else if let Some(s) = panic_payload.downcast_ref::<String>() {
|
||||||
|
s.clone()
|
||||||
|
} else {
|
||||||
|
"unknown panic".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
error!(
|
||||||
|
task_id = node_id.0,
|
||||||
|
panic_message = %panic_message,
|
||||||
|
"task panicked during inline execution"
|
||||||
|
);
|
||||||
|
|
||||||
Err(DagError::TaskPanicked {
|
Err(DagError::TaskPanicked {
|
||||||
task_id: node_id.0,
|
task_id: node_id.0,
|
||||||
panic_message: if let Some(s) = panic_payload.downcast_ref::<&str>()
|
panic_message,
|
||||||
{
|
|
||||||
s.to_string()
|
|
||||||
} else if let Some(s) = panic_payload.downcast_ref::<String>() {
|
|
||||||
s.clone()
|
|
||||||
} else {
|
|
||||||
"unknown panic".to_string()
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -415,6 +467,8 @@ impl DagRunner {
|
|||||||
|
|
||||||
// Spawn each task in this layer
|
// Spawn each task in this layer
|
||||||
for &node_id in &layer {
|
for &node_id in &layer {
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
trace!(task_id = node_id.0, "spawning task");
|
||||||
let mut task_tx = tx.clone();
|
let mut task_tx = tx.clone();
|
||||||
let out_tx = output_tx.clone();
|
let out_tx = output_tx.clone();
|
||||||
|
|
||||||
@@ -480,9 +534,14 @@ impl DagRunner {
|
|||||||
|
|
||||||
// Return first error if any
|
// Return first error if any
|
||||||
if let Some(err) = first_error {
|
if let Some(err) = first_error {
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
error!(?err, "DAG execution failed");
|
||||||
return Err(err);
|
return Err(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
info!("DAG execution completed successfully");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -551,6 +610,9 @@ impl DagRunner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn compute_layers(&self) -> DagResult<Vec<Vec<NodeId>>> {
|
fn compute_layers(&self) -> DagResult<Vec<Vec<NodeId>>> {
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
debug!("computing topological layers");
|
||||||
|
|
||||||
let mut in_degree: HashMap<NodeId, usize> = HashMap::new();
|
let mut in_degree: HashMap<NodeId, usize> = HashMap::new();
|
||||||
let mut layers = Vec::new();
|
let mut layers = Vec::new();
|
||||||
|
|
||||||
@@ -620,12 +682,23 @@ impl DagRunner {
|
|||||||
unvisited.len(),
|
unvisited.len(),
|
||||||
unvisited
|
unvisited
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
error!(
|
||||||
|
unvisited_count = unvisited.len(),
|
||||||
|
unvisited_nodes = ?unvisited,
|
||||||
|
"cycle detected in DAG"
|
||||||
|
);
|
||||||
|
|
||||||
return Err(DagError::CycleDetected {
|
return Err(DagError::CycleDetected {
|
||||||
nodes: unvisited,
|
nodes: unvisited,
|
||||||
description,
|
description,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
debug!(layer_count = layers.len(), "topological layers computed");
|
||||||
|
|
||||||
Ok(layers)
|
Ok(layers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,4 +17,5 @@ mod performance;
|
|||||||
mod runtimes;
|
mod runtimes;
|
||||||
mod task_patterns;
|
mod task_patterns;
|
||||||
mod timing;
|
mod timing;
|
||||||
|
mod tracing;
|
||||||
mod type_safety;
|
mod type_safety;
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
//! Tests for tracing functionality
|
||||||
|
//!
|
||||||
|
//! These tests verify that the tracing feature works correctly when enabled
|
||||||
|
//! and that the library compiles and works without it when disabled.
|
||||||
|
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
mod with_tracing;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "tracing"))]
|
||||||
|
mod without_tracing;
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
//! Tests with tracing feature enabled
|
||||||
|
|
||||||
|
use dagx::{task, DagRunner, Task};
|
||||||
|
use tracing_subscriber::{fmt, EnvFilter};
|
||||||
|
|
||||||
|
struct Value(i32);
|
||||||
|
|
||||||
|
#[task]
|
||||||
|
impl Value {
|
||||||
|
async fn run(&self) -> i32 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Add;
|
||||||
|
|
||||||
|
#[task]
|
||||||
|
impl Add {
|
||||||
|
async fn run(a: &i32, b: &i32) -> i32 {
|
||||||
|
a + b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Multiply;
|
||||||
|
|
||||||
|
#[task]
|
||||||
|
impl Multiply {
|
||||||
|
async fn run(a: &i32, b: &i32) -> i32 {
|
||||||
|
a * b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_tracing_with_subscriber() {
|
||||||
|
// Initialize tracing subscriber for this test
|
||||||
|
let _ = fmt()
|
||||||
|
.with_env_filter(EnvFilter::from_default_env())
|
||||||
|
.with_test_writer()
|
||||||
|
.try_init();
|
||||||
|
|
||||||
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
|
let a = dag.add_task(Value(2));
|
||||||
|
let b = dag.add_task(Value(3));
|
||||||
|
let sum = dag.add_task(Add).depends_on((&a, &b));
|
||||||
|
|
||||||
|
dag.run(|fut| {
|
||||||
|
tokio::spawn(fut);
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(dag.get(sum).unwrap(), 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_tracing_with_complex_dag() {
|
||||||
|
// Initialize tracing subscriber
|
||||||
|
let _ = fmt()
|
||||||
|
.with_env_filter(EnvFilter::from_default_env())
|
||||||
|
.with_test_writer()
|
||||||
|
.try_init();
|
||||||
|
|
||||||
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
|
// Create a diamond pattern
|
||||||
|
let source = dag.add_task(Value(10));
|
||||||
|
let left = dag.add_task(Add).depends_on((&source, &source));
|
||||||
|
let right = dag.add_task(Multiply).depends_on((&source, &source));
|
||||||
|
let sink = dag.add_task(Add).depends_on((&left, &right));
|
||||||
|
|
||||||
|
dag.run(|fut| {
|
||||||
|
tokio::spawn(fut);
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// source = 10
|
||||||
|
// left = 10 + 10 = 20
|
||||||
|
// right = 10 * 10 = 100
|
||||||
|
// sink = 20 + 100 = 120
|
||||||
|
assert_eq!(dag.get(sink).unwrap(), 120);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_tracing_inline_execution() {
|
||||||
|
// Initialize tracing subscriber
|
||||||
|
let _ = fmt()
|
||||||
|
.with_env_filter(EnvFilter::from_default_env())
|
||||||
|
.with_test_writer()
|
||||||
|
.try_init();
|
||||||
|
|
||||||
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
|
// Create a linear chain (should trigger inline execution)
|
||||||
|
let a = dag.add_task(Value(1));
|
||||||
|
let b = dag.add_task(Add).depends_on((&a, &a));
|
||||||
|
let c = dag.add_task(Multiply).depends_on((&b, &b));
|
||||||
|
|
||||||
|
dag.run(|fut| {
|
||||||
|
tokio::spawn(fut);
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// a = 1
|
||||||
|
// b = 1 + 1 = 2
|
||||||
|
// c = 2 * 2 = 4
|
||||||
|
assert_eq!(dag.get(c).unwrap(), 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_tracing_multiple_layers() {
|
||||||
|
// Initialize tracing subscriber
|
||||||
|
let _ = fmt()
|
||||||
|
.with_env_filter(
|
||||||
|
EnvFilter::try_from_default_env()
|
||||||
|
.unwrap_or_else(|_| EnvFilter::new("debug")),
|
||||||
|
)
|
||||||
|
.with_test_writer()
|
||||||
|
.try_init();
|
||||||
|
|
||||||
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
|
// Layer 0
|
||||||
|
let v1 = dag.add_task(Value(1));
|
||||||
|
let v2 = dag.add_task(Value(2));
|
||||||
|
let v3 = dag.add_task(Value(3));
|
||||||
|
|
||||||
|
// Layer 1
|
||||||
|
let sum12 = dag.add_task(Add).depends_on((&v1, &v2));
|
||||||
|
let mul23 = dag.add_task(Multiply).depends_on((&v2, &v3));
|
||||||
|
|
||||||
|
// Layer 2
|
||||||
|
let final_sum = dag.add_task(Add).depends_on((&sum12, &mul23));
|
||||||
|
|
||||||
|
dag.run(|fut| {
|
||||||
|
tokio::spawn(fut);
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// v1 = 1, v2 = 2, v3 = 3
|
||||||
|
// sum12 = 1 + 2 = 3
|
||||||
|
// mul23 = 2 * 3 = 6
|
||||||
|
// final_sum = 3 + 6 = 9
|
||||||
|
assert_eq!(dag.get(final_sum).unwrap(), 9);
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
//! Tests that the library works correctly without tracing feature
|
||||||
|
|
||||||
|
use dagx::{task, DagRunner, Task};
|
||||||
|
|
||||||
|
struct Value(i32);
|
||||||
|
|
||||||
|
#[task]
|
||||||
|
impl Value {
|
||||||
|
async fn run(&self) -> i32 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Add;
|
||||||
|
|
||||||
|
#[task]
|
||||||
|
impl Add {
|
||||||
|
async fn run(a: &i32, b: &i32) -> i32 {
|
||||||
|
a + b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_basic_dag_without_tracing() {
|
||||||
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
|
let a = dag.add_task(Value(2));
|
||||||
|
let b = dag.add_task(Value(3));
|
||||||
|
let sum = dag.add_task(Add).depends_on((&a, &b));
|
||||||
|
|
||||||
|
dag.run(|fut| {
|
||||||
|
tokio::spawn(fut);
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(dag.get(sum).unwrap(), 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_complex_dag_without_tracing() {
|
||||||
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
|
// Create multiple layers
|
||||||
|
let v1 = dag.add_task(Value(1));
|
||||||
|
let v2 = dag.add_task(Value(2));
|
||||||
|
let v3 = dag.add_task(Value(3));
|
||||||
|
let v4 = dag.add_task(Value(4));
|
||||||
|
|
||||||
|
let sum12 = dag.add_task(Add).depends_on((&v1, &v2));
|
||||||
|
let sum34 = dag.add_task(Add).depends_on((&v3, &v4));
|
||||||
|
let final_sum = dag.add_task(Add).depends_on((&sum12, &sum34));
|
||||||
|
|
||||||
|
dag.run(|fut| {
|
||||||
|
tokio::spawn(fut);
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(dag.get(final_sum).unwrap(), 10);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user