Skip to main content

safer_ring/future/
batch_future.rs

1//! Future implementation for batch operations.
2
3use std::collections::HashMap;
4use std::future::Future;
5use std::pin::Pin;
6use std::sync::Arc;
7use std::task::{Context, Poll};
8
9use crate::error::Result;
10use crate::future::WakerRegistry;
11
12use crate::ring::batch::{BatchResult, OperationResult};
13use crate::ring::Ring;
14
15/// Future for batch operations that can be awaited.
16///
17/// This future manages the completion of multiple operations submitted as a batch,
18/// handling dependencies and partial failures according to the batch configuration.
19///
20/// # Example
21///
22/// ```rust,ignore
23/// # use safer_ring::{Ring, Batch, Operation, PinnedBuffer};
24/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
25/// let mut ring = Ring::new(32)?;
26/// let mut batch = Batch::new();
27/// let mut buffer = PinnedBuffer::with_capacity(1024);
28///
29/// batch.add_operation(Operation::read().fd(0).buffer(buffer.as_mut_slice()))?;
30/// let results = ring.submit_batch(batch).await?;
31///
32/// println!("Batch completed with {} operations", results.results.len());
33/// # Ok(())
34/// # }
35/// ```
36pub struct BatchFuture<'ring> {
37    /// Ring reference for polling completions
38    ring: &'ring mut Ring<'ring>,
39    /// Results collected so far
40    results: Vec<Option<OperationResult>>,
41    /// Dependencies between operations (dependent -> dependencies)
42    dependencies: HashMap<usize, Vec<usize>>,
43    /// Whether to fail fast on first error
44    fail_fast: bool,
45    /// Whether the batch has completed
46    completed: bool,
47    /// Operation IDs for tracking completions
48    #[allow(dead_code)] // Used for tracking operations, may be needed for debugging
49    operation_ids: Vec<Option<u64>>,
50    /// Fast lookup map: operation_id -> batch_index for O(1) completion matching
51    id_to_index: HashMap<u64, usize>,
52}
53
54impl<'ring> BatchFuture<'ring> {
55    /// Create a new batch future.
56    ///
57    /// # Arguments
58    ///
59    /// * `operation_ids` - Vector of operation IDs that have been submitted
60    /// * `dependencies` - Map of operation dependencies
61    /// * `ring` - Ring reference for completion polling
62    /// * `waker_registry` - Waker registry for async coordination
63    /// * `fail_fast` - Whether to cancel remaining operations on first failure
64    pub(crate) fn new(
65        operation_ids: Vec<Option<u64>>,
66        dependencies: HashMap<usize, Vec<usize>>,
67        ring: &'ring mut Ring<'ring>,
68        _waker_registry: Arc<WakerRegistry>,
69        fail_fast: bool,
70    ) -> Self {
71        let operation_count = operation_ids.len();
72        let results = (0..operation_count).map(|_| None).collect();
73
74        // Build the fast lookup map for O(1) operation_id -> batch_index mapping
75        let mut id_to_index = HashMap::new();
76        for (index, id_opt) in operation_ids.iter().enumerate() {
77            if let Some(id) = id_opt {
78                id_to_index.insert(*id, index);
79            }
80        }
81
82        Self {
83            ring,
84            results,
85            dependencies,
86            fail_fast,
87            completed: false,
88            operation_ids,
89            id_to_index,
90        }
91    }
92
93    /// Poll for completion of submitted operations.
94    fn poll_completions(&mut self, cx: &mut Context<'_>) -> Poll<Result<()>> {
95        let mut any_completed = false;
96        let mut any_failed = false;
97        let mut completed_operations = Vec::new();
98
99        // Process ALL available completions in one batch for efficiency
100        // This is much more efficient than checking each operation individually
101        match self.ring.try_complete() {
102            Ok(completions) => {
103                // Process each completion and match it to our pending operations
104                for completion in completions {
105                    let operation_id = completion.id();
106
107                    // Use O(1) HashMap lookup instead of O(N) linear search
108                    if let Some(&index) = self.id_to_index.get(&operation_id) {
109                        if self.results[index].is_some() {
110                            continue; // Already completed (shouldn't happen, but defensive)
111                        }
112
113                        // Extract the result from the completion
114                        let result = match completion.result() {
115                            Ok(bytes) => OperationResult::Success(*bytes),
116                            Err(e) => {
117                                let error_msg = e.to_string();
118                                OperationResult::Error(error_msg)
119                            }
120                        };
121
122                        let is_error = matches!(result, OperationResult::Error(_));
123                        self.results[index] = Some(result);
124                        any_completed = true;
125                        if is_error {
126                            any_failed = true;
127                        }
128                        completed_operations.push(index);
129                    }
130                    // If we can't find the operation, it might be from a different batch
131                    // or completed operation - ignore it
132                }
133            }
134            Err(e) => {
135                // Error polling completions - this might be a system error
136                return Poll::Ready(Err(e));
137            }
138        }
139
140        // Process completed operations to check for ready dependencies
141        for completed_index in completed_operations {
142            self.check_ready_operations(completed_index);
143
144            // Cancel dependent operations if fail_fast is enabled and this operation failed
145            if self.fail_fast
146                && matches!(
147                    self.results[completed_index],
148                    Some(OperationResult::Error(_))
149                )
150            {
151                self.cancel_dependent_operations(completed_index);
152            }
153        }
154
155        // If we're in fail_fast mode and something failed, cancel everything
156        if self.fail_fast && any_failed {
157            self.cancel_all_remaining_operations();
158            return Poll::Ready(Ok(()));
159        }
160
161        // Check if all operations have completed
162        if self.all_operations_completed() {
163            self.completed = true;
164            return Poll::Ready(Ok(()));
165        }
166
167        // If we made progress, continue polling
168        if any_completed {
169            cx.waker().wake_by_ref();
170            return Poll::Pending;
171        }
172
173        // For batch operations, we'll use a simple polling approach
174        // In a more sophisticated implementation, we could register wakers
175        // for individual operations, but for now we'll just return Pending
176        Poll::Pending
177    }
178
179    /// Check if operations that were waiting for dependencies are now ready.
180    fn check_ready_operations(&mut self, completed_index: usize) {
181        // For now, we submit all operations immediately, so dependency handling
182        // is simplified. In a more sophisticated implementation, we would
183        // track which operations are waiting for dependencies and submit them
184        // when their dependencies complete.
185
186        // This is a placeholder for future dependency handling logic
187        let _newly_ready: Vec<usize> = Vec::new();
188
189        // Find operations that were waiting for this one to complete
190        for (&_dependent_index, dependencies) in &self.dependencies {
191            if dependencies.contains(&completed_index) {
192                // Check if all dependencies for this operation are now satisfied
193                let _all_deps_satisfied = dependencies.iter().all(|&dep_index| {
194                    self.results[dep_index].is_some()
195                        && self.results[dep_index].as_ref().unwrap().is_success()
196                });
197
198                // In the current implementation, all operations are submitted immediately
199                // so we don't need to track ready operations
200            }
201        }
202    }
203
204    /// Cancel operations that depend on a failed operation.
205    fn cancel_dependent_operations(&mut self, failed_index: usize) {
206        let mut to_cancel = Vec::new();
207        let mut visited = std::collections::HashSet::new();
208        let mut stack = vec![failed_index];
209
210        // Find all operations that transitively depend on the failed operation
211        while let Some(current) = stack.pop() {
212            if visited.contains(&current) {
213                continue;
214            }
215            visited.insert(current);
216
217            for (&dependent, dependencies) in &self.dependencies {
218                if dependencies.contains(&current) && !visited.contains(&dependent) {
219                    to_cancel.push(dependent);
220                    stack.push(dependent);
221                }
222            }
223        }
224
225        // Cancel the dependent operations
226        for &index in &to_cancel {
227            if self.results[index].is_none() {
228                self.results[index] = Some(OperationResult::Cancelled);
229            }
230        }
231    }
232
233    /// Cancel all remaining operations (used in fail_fast mode).
234    fn cancel_all_remaining_operations(&mut self) {
235        for result in self.results.iter_mut() {
236            if result.is_none() {
237                *result = Some(OperationResult::Cancelled);
238            }
239        }
240    }
241
242    /// Check if all operations have completed (successfully, failed, or cancelled).
243    fn all_operations_completed(&self) -> bool {
244        self.results.iter().all(|result| result.is_some())
245    }
246
247    /// Submit operations that are ready (have no pending dependencies).
248    fn submit_ready_operations(&mut self) -> Result<()> {
249        // This would be called during initial setup or when dependencies are satisfied
250        // For now, we assume operations are submitted externally
251        Ok(())
252    }
253}
254
255impl<'ring> Future for BatchFuture<'ring> {
256    type Output = Result<BatchResult>;
257
258    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
259        if self.completed {
260            // Collect all results
261            let results: Vec<OperationResult> = self
262                .results
263                .iter()
264                .map(|opt| opt.as_ref().cloned().unwrap_or(OperationResult::Cancelled))
265                .collect();
266
267            return Poll::Ready(Ok(BatchResult::new(results)));
268        }
269
270        // Submit any operations that are ready
271        if let Err(e) = self.submit_ready_operations() {
272            return Poll::Ready(Err(e));
273        }
274
275        // Poll for completions
276        match self.poll_completions(cx) {
277            Poll::Ready(Ok(())) => {
278                // All operations completed, collect results
279                let results: Vec<OperationResult> = self
280                    .results
281                    .iter()
282                    .map(|opt| opt.as_ref().cloned().unwrap_or(OperationResult::Cancelled))
283                    .collect();
284
285                Poll::Ready(Ok(BatchResult::new(results)))
286            }
287            Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
288            Poll::Pending => Poll::Pending,
289        }
290    }
291}
292
293// Implement Drop to ensure proper cleanup
294impl<'ring> Drop for BatchFuture<'ring> {
295    fn drop(&mut self) {
296        // Cancel any remaining operations to prevent resource leaks
297        self.cancel_all_remaining_operations();
298    }
299}