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(¤t) {
213 continue;
214 }
215 visited.insert(current);
216
217 for (&dependent, dependencies) in &self.dependencies {
218 if dependencies.contains(¤t) && !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}