fix: support arbitrary types in task dependencies
Enable tasks to accept ANY type implementing Clone + Send + Sync as dependencies, without requiring users to implement ExtractInput trait. The #[task] macro now generates task-specific extraction logic inline via a new extract_and_run() method. This leverages the macro's perfect compile-time knowledge of parameter types to handle extraction for: - Zero parameters (unit type, source tasks) - Single parameter (any Clone + Send + Sync type) - Multiple parameters (tuples, awaited concurrently) Previously, only hardcoded primitive types and specific stdlib types worked. Custom user types required manual ExtractInput implementations. Now custom types work automatically with zero API changes. Changes: - Add extract_and_run() method to Task trait (internal/hidden) - Generate inline extraction in #[task] macro based on parameter count - Remove ExtractInput bound from DagRunner::add_task() - Retain ExtractInput for internal task_fn() test helper - Add comprehensive custom type tests No breaking changes - purely internal implementation improvement.
This commit is contained in:
7 files changed
+300
-14
No files matched your search
+7
-10
@@ -14,7 +14,6 @@ use std::sync::Arc;
|
||||
use futures::channel::oneshot;
|
||||
|
||||
use crate::error::DagError;
|
||||
use crate::extract::ExtractInput;
|
||||
use crate::task::Task;
|
||||
use crate::types::NodeId;
|
||||
|
||||
@@ -84,7 +83,7 @@ impl<T: Task + 'static> TypedNode<T> {
|
||||
|
||||
impl<T: Task + 'static> ExecutableNode for TypedNode<T>
|
||||
where
|
||||
T::Input: 'static + Clone + ExtractInput,
|
||||
T::Input: 'static + Clone,
|
||||
T::Output: 'static + Clone,
|
||||
{
|
||||
fn create_output_channels(&self, num_dependents: usize) -> ChannelPair {
|
||||
@@ -107,24 +106,22 @@ where
|
||||
senders: ErasedChannels,
|
||||
) -> ExecuteFuture {
|
||||
Box::pin(async move {
|
||||
// Extract typed input from type-erased receivers
|
||||
// TypedNode<T> knows T::Input, so it can safely downcast
|
||||
let input = match T::Input::extract_from_channels(receivers).await {
|
||||
Ok(input) => input,
|
||||
// Use the task's extract_and_run method which has inline extraction logic
|
||||
// generated by the #[task] macro. This allows ANY type to work without
|
||||
// requiring ExtractInput trait implementations.
|
||||
let output = match self.task.extract_and_run(receivers).await {
|
||||
Ok(output) => output,
|
||||
Err(_msg) => {
|
||||
// Failed to extract input - likely channel closed or type mismatch
|
||||
return Err(DagError::InvalidDependency { task_id: self.id.0 });
|
||||
}
|
||||
};
|
||||
|
||||
// Execute task (consumes self.task)
|
||||
let output = self.task.run(input).await;
|
||||
|
||||
// Arc Wrapping Strategy:
|
||||
// Wrap output in Arc ONCE for efficient fan-out sharing.
|
||||
// For 1:N dependencies, this enables O(1) sharing (Arc clone = atomic increment)
|
||||
// instead of O(N) cloning (N deep copies of output data).
|
||||
// Downstream tasks receive &T after ExtractInput extracts from Arc.
|
||||
// Downstream tasks receive &T after extraction from Arc.
|
||||
let arc_output = Arc::new(output);
|
||||
|
||||
// Send Arc to all dependents via their channels
|
||||
|
||||
Reference in new issue
Block a user