1use crate::traits::BlockStore;
7use ipfrs_core::{Block, Cid};
8use std::sync::Arc;
9use tokio::sync::Semaphore;
10
11#[derive(Debug, Clone)]
13pub struct BatchConfig {
14 pub max_concurrency: usize,
16 pub batch_size: usize,
18 pub fail_fast: bool,
20}
21
22impl Default for BatchConfig {
23 fn default() -> Self {
24 Self {
25 max_concurrency: 10,
26 batch_size: 100,
27 fail_fast: false,
28 }
29 }
30}
31
32impl BatchConfig {
33 pub fn new(max_concurrency: usize, batch_size: usize) -> Self {
35 Self {
36 max_concurrency,
37 batch_size,
38 fail_fast: false,
39 }
40 }
41
42 pub fn with_fail_fast(mut self, fail_fast: bool) -> Self {
44 self.fail_fast = fail_fast;
45 self
46 }
47
48 pub fn high_throughput() -> Self {
50 Self {
51 max_concurrency: 50,
52 batch_size: 500,
53 fail_fast: false,
54 }
55 }
56
57 pub fn low_latency() -> Self {
59 Self {
60 max_concurrency: 20,
61 batch_size: 50,
62 fail_fast: false,
63 }
64 }
65
66 pub fn conservative() -> Self {
68 Self {
69 max_concurrency: 5,
70 batch_size: 20,
71 fail_fast: false,
72 }
73 }
74}
75
76#[derive(Debug, Clone)]
78pub struct BatchResult<T> {
79 pub successful: Vec<T>,
81 pub failed: Vec<(T, String)>,
83 pub total: usize,
85}
86
87impl<T> BatchResult<T> {
88 pub fn new() -> Self {
90 Self {
91 successful: Vec::new(),
92 failed: Vec::new(),
93 total: 0,
94 }
95 }
96
97 pub fn is_success(&self) -> bool {
99 self.failed.is_empty()
100 }
101
102 pub fn success_rate(&self) -> f64 {
104 if self.total == 0 {
105 1.0
106 } else {
107 self.successful.len() as f64 / self.total as f64
108 }
109 }
110
111 pub fn success_count(&self) -> usize {
113 self.successful.len()
114 }
115
116 pub fn failure_count(&self) -> usize {
118 self.failed.len()
119 }
120}
121
122impl<T> Default for BatchResult<T> {
123 fn default() -> Self {
124 Self::new()
125 }
126}
127
128pub async fn batch_put<S: BlockStore + Send + Sync + 'static>(
133 store: Arc<S>,
134 blocks: Vec<Block>,
135 config: BatchConfig,
136) -> BatchResult<Cid> {
137 let mut result = BatchResult::new();
138 result.total = blocks.len();
139
140 let semaphore = Arc::new(Semaphore::new(config.max_concurrency));
141 let mut handles = Vec::new();
142
143 for chunk in blocks.chunks(config.batch_size) {
144 for block in chunk {
145 let permit = semaphore
146 .clone()
147 .acquire_owned()
148 .await
149 .expect("semaphore is never explicitly closed");
150 let block = block.clone();
151 let cid = *block.cid();
152 let store = store.clone();
153
154 let handle = tokio::spawn(async move {
155 let _permit = permit; (cid, store.put(&block).await)
157 });
158
159 handles.push(handle);
160 }
161
162 for handle in handles.drain(..) {
164 match handle.await {
165 Ok((cid, Ok(_))) => result.successful.push(cid),
166 Ok((cid, Err(e))) => {
167 result.failed.push((cid, e.to_string()));
168 if config.fail_fast {
169 return result;
170 }
171 }
172 Err(e) => {
173 result
175 .failed
176 .push((Cid::default(), format!("Task error: {e}")));
177 }
178 }
179 }
180 }
181
182 result
183}
184
185pub async fn batch_get<S: BlockStore + Send + Sync + 'static>(
189 store: Arc<S>,
190 cids: Vec<Cid>,
191 config: BatchConfig,
192) -> BatchResult<Block> {
193 let mut result = BatchResult::new();
194 result.total = cids.len();
195
196 let semaphore = Arc::new(Semaphore::new(config.max_concurrency));
197 let mut handles = Vec::new();
198
199 for chunk in cids.chunks(config.batch_size) {
200 for cid in chunk {
201 let permit = semaphore
202 .clone()
203 .acquire_owned()
204 .await
205 .expect("semaphore is never explicitly closed");
206 let cid = *cid;
207 let store = store.clone();
208
209 let handle = tokio::spawn(async move {
210 let _permit = permit;
211 (cid, store.get(&cid).await)
212 });
213
214 handles.push(handle);
215 }
216
217 for handle in handles.drain(..) {
219 match handle.await {
220 Ok((_cid, Ok(Some(block)))) => result.successful.push(block),
221 Ok((cid, Ok(None))) => {
222 result.failed.push((
223 Block::from_parts(cid, bytes::Bytes::new()),
224 "Block not found".to_string(),
225 ));
226 }
227 Ok((cid, Err(e))) => {
228 result
229 .failed
230 .push((Block::from_parts(cid, bytes::Bytes::new()), e.to_string()));
231 if config.fail_fast {
232 return result;
233 }
234 }
235 Err(e) => {
236 result.failed.push((
237 Block::from_parts(Cid::default(), bytes::Bytes::new()),
238 format!("Task error: {e}"),
239 ));
240 }
241 }
242 }
243 }
244
245 result
246}
247
248pub async fn batch_delete<S: BlockStore + Send + Sync + 'static>(
250 store: Arc<S>,
251 cids: Vec<Cid>,
252 config: BatchConfig,
253) -> BatchResult<Cid> {
254 let mut result = BatchResult::new();
255 result.total = cids.len();
256
257 let semaphore = Arc::new(Semaphore::new(config.max_concurrency));
258 let mut handles = Vec::new();
259
260 for chunk in cids.chunks(config.batch_size) {
261 for cid in chunk {
262 let permit = semaphore
263 .clone()
264 .acquire_owned()
265 .await
266 .expect("semaphore is never explicitly closed");
267 let cid = *cid;
268 let store = store.clone();
269
270 let handle = tokio::spawn(async move {
271 let _permit = permit;
272 (cid, store.delete(&cid).await)
273 });
274
275 handles.push(handle);
276 }
277
278 for handle in handles.drain(..) {
280 match handle.await {
281 Ok((cid, Ok(_))) => result.successful.push(cid),
282 Ok((cid, Err(e))) => {
283 result.failed.push((cid, e.to_string()));
284 if config.fail_fast {
285 return result;
286 }
287 }
288 Err(e) => {
289 result
290 .failed
291 .push((Cid::default(), format!("Task error: {e}")));
292 }
293 }
294 }
295 }
296
297 result
298}
299
300pub async fn batch_has<S: BlockStore + Send + Sync + 'static>(
302 store: Arc<S>,
303 cids: Vec<Cid>,
304 config: BatchConfig,
305) -> BatchResult<(Cid, bool)> {
306 let mut result = BatchResult::new();
307 result.total = cids.len();
308
309 let semaphore = Arc::new(Semaphore::new(config.max_concurrency));
310 let mut handles = Vec::new();
311
312 for chunk in cids.chunks(config.batch_size) {
313 for cid in chunk {
314 let permit = semaphore
315 .clone()
316 .acquire_owned()
317 .await
318 .expect("semaphore is never explicitly closed");
319 let cid = *cid;
320 let store = store.clone();
321
322 let handle = tokio::spawn(async move {
323 let _permit = permit;
324 (cid, store.has(&cid).await)
325 });
326
327 handles.push(handle);
328 }
329
330 for handle in handles.drain(..) {
332 match handle.await {
333 Ok((cid, Ok(exists))) => result.successful.push((cid, exists)),
334 Ok((cid, Err(e))) => {
335 result.failed.push(((cid, false), e.to_string()));
336 if config.fail_fast {
337 return result;
338 }
339 }
340 Err(e) => {
341 result
342 .failed
343 .push(((Cid::default(), false), format!("Task error: {e}")));
344 }
345 }
346 }
347 }
348
349 result
350}
351
352#[cfg(test)]
353mod tests {
354 use super::*;
355 use crate::MemoryBlockStore;
356 use bytes::Bytes;
357
358 #[tokio::test]
359 async fn test_batch_put() {
360 let store = Arc::new(MemoryBlockStore::new());
361 let mut blocks = Vec::new();
362
363 for i in 0..10 {
364 let data = format!("block {}", i);
365 let block = Block::new(Bytes::from(data)).unwrap();
366 blocks.push(block);
367 }
368
369 let config = BatchConfig::default();
370 let result = batch_put(store.clone(), blocks.clone(), config).await;
371
372 assert!(result.is_success());
373 assert_eq!(result.success_count(), 10);
374 assert_eq!(result.failure_count(), 0);
375 assert_eq!(result.success_rate(), 1.0);
376 }
377
378 #[tokio::test]
379 async fn test_batch_get() {
380 let store = Arc::new(MemoryBlockStore::new());
381 let mut blocks = Vec::new();
382 let mut cids = Vec::new();
383
384 for i in 0..5 {
385 let data = format!("block {}", i);
386 let block = Block::new(Bytes::from(data)).unwrap();
387 cids.push(*block.cid());
388 store.put(&block).await.unwrap();
389 blocks.push(block);
390 }
391
392 let config = BatchConfig::default();
393 let result = batch_get(store.clone(), cids, config).await;
394
395 assert!(result.is_success());
396 assert_eq!(result.success_count(), 5);
397 }
398
399 #[tokio::test]
400 async fn test_batch_has() {
401 let store = Arc::new(MemoryBlockStore::new());
402 let mut cids = Vec::new();
403
404 for i in 0..5 {
405 let data = format!("block {}", i);
406 let block = Block::new(Bytes::from(data)).unwrap();
407 cids.push(*block.cid());
408 store.put(&block).await.unwrap();
409 }
410
411 let config = BatchConfig::default();
412 let result = batch_has(store.clone(), cids, config).await;
413
414 assert!(result.is_success());
415 assert_eq!(result.success_count(), 5);
416
417 for (_, exists) in result.successful {
419 assert!(exists);
420 }
421 }
422
423 #[tokio::test]
424 async fn test_batch_delete() {
425 let store = Arc::new(MemoryBlockStore::new());
426 let mut cids = Vec::new();
427
428 for i in 0..5 {
429 let data = format!("block {}", i);
430 let block = Block::new(Bytes::from(data)).unwrap();
431 cids.push(*block.cid());
432 store.put(&block).await.unwrap();
433 }
434
435 let config = BatchConfig::default();
436 let result = batch_delete(store.clone(), cids.clone(), config).await;
437
438 assert!(result.is_success());
439 assert_eq!(result.success_count(), 5);
440
441 for cid in cids {
443 assert!(!store.has(&cid).await.unwrap());
444 }
445 }
446
447 #[test]
448 fn test_batch_config_presets() {
449 let high_throughput = BatchConfig::high_throughput();
450 assert_eq!(high_throughput.max_concurrency, 50);
451 assert_eq!(high_throughput.batch_size, 500);
452
453 let low_latency = BatchConfig::low_latency();
454 assert_eq!(low_latency.max_concurrency, 20);
455 assert_eq!(low_latency.batch_size, 50);
456
457 let conservative = BatchConfig::conservative();
458 assert_eq!(conservative.max_concurrency, 5);
459 assert_eq!(conservative.batch_size, 20);
460 }
461
462 #[test]
463 fn test_batch_result() {
464 let mut result = BatchResult::<i32>::new();
465 result.total = 10;
466 result.successful = vec![1, 2, 3, 4, 5];
467 result.failed = vec![(6, "error".to_string())];
468
469 assert!(!result.is_success());
470 assert_eq!(result.success_count(), 5);
471 assert_eq!(result.failure_count(), 1);
472 assert_eq!(result.success_rate(), 0.5);
473 }
474}