fix: change where TaskHandle is borrowed to prevent repeat output fetches
This commit is contained in:
1 parent
c8efb00175
commit
9fbe4c2fd8
47 files changed
+323
-513
No files matched your search
+12
-16
@@ -50,11 +50,11 @@ use tracing::debug;
|
||||
/// let b = dag.add_task(Multiplier::new(2));
|
||||
/// // b is TaskBuilder until we call depends_on()
|
||||
///
|
||||
/// let b = b.depends_on(a);
|
||||
/// let b = b.depends_on(&a);
|
||||
/// // Now b is a TaskHandle<i32>
|
||||
///
|
||||
///let mut output = dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await.unwrap();
|
||||
/// assert_eq!(output.get(b).unwrap(), 20);
|
||||
/// assert_eq!(output.get(b), 20);
|
||||
/// # };
|
||||
/// ```
|
||||
#[must_use]
|
||||
@@ -76,11 +76,16 @@ where
|
||||
/// Provide all dependencies exactly once as a tuple.
|
||||
///
|
||||
/// The dependencies must match the task's `Input` type exactly:
|
||||
/// - `Input = A`: Pass `TaskHandle<A>`
|
||||
/// - `Input = (A, B, ...)`: Pass `(TaskHandle<A>, TaskHandle<B>, ...)`
|
||||
/// - `Input = A`: Pass `&TaskHandle<A>`
|
||||
/// - `Input = (A, B, ...)`: Pass `(&TaskHandle<A>, &TaskHandle<B>, ...)`
|
||||
///
|
||||
/// The order of dependencies in the tuple must match the order in `Input`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will cause a panic in [`DagRunner::run`] if called with a [`TaskHandle`] from a `DagRunner` instance
|
||||
/// other than the one which created this TaskBuilder.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
@@ -117,10 +122,10 @@ where
|
||||
/// let y = dag.add_task(Value(3));
|
||||
///
|
||||
/// // Single dependency
|
||||
/// let double = dag.add_task(Scale(2)).depends_on(x);
|
||||
/// let double = dag.add_task(Scale(2)).depends_on(&x);
|
||||
///
|
||||
/// // Multiple dependencies: tuple form
|
||||
/// let sum = dag.add_task(Add).depends_on((x, y));
|
||||
/// let sum = dag.add_task(Add).depends_on((&x, &y));
|
||||
///
|
||||
///let mut output = dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await.unwrap();
|
||||
/// # };
|
||||
@@ -188,7 +193,7 @@ pub struct NodeId(pub u32);
|
||||
///
|
||||
///let mut output = dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await.unwrap();
|
||||
///
|
||||
/// assert_eq!(output.get(node).unwrap(), 42);
|
||||
/// assert_eq!(output.get(node), 42);
|
||||
/// # };
|
||||
/// ```
|
||||
pub struct TaskHandle<T> {
|
||||
@@ -196,14 +201,6 @@ pub struct TaskHandle<T> {
|
||||
pub(crate) _phantom: PhantomData<fn() -> T>,
|
||||
}
|
||||
|
||||
impl<T> Clone for TaskHandle<T> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Copy for TaskHandle<T> {}
|
||||
|
||||
/// Takes a task and converts it to either a TaskBuilder or a TaskHandle,
|
||||
/// depending on whether it has inputs or not.
|
||||
///
|
||||
@@ -235,7 +232,6 @@ where
|
||||
/// Macro to implement TaskWire for different tuple sizes.
|
||||
macro_rules! impl_wire_tuple {
|
||||
($($T:ident),+) => {
|
||||
// For tuples of any combination of &TaskHandle, TaskHandle, and TaskBuilder
|
||||
impl<Tk, $($T: Send + Sync + 'static),+> TaskWire<($($T,)+)> for Tk
|
||||
where
|
||||
Tk: Task<($($T,)+)> + Sync + 'static
|
||||
|
||||
@@ -38,7 +38,7 @@ async fn test_unit_struct_with_state() {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(output.get(handle).unwrap(), 42);
|
||||
assert_eq!(output.get(handle), 42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -62,8 +62,8 @@ fn test_task_builder_chain() {
|
||||
|
||||
// Test chaining multiple tasks
|
||||
let t1 = dag.add_task(TestTask { value: 1 });
|
||||
let t2 = dag.add_task(TestTaskWithInput).depends_on(t1);
|
||||
let t3 = dag.add_task(TestTaskWithInput).depends_on(t2);
|
||||
let t2 = dag.add_task(TestTaskWithInput).depends_on(&t1);
|
||||
let t3 = dag.add_task(TestTaskWithInput).depends_on(&t2);
|
||||
|
||||
// All should return TaskHandles
|
||||
let _ = t1;
|
||||
@@ -89,7 +89,7 @@ fn test_multiple_dependencies() {
|
||||
let b = dag.add_task(TestTask { value: 20 });
|
||||
|
||||
// Test multiple dependencies
|
||||
let sum = dag.add_task(AddTask).depends_on((a, b));
|
||||
let sum = dag.add_task(AddTask).depends_on((&a, &b));
|
||||
|
||||
// Should return a TaskHandle
|
||||
let _ = sum;
|
||||
|
||||
+3
-3
@@ -17,7 +17,7 @@ impl DepsTuple<()> for () {
|
||||
}
|
||||
|
||||
// Implementation for single dependency (all forms)
|
||||
impl<O> DepsTuple<(O,)> for TaskHandle<O> {
|
||||
impl<O> DepsTuple<(O,)> for &TaskHandle<O> {
|
||||
fn to_node_ids(self) -> Vec<NodeId> {
|
||||
vec![self.id]
|
||||
}
|
||||
@@ -28,14 +28,14 @@ impl<O> DepsTuple<(O,)> for TaskHandle<O> {
|
||||
/// This allows flexible dependency specification:
|
||||
/// ```ignore
|
||||
/// // Using handles
|
||||
/// task.depends_on((handle_a, handle_b))
|
||||
/// task.depends_on((&handle_a, &handle_b))
|
||||
///
|
||||
/// // Using builders directly
|
||||
/// task.depends_on((dag.add_task(TaskA), dag.add_task(TaskB)))
|
||||
/// ```
|
||||
macro_rules! impl_deps_tuple {
|
||||
($($O:ident),+) => {
|
||||
impl<$($O),+> DepsTuple<($($O,)+)> for ($(TaskHandle<$O>,)+)
|
||||
impl<$($O),+> DepsTuple<($($O,)+)> for ($(&TaskHandle<$O>,)+)
|
||||
{
|
||||
#[allow(non_snake_case)]
|
||||
fn to_node_ids(self) -> Vec<NodeId> {
|
||||
|
||||
+13
-52
@@ -37,19 +37,6 @@ fn test_deps_tuple_single_handle_ref() {
|
||||
assert_eq!(node_ids[0], NodeId(42));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deps_tuple_single_handle_owned() {
|
||||
// Test lines 32-36 in deps.rs - TaskHandle<T>
|
||||
let handle: TaskHandle<String> = TaskHandle {
|
||||
id: NodeId(99),
|
||||
_phantom: std::marker::PhantomData,
|
||||
};
|
||||
let handle_copy = handle; // Copy for use
|
||||
let node_ids = handle_copy.to_node_ids();
|
||||
assert_eq!(node_ids.len(), 1);
|
||||
assert_eq!(node_ids[0], NodeId(99));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deps_tuple_single_handle_tuple_ref() {
|
||||
// Test lines 38-42 in deps.rs - (&TaskHandle<T>,)
|
||||
@@ -57,24 +44,11 @@ fn test_deps_tuple_single_handle_tuple_ref() {
|
||||
id: NodeId(7),
|
||||
_phantom: std::marker::PhantomData,
|
||||
};
|
||||
let node_ids = (handle,).to_node_ids();
|
||||
let node_ids = (&handle,).to_node_ids();
|
||||
assert_eq!(node_ids.len(), 1);
|
||||
assert_eq!(node_ids[0], NodeId(7));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deps_tuple_single_handle_tuple_owned() {
|
||||
// Test lines 44-48 in deps.rs - (TaskHandle<T>,)
|
||||
let handle: TaskHandle<Vec<u8>> = TaskHandle {
|
||||
id: NodeId(13),
|
||||
_phantom: std::marker::PhantomData,
|
||||
};
|
||||
let handle_copy = handle; // Copy for use
|
||||
let node_ids = (handle_copy,).to_node_ids();
|
||||
assert_eq!(node_ids.len(), 1);
|
||||
assert_eq!(node_ids[0], NodeId(13));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deps_tuple_task_builder_ref() {
|
||||
// Test lines 51-55 in deps.rs - &TaskBuilder
|
||||
@@ -96,7 +70,7 @@ fn test_deps_tuple_task_builder_tuple() {
|
||||
let builder = dag.add_task(TestTask { value: 100 });
|
||||
let builder_id = builder.id;
|
||||
|
||||
let node_ids = (builder,).to_node_ids();
|
||||
let node_ids = (&builder,).to_node_ids();
|
||||
assert_eq!(node_ids.len(), 1);
|
||||
assert_eq!(node_ids[0], builder_id);
|
||||
}
|
||||
@@ -114,7 +88,7 @@ fn test_deps_tuple_multiple_handles() {
|
||||
};
|
||||
|
||||
// Test 2-tuple
|
||||
let node_ids = (handle1, handle2).to_node_ids();
|
||||
let node_ids = (&handle1, &handle2).to_node_ids();
|
||||
assert_eq!(node_ids.len(), 2);
|
||||
assert_eq!(node_ids[0], NodeId(1));
|
||||
assert_eq!(node_ids[1], NodeId(2));
|
||||
@@ -124,32 +98,13 @@ fn test_deps_tuple_multiple_handles() {
|
||||
id: NodeId(3),
|
||||
_phantom: std::marker::PhantomData,
|
||||
};
|
||||
let node_ids = (handle1, handle2, handle3).to_node_ids();
|
||||
let node_ids = (&handle1, &handle2, &handle3).to_node_ids();
|
||||
assert_eq!(node_ids.len(), 3);
|
||||
assert_eq!(node_ids[0], NodeId(1));
|
||||
assert_eq!(node_ids[1], NodeId(2));
|
||||
assert_eq!(node_ids[2], NodeId(3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deps_tuple_multiple_builders() {
|
||||
// Test macro-generated implementations for multiple builders
|
||||
let mut dag = DagRunner::new();
|
||||
let builder1 = dag.add_task(TestTask { value: 10 });
|
||||
let builder1_id = builder1.id;
|
||||
let builder2 = dag.add_task(TestTask { value: 20 });
|
||||
let builder2_id = builder2.id;
|
||||
let builder3 = dag.add_task(TestTask { value: 30 });
|
||||
let builder3_id = builder3.id;
|
||||
|
||||
// Test 3-tuple of builders
|
||||
let node_ids = (builder1, builder2, builder3).to_node_ids();
|
||||
assert_eq!(node_ids.len(), 3);
|
||||
assert_eq!(node_ids[0], builder1_id);
|
||||
assert_eq!(node_ids[1], builder2_id);
|
||||
assert_eq!(node_ids[2], builder3_id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deps_tuple_large_tuples() {
|
||||
// Test that larger tuples work (up to 16 elements)
|
||||
@@ -162,7 +117,7 @@ fn test_deps_tuple_large_tuples() {
|
||||
}
|
||||
|
||||
// Test 4-tuple
|
||||
let node_ids = (handles[0], handles[1], handles[2], handles[3]).to_node_ids();
|
||||
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 as u32));
|
||||
@@ -170,8 +125,14 @@ fn test_deps_tuple_large_tuples() {
|
||||
|
||||
// Test 8-tuple
|
||||
let node_ids = (
|
||||
handles[0], handles[1], handles[2], handles[3], handles[4], handles[5], handles[6],
|
||||
handles[7],
|
||||
&handles[0],
|
||||
&handles[1],
|
||||
&handles[2],
|
||||
&handles[3],
|
||||
&handles[4],
|
||||
&handles[5],
|
||||
&handles[6],
|
||||
&handles[7],
|
||||
)
|
||||
.to_node_ids();
|
||||
assert_eq!(node_ids.len(), 8);
|
||||
|
||||
@@ -6,42 +6,13 @@
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub enum DagError {
|
||||
/// Invalid dependency: task does not exist
|
||||
InvalidDependency { task_id: u32 },
|
||||
/// Type mismatch in task dependencies
|
||||
TypeMismatch {
|
||||
expected: &'static str,
|
||||
found: &'static str,
|
||||
},
|
||||
/// Task panicked during execution
|
||||
TaskPanicked { task_id: u32, panic_message: String },
|
||||
/// Result not found for task
|
||||
ResultNotFound { task_id: u32 },
|
||||
}
|
||||
|
||||
impl std::fmt::Display for DagError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
DagError::InvalidDependency { task_id } => {
|
||||
write!(
|
||||
f,
|
||||
"Invalid dependency: task #{} does not exist.\n\
|
||||
\n\
|
||||
Ensure all task handles reference tasks that have been added to the DAG.",
|
||||
task_id
|
||||
)
|
||||
}
|
||||
DagError::TypeMismatch { expected, found } => {
|
||||
write!(
|
||||
f,
|
||||
"Type mismatch in task dependencies.\n\
|
||||
Expected: {}\n\
|
||||
Found: {}\n\
|
||||
\n\
|
||||
Verify that dependency types match the task's Input type exactly.",
|
||||
expected, found
|
||||
)
|
||||
}
|
||||
DagError::TaskPanicked {
|
||||
task_id,
|
||||
panic_message,
|
||||
@@ -54,15 +25,6 @@ impl std::fmt::Display for DagError {
|
||||
task_id, panic_message
|
||||
)
|
||||
}
|
||||
DagError::ResultNotFound { task_id } => {
|
||||
write!(
|
||||
f,
|
||||
"Result not found for task #{}.\n\
|
||||
\n\
|
||||
Call dag.run(|fut| async move {{ tokio::spawn(fut).await.unwrap() }}).",
|
||||
task_id
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-38
@@ -2,33 +2,6 @@
|
||||
|
||||
use crate::error::DagError;
|
||||
|
||||
#[test]
|
||||
fn test_dag_error_display_invalid_dependency() {
|
||||
// Test lines 47-54 in error.rs
|
||||
let err = DagError::InvalidDependency { task_id: 42 };
|
||||
let display = format!("{}", err);
|
||||
|
||||
assert!(display.contains("Invalid dependency"));
|
||||
assert!(display.contains("task #42"));
|
||||
assert!(display.contains("does not exist"));
|
||||
assert!(display.contains("Ensure all task handles"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dag_error_display_type_mismatch() {
|
||||
// Test lines 56-65 in error.rs
|
||||
let err = DagError::TypeMismatch {
|
||||
expected: "i32",
|
||||
found: "String",
|
||||
};
|
||||
let display = format!("{}", err);
|
||||
|
||||
assert!(display.contains("Type mismatch"));
|
||||
assert!(display.contains("Expected: i32"));
|
||||
assert!(display.contains("Found: String"));
|
||||
assert!(display.contains("Verify that dependency types"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dag_error_display_task_panicked() {
|
||||
// Test lines 67-77 in error.rs
|
||||
@@ -44,20 +17,13 @@ fn test_dag_error_display_task_panicked() {
|
||||
assert!(display.contains("entire DAG execution is aborted"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dag_error_display_result_not_found() {
|
||||
let err = DagError::ResultNotFound { task_id: 7 };
|
||||
let display = format!("{}", err);
|
||||
|
||||
assert!(display.contains("Result not found"));
|
||||
assert!(display.contains("task #7"));
|
||||
assert!(display.contains("Call dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dag_error_std_error_impl() {
|
||||
// Test that DagError implements std::error::Error
|
||||
let err = DagError::InvalidDependency { task_id: 1 };
|
||||
let err = DagError::TaskPanicked {
|
||||
task_id: 1,
|
||||
panic_message: "test panic".to_string(),
|
||||
};
|
||||
let err_ref: &dyn std::error::Error = &err;
|
||||
|
||||
// Should be able to call Error trait methods
|
||||
|
||||
+14
-14
@@ -42,11 +42,11 @@
|
||||
//! let y = dag.add_task(Value(3));
|
||||
//!
|
||||
//! // Add task with dependencies
|
||||
//! let sum = dag.add_task(Add).depends_on((x, y));
|
||||
//! let sum = dag.add_task(Add).depends_on((&x, &y));
|
||||
//!
|
||||
//! // Execute and retrieve results
|
||||
//!let mut output = dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await.unwrap();
|
||||
//! assert_eq!(output.get(sum).unwrap(), 5);
|
||||
//! assert_eq!(output.get(sum), 5);
|
||||
//! # };
|
||||
//! ```
|
||||
//!
|
||||
@@ -184,7 +184,7 @@
|
||||
//! # async {
|
||||
//! # let mut dag = DagRunner::new();
|
||||
//! # let source = dag.add_task(Value(42));
|
||||
//! let doubled = dag.add_task(Double).depends_on(source);
|
||||
//! let doubled = dag.add_task(Double).depends_on(&source);
|
||||
//! # };
|
||||
//! ```
|
||||
//!
|
||||
@@ -210,7 +210,7 @@
|
||||
//! # let mut dag = DagRunner::new();
|
||||
//! # let x = dag.add_task(Value(2));
|
||||
//! # let y = dag.add_task(Value(3));
|
||||
//! let sum = dag.add_task(Add).depends_on((x, y));
|
||||
//! let sum = dag.add_task(Add).depends_on((&x, &y));
|
||||
//! # };
|
||||
//! ```
|
||||
//!
|
||||
@@ -252,13 +252,13 @@
|
||||
//! let mut dag = DagRunner::new();
|
||||
//!
|
||||
//! let base = dag.add_task(Value(10));
|
||||
//! let plus1 = dag.add_task(Add(1)).depends_on(base);
|
||||
//! let times2 = dag.add_task(Scale(2)).depends_on(base);
|
||||
//! let plus1 = dag.add_task(Add(1)).depends_on(&base);
|
||||
//! let times2 = dag.add_task(Scale(2)).depends_on(&base);
|
||||
//!
|
||||
//!let mut output = dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await.unwrap();
|
||||
//!
|
||||
//! assert_eq!(output.get(plus1).unwrap(), 11);
|
||||
//! assert_eq!(output.get(times2).unwrap(), 20);
|
||||
//! assert_eq!(output.get(plus1), 11);
|
||||
//! assert_eq!(output.get(times2), 20);
|
||||
//! # };
|
||||
//! ```
|
||||
//!
|
||||
@@ -310,11 +310,11 @@
|
||||
//! let name = dag.add_task(Name("Alice".to_string()));
|
||||
//! let age = dag.add_task(Age(30));
|
||||
//! let active = dag.add_task(Active(true));
|
||||
//! let result = dag.add_task(FormatUser).depends_on((name, age, active));
|
||||
//! let result = dag.add_task(FormatUser).depends_on((&name, &age, &active));
|
||||
//!
|
||||
//!let mut output = dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await.unwrap();
|
||||
//!
|
||||
//! assert_eq!(output.get(result).unwrap(), "User: Alice, Age: 30, Active: true");
|
||||
//! assert_eq!(output.get(result), "User: Alice, Age: 30, Active: true");
|
||||
//! # };
|
||||
//! ```
|
||||
//!
|
||||
@@ -368,7 +368,7 @@
|
||||
//! # let node = dag.add_task(Value(42));
|
||||
//! // Simple approach with .unwrap()
|
||||
//! let mut output = dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await.unwrap();
|
||||
//! let result = output.get(node).unwrap();
|
||||
//! let result = output.get(node);
|
||||
//! # let mut dag = DagRunner::new();
|
||||
//!
|
||||
//! // Or handle errors explicitly
|
||||
@@ -428,9 +428,9 @@
|
||||
//! let data = dag.add_task(FetchData);
|
||||
//!
|
||||
//! // All three tasks get efficient Arc-wrapped sharing automatically
|
||||
//! let task1 = dag.add_task(ProcessData).depends_on(data);
|
||||
//! let task2 = dag.add_task(ProcessData).depends_on(data);
|
||||
//! let task3 = dag.add_task(ProcessData).depends_on(data);
|
||||
//! let task1 = dag.add_task(ProcessData).depends_on(&data);
|
||||
//! let task2 = dag.add_task(ProcessData).depends_on(&data);
|
||||
//! let task3 = dag.add_task(ProcessData).depends_on(&data);
|
||||
//!
|
||||
//!let mut output = dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await.unwrap();
|
||||
//! # };
|
||||
|
||||
+12
-22
@@ -1,6 +1,6 @@
|
||||
use std::{any::Any, sync::Arc};
|
||||
|
||||
use crate::{builder::NodeId, runner::PassThroughHashMap, DagError, DagResult, TaskHandle};
|
||||
use crate::{builder::NodeId, runner::PassThroughHashMap, TaskHandle};
|
||||
|
||||
pub struct DagOutput {
|
||||
outputs: PassThroughHashMap<NodeId, Arc<dyn Any + Send + Sync + 'static>>,
|
||||
@@ -12,14 +12,11 @@ impl DagOutput {
|
||||
/// # Behavior
|
||||
///
|
||||
/// All task outputs are stored after execution and can be retrieved via get().
|
||||
/// Each task's output can only be retrieved once, after which it will return `DagError::ResultNotFound`.
|
||||
///
|
||||
/// # Errors
|
||||
/// # Panics
|
||||
///
|
||||
/// Returns `DagError::ResultNotFound` if:
|
||||
/// - The task hasn't been executed yet
|
||||
/// - The handle is invalid
|
||||
/// - The output has already been retrieved
|
||||
/// This function panics if called with a [`TaskHandle`] from a [`crate::DagRunner`] instance other than the
|
||||
/// one which produced this DagOutput.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -49,26 +46,19 @@ impl DagOutput {
|
||||
///
|
||||
/// let mut output = dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await.unwrap();
|
||||
///
|
||||
/// assert_eq!(output.get(task).unwrap(), 42);
|
||||
/// assert_eq!(output.get(task), 42);
|
||||
/// # };
|
||||
/// ```
|
||||
pub fn get<T: 'static + Send + Sync>(&mut self, handle: TaskHandle<T>) -> DagResult<T> {
|
||||
let arc_output = self
|
||||
.outputs
|
||||
.remove(&handle.id)
|
||||
.ok_or(DagError::ResultNotFound {
|
||||
task_id: handle.id.0,
|
||||
})?;
|
||||
pub fn get<T: 'static + Send + Sync>(&mut self, handle: TaskHandle<T>) -> T {
|
||||
// Because this consumes the task handle, it can only be retrieved once, so is certain to be there
|
||||
let arc_output = self.outputs.remove(&handle.id).unwrap();
|
||||
|
||||
// Downcast Arc<dyn Any> to Arc<T>
|
||||
let output = arc_output
|
||||
.downcast::<T>()
|
||||
.map_err(|_| DagError::TypeMismatch {
|
||||
expected: std::any::type_name::<T>(),
|
||||
found: "unknown",
|
||||
})?;
|
||||
let output = arc_output.downcast::<T>().unwrap();
|
||||
|
||||
Ok(Arc::into_inner(output).unwrap())
|
||||
// Because we never give the user access to anything but a reference to the Arc,
|
||||
// we can guarantee that the strong reference count is exactly one here
|
||||
Arc::into_inner(output).unwrap()
|
||||
}
|
||||
|
||||
pub(super) fn new(
|
||||
|
||||
+5
-5
@@ -94,11 +94,11 @@ pub(crate) type PassThroughHashMap<K, V> = HashMap<K, V, PassThroughHasher>;
|
||||
/// // Construct instances using ::new() pattern
|
||||
/// let x = dag.add_task(LoadValue::new(2));
|
||||
/// let y = dag.add_task(LoadValue::new(3));
|
||||
/// let sum = dag.add_task(Add).depends_on((x, y));
|
||||
/// let sum = dag.add_task(Add).depends_on((&x, &y));
|
||||
///
|
||||
///let mut output = dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await.unwrap();
|
||||
///
|
||||
/// assert_eq!(output.get(sum).unwrap(), 5);
|
||||
/// assert_eq!(output.get(sum), 5);
|
||||
/// # };
|
||||
/// ```
|
||||
pub struct DagRunner {
|
||||
@@ -183,10 +183,10 @@ impl DagRunner {
|
||||
/// let base = dag.add_task(LoadValue::new(10));
|
||||
///
|
||||
/// // Construct task with offset of 1
|
||||
/// let inc = dag.add_task(AddOffset::new(1)).depends_on(base);
|
||||
/// let inc = dag.add_task(AddOffset::new(1)).depends_on(&base);
|
||||
///
|
||||
///let mut output = dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await.unwrap();
|
||||
/// assert_eq!(output.get(inc).unwrap(), 11);
|
||||
/// assert_eq!(output.get(inc), 11);
|
||||
/// # };
|
||||
/// ```
|
||||
pub fn add_task<'dag, Input, Tk>(&'dag mut self, task: Tk) -> Tk::Retval<'dag>
|
||||
@@ -257,7 +257,7 @@ impl DagRunner {
|
||||
///
|
||||
/// let a = dag.add_task(Value(1));
|
||||
/// let b = dag.add_task(Value(2));
|
||||
/// let sum = dag.add_task(Add).depends_on((a, b));
|
||||
/// let sum = dag.add_task(Add).depends_on((&a, &b));
|
||||
///
|
||||
///let mut output = dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await.unwrap(); // Executes all tasks
|
||||
/// # };
|
||||
|
||||
+7
-44
@@ -4,9 +4,7 @@ use std::collections::HashMap;
|
||||
use std::hash::Hasher;
|
||||
|
||||
use crate::builder::TaskHandle;
|
||||
use crate::error::DagError;
|
||||
use crate::runner::{DagRunner, PassThroughHasher};
|
||||
use crate::DagOutput;
|
||||
|
||||
// Initialize tracing subscriber for tests (idempotent)
|
||||
#[cfg(feature = "tracing")]
|
||||
@@ -65,9 +63,10 @@ fn test_dag_runner_default() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[should_panic]
|
||||
async fn test_get_wrong_type() {
|
||||
init_tracing();
|
||||
// Test getting with wrong type - this should return TypeMismatch
|
||||
// Test getting with wrong type - this should panic
|
||||
let mut dag = DagRunner::new();
|
||||
let handle = dag.add_task(TestTask { value: 42 });
|
||||
|
||||
@@ -82,38 +81,11 @@ async fn test_get_wrong_type() {
|
||||
_phantom: std::marker::PhantomData,
|
||||
};
|
||||
|
||||
let result = output.get(fake_handle);
|
||||
assert!(result.is_err());
|
||||
|
||||
// When downcast fails, we get TypeMismatch
|
||||
match result.unwrap_err() {
|
||||
DagError::TypeMismatch { expected, .. } => {
|
||||
assert_eq!(expected, std::any::type_name::<String>());
|
||||
}
|
||||
_ => panic!("Expected TypeMismatch error"),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_result_not_found() {
|
||||
init_tracing();
|
||||
// Test getting a result before running the DAG
|
||||
let mut dag = DagRunner::new();
|
||||
let handle = dag.add_task(TestTask { value: 42 });
|
||||
let handle_id = handle.id.0; // Save ID before moving handle
|
||||
let mut output = DagOutput::new(HashMap::default());
|
||||
|
||||
let result = output.get(handle);
|
||||
assert!(result.is_err());
|
||||
match result.unwrap_err() {
|
||||
DagError::ResultNotFound { task_id } => {
|
||||
assert_eq!(task_id, handle_id);
|
||||
}
|
||||
_ => panic!("Expected ResultNotFound error"),
|
||||
}
|
||||
let _result = output.get(fake_handle);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[should_panic]
|
||||
async fn test_invalid_node_id_in_get() {
|
||||
init_tracing();
|
||||
// Test getting with an invalid node ID
|
||||
@@ -130,16 +102,7 @@ async fn test_invalid_node_id_in_get() {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let result = output.get(invalid_handle);
|
||||
assert!(result.is_err());
|
||||
|
||||
// With centralized output storage, we get ResultNotFound for invalid node IDs
|
||||
match result.unwrap_err() {
|
||||
DagError::ResultNotFound { task_id } => {
|
||||
assert_eq!(task_id, 999);
|
||||
}
|
||||
_ => panic!("Expected ResultNotFound error"),
|
||||
}
|
||||
let _result = output.get(invalid_handle);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -175,8 +138,8 @@ async fn test_task_panic_in_multi_task_layer() {
|
||||
let source = dag.add_task(Source);
|
||||
|
||||
// Create two tasks in the same layer - one panics, one doesn't
|
||||
let _panic_task = dag.add_task(PanicTask).depends_on(source);
|
||||
let _good_task = dag.add_task(GoodTask).depends_on(source);
|
||||
let _panic_task = dag.add_task(PanicTask).depends_on(&source);
|
||||
let _good_task = dag.add_task(GoodTask).depends_on(&source);
|
||||
|
||||
// Run the DAG - the panic should be caught
|
||||
let result = dag
|
||||
|
||||
@@ -47,9 +47,6 @@ where
|
||||
/// TaskInput with the remaining dependencies.
|
||||
///
|
||||
/// No dependencies can be retrieved from an empty TaskInput (Input = ()).
|
||||
///
|
||||
/// **Note:** Calling [`std::mem::forget`] on a `TaskInput` instance will make it impossible to retrieve outputs of dependencies.
|
||||
/// There's no reason to ever do this.
|
||||
pub struct TaskInput<'inputs, Input: Send> {
|
||||
inputs: Iter<'inputs, Arc<dyn Any + Send + Sync + 'static>>,
|
||||
phantom: PhantomData<Input>,
|
||||
|
||||
Reference in new issue
Block a user