1use parking_lot::Mutex;
20use std::ops::Range;
21use std::{convert::TryInto, sync::Arc};
22
23use crate::multipart::{MultipartStore, PartId};
24use crate::{CopyOptions, GetOptions, RenameOptions, UploadPart};
25use crate::{
26 GetResult, GetResultPayload, ListResult, MultipartId, MultipartUpload, ObjectMeta, ObjectStore,
27 PutMultipartOptions, PutOptions, PutPayload, PutResult, Result, path::Path,
28};
29use async_trait::async_trait;
30use bytes::Bytes;
31use futures_util::{FutureExt, StreamExt, stream::BoxStream};
32use std::time::Duration;
33
34#[derive(Debug, Default, Clone, Copy)]
36pub struct ThrottleConfig {
37 pub wait_delete_per_call: Duration,
45
46 pub wait_get_per_byte: Duration,
55
56 pub wait_get_per_call: Duration,
62
63 pub wait_list_per_call: Duration,
69
70 pub wait_list_per_entry: Duration,
79
80 pub wait_list_with_delimiter_per_call: Duration,
87
88 pub wait_list_with_delimiter_per_entry: Duration,
95
96 pub wait_put_per_call: Duration,
101}
102
103async fn sleep(duration: Duration) {
105 if !duration.is_zero() {
106 tokio::time::sleep(duration).await
107 }
108}
109
110#[derive(Debug)]
117pub struct ThrottledStore<T> {
118 inner: T,
119 config: Arc<Mutex<ThrottleConfig>>,
120}
121
122impl<T> ThrottledStore<T> {
123 pub fn new(inner: T, config: ThrottleConfig) -> Self {
125 Self {
126 inner,
127 config: Arc::new(Mutex::new(config)),
128 }
129 }
130
131 pub fn config_mut<F>(&self, f: F)
133 where
134 F: Fn(&mut ThrottleConfig),
135 {
136 let mut guard = self.config.lock();
137 f(&mut guard)
138 }
139
140 pub fn config(&self) -> ThrottleConfig {
142 *self.config.lock()
143 }
144}
145
146impl<T: ObjectStore> std::fmt::Display for ThrottledStore<T> {
147 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148 write!(f, "ThrottledStore({})", self.inner)
149 }
150}
151
152#[async_trait]
153#[deny(clippy::missing_trait_methods)]
154impl<T: ObjectStore> ObjectStore for ThrottledStore<T> {
155 async fn put_opts(
156 &self,
157 location: &Path,
158 payload: PutPayload,
159 opts: PutOptions,
160 ) -> Result<PutResult> {
161 sleep(self.config().wait_put_per_call).await;
162 self.inner.put_opts(location, payload, opts).await
163 }
164
165 async fn put_multipart_opts(
166 &self,
167 location: &Path,
168 opts: PutMultipartOptions,
169 ) -> Result<Box<dyn MultipartUpload>> {
170 let upload = self.inner.put_multipart_opts(location, opts).await?;
171 Ok(Box::new(ThrottledUpload {
172 upload,
173 sleep: self.config().wait_put_per_call,
174 }))
175 }
176
177 async fn get_opts(&self, location: &Path, options: GetOptions) -> Result<GetResult> {
178 sleep(self.config().wait_get_per_call).await;
179
180 let wait_get_per_byte = self.config().wait_get_per_byte;
182
183 let result = self.inner.get_opts(location, options).await?;
184 Ok(throttle_get(result, wait_get_per_byte))
185 }
186
187 async fn get_ranges(&self, location: &Path, ranges: &[Range<u64>]) -> Result<Vec<Bytes>> {
188 let config = self.config();
189
190 let total_bytes: u64 = ranges.iter().map(|range| range.end - range.start).sum();
191 let sleep_duration =
192 config.wait_get_per_call + config.wait_get_per_byte * total_bytes as u32;
193
194 sleep(sleep_duration).await;
195
196 self.inner.get_ranges(location, ranges).await
197 }
198
199 fn delete_stream(
200 &self,
201 locations: BoxStream<'static, Result<Path>>,
202 ) -> BoxStream<'static, Result<Path>> {
203 let wait_delete_per_call = self.config().wait_delete_per_call;
208 let locations = throttle_stream(locations, move |_| wait_delete_per_call);
209 self.inner.delete_stream(locations)
210 }
211
212 fn list(&self, prefix: Option<&Path>) -> BoxStream<'static, Result<ObjectMeta>> {
213 let stream = self.inner.list(prefix);
214 let config = Arc::clone(&self.config);
215 futures_util::stream::once(async move {
216 let config = *config.lock();
217 let wait_list_per_entry = config.wait_list_per_entry;
218 sleep(config.wait_list_per_call).await;
219 throttle_stream(stream, move |_| wait_list_per_entry)
220 })
221 .flatten()
222 .boxed()
223 }
224
225 fn list_with_offset(
226 &self,
227 prefix: Option<&Path>,
228 offset: &Path,
229 ) -> BoxStream<'static, Result<ObjectMeta>> {
230 let stream = self.inner.list_with_offset(prefix, offset);
231 let config = Arc::clone(&self.config);
232 futures_util::stream::once(async move {
233 let config = *config.lock();
234 let wait_list_per_entry = config.wait_list_per_entry;
235 sleep(config.wait_list_per_call).await;
236 throttle_stream(stream, move |_| wait_list_per_entry)
237 })
238 .flatten()
239 .boxed()
240 }
241
242 async fn list_with_delimiter(&self, prefix: Option<&Path>) -> Result<ListResult> {
243 sleep(self.config().wait_list_with_delimiter_per_call).await;
244
245 match self.inner.list_with_delimiter(prefix).await {
246 Ok(list_result) => {
247 let entries_len = usize_to_u32_saturate(list_result.objects.len());
248 sleep(self.config().wait_list_with_delimiter_per_entry * entries_len).await;
249 Ok(list_result)
250 }
251 Err(err) => Err(err),
252 }
253 }
254
255 async fn copy_opts(&self, from: &Path, to: &Path, options: CopyOptions) -> Result<()> {
256 sleep(self.config().wait_put_per_call).await;
257
258 self.inner.copy_opts(from, to, options).await
259 }
260
261 async fn rename_opts(&self, from: &Path, to: &Path, options: RenameOptions) -> Result<()> {
262 sleep(self.config().wait_put_per_call).await;
263
264 self.inner.rename_opts(from, to, options).await
265 }
266}
267
268fn usize_to_u32_saturate(x: usize) -> u32 {
270 x.try_into().unwrap_or(u32::MAX)
271}
272
273fn throttle_get(result: GetResult, wait_get_per_byte: Duration) -> GetResult {
274 #[allow(clippy::infallible_destructuring_match)]
275 let s = match result.payload {
276 GetResultPayload::Stream(s) => s,
277 #[cfg(all(feature = "fs", not(target_arch = "wasm32")))]
278 GetResultPayload::File(_, _) => unimplemented!(),
279 };
280
281 let stream = throttle_stream(s, move |bytes| {
282 let bytes_len: u32 = usize_to_u32_saturate(bytes.len());
283 wait_get_per_byte * bytes_len
284 });
285
286 GetResult {
287 payload: GetResultPayload::Stream(stream),
288 ..result
289 }
290}
291
292fn throttle_stream<T: Send + 'static, E: Send + 'static, F>(
293 stream: BoxStream<'_, Result<T, E>>,
294 delay: F,
295) -> BoxStream<'_, Result<T, E>>
296where
297 F: Fn(&T) -> Duration + Send + Sync + 'static,
298{
299 stream
300 .then(move |result| {
301 let delay = result.as_ref().ok().map(&delay).unwrap_or_default();
302 sleep(delay).then(|_| futures_util::future::ready(result))
303 })
304 .boxed()
305}
306
307#[async_trait]
308impl<T: MultipartStore> MultipartStore for ThrottledStore<T> {
309 async fn create_multipart(&self, path: &Path) -> Result<MultipartId> {
310 self.inner.create_multipart(path).await
311 }
312
313 async fn create_multipart_opts(
314 &self,
315 path: &Path,
316 opts: PutMultipartOptions,
317 ) -> Result<MultipartId> {
318 self.inner.create_multipart_opts(path, opts).await
319 }
320
321 async fn put_part(
322 &self,
323 path: &Path,
324 id: &MultipartId,
325 part_idx: usize,
326 data: PutPayload,
327 ) -> Result<PartId> {
328 sleep(self.config().wait_put_per_call).await;
329 self.inner.put_part(path, id, part_idx, data).await
330 }
331
332 async fn complete_multipart(
333 &self,
334 path: &Path,
335 id: &MultipartId,
336 parts: Vec<PartId>,
337 ) -> Result<PutResult> {
338 self.inner.complete_multipart(path, id, parts).await
339 }
340
341 async fn abort_multipart(&self, path: &Path, id: &MultipartId) -> Result<()> {
342 self.inner.abort_multipart(path, id).await
343 }
344}
345
346#[derive(Debug)]
347struct ThrottledUpload {
348 upload: Box<dyn MultipartUpload>,
349 sleep: Duration,
350}
351
352#[async_trait]
353impl MultipartUpload for ThrottledUpload {
354 fn put_part(&mut self, data: PutPayload) -> UploadPart {
355 let duration = self.sleep;
356 let put = self.upload.put_part(data);
357 Box::pin(async move {
358 sleep(duration).await;
359 put.await
360 })
361 }
362
363 async fn complete(&mut self) -> Result<PutResult> {
364 self.upload.complete().await
365 }
366
367 async fn abort(&mut self) -> Result<()> {
368 self.upload.abort().await
369 }
370}
371
372#[cfg(test)]
373mod tests {
374 use super::*;
375 #[cfg(target_os = "linux")]
376 use crate::GetResultPayload;
377 use crate::ObjectStoreExt;
378 use crate::{integration::*, memory::InMemory};
379 use futures_util::TryStreamExt;
380 use tokio::time::Duration;
381 use tokio::time::Instant;
382
383 const WAIT_TIME: Duration = Duration::from_millis(100);
384 const ZERO: Duration = Duration::from_millis(0); macro_rules! assert_bounds {
387 ($d:expr, $lower:expr) => {
388 assert_bounds!($d, $lower, $lower + 2);
389 };
390 ($d:expr, $lower:expr, $upper:expr) => {
391 let d = $d;
392 let lower = $lower * WAIT_TIME;
393 let upper = $upper * WAIT_TIME;
394 assert!(d >= lower, "{:?} must be >= than {:?}", d, lower);
395 assert!(d < upper, "{:?} must be < than {:?}", d, upper);
396 };
397 }
398
399 #[tokio::test]
400 async fn throttle_test() {
401 let inner = InMemory::new();
402 let store = ThrottledStore::new(inner, ThrottleConfig::default());
403
404 put_get_delete_list(&store).await;
405 list_uses_directories_correctly(&store).await;
406 list_with_delimiter(&store).await;
407 rename_and_copy(&store).await;
408 copy_if_not_exists(&store).await;
409 stream_get(&store).await;
410 multipart(&store, &store).await;
411 multipart_with_opts(&store, &store).await;
412 multipart_put_part_out_of_order(&store, &store).await;
413 }
414
415 #[tokio::test]
416 async fn delete_test() {
417 let inner = InMemory::new();
418 let store = ThrottledStore::new(inner, ThrottleConfig::default());
419
420 assert_bounds!(measure_delete(&store, None).await, 0);
421 assert_bounds!(measure_delete(&store, Some(0)).await, 0);
422 assert_bounds!(measure_delete(&store, Some(10)).await, 0);
423
424 store.config_mut(|cfg| cfg.wait_delete_per_call = WAIT_TIME);
425 assert_bounds!(measure_delete(&store, None).await, 1);
426 assert_bounds!(measure_delete(&store, Some(0)).await, 1);
427 assert_bounds!(measure_delete(&store, Some(10)).await, 1);
428 }
429
430 #[tokio::test]
431 #[cfg(target_os = "linux")]
433 async fn delete_stream_test() {
434 let inner = InMemory::new();
435 let store = ThrottledStore::new(inner, ThrottleConfig::default());
436
437 assert_bounds!(measure_delete_stream(&store, 0).await, 0);
438 assert_bounds!(measure_delete_stream(&store, 10).await, 0);
439
440 store.config_mut(|cfg| cfg.wait_delete_per_call = WAIT_TIME);
441 assert_bounds!(measure_delete_stream(&store, 0).await, 0);
442 assert_bounds!(measure_delete_stream(&store, 10).await, 10);
443 }
444
445 #[tokio::test]
446 #[cfg(target_os = "linux")]
448 async fn get_test() {
449 let inner = InMemory::new();
450 let store = ThrottledStore::new(inner, ThrottleConfig::default());
451
452 assert_bounds!(measure_get(&store, None).await, 0);
453 assert_bounds!(measure_get(&store, Some(0)).await, 0);
454 assert_bounds!(measure_get(&store, Some(10)).await, 0);
455
456 store.config_mut(|cfg| cfg.wait_get_per_call = WAIT_TIME);
457 assert_bounds!(measure_get(&store, None).await, 1);
458 assert_bounds!(measure_get(&store, Some(0)).await, 1);
459 assert_bounds!(measure_get(&store, Some(10)).await, 1);
460
461 store.config_mut(|cfg| {
462 cfg.wait_get_per_call = ZERO;
463 cfg.wait_get_per_byte = WAIT_TIME;
464 });
465 assert_bounds!(measure_get(&store, Some(2)).await, 2);
466
467 store.config_mut(|cfg| {
468 cfg.wait_get_per_call = WAIT_TIME;
469 cfg.wait_get_per_byte = WAIT_TIME;
470 });
471 assert_bounds!(measure_get(&store, Some(2)).await, 3);
472 }
473
474 #[tokio::test]
475 #[cfg(target_os = "linux")]
477 async fn list_test() {
478 let inner = InMemory::new();
479 let store = ThrottledStore::new(inner, ThrottleConfig::default());
480
481 assert_bounds!(measure_list(&store, 0).await, 0);
482 assert_bounds!(measure_list(&store, 10).await, 0);
483
484 store.config_mut(|cfg| cfg.wait_list_per_call = WAIT_TIME);
485 assert_bounds!(measure_list(&store, 0).await, 1);
486 assert_bounds!(measure_list(&store, 10).await, 1);
487
488 store.config_mut(|cfg| {
489 cfg.wait_list_per_call = ZERO;
490 cfg.wait_list_per_entry = WAIT_TIME;
491 });
492 assert_bounds!(measure_list(&store, 2).await, 2);
493
494 store.config_mut(|cfg| {
495 cfg.wait_list_per_call = WAIT_TIME;
496 cfg.wait_list_per_entry = WAIT_TIME;
497 });
498 assert_bounds!(measure_list(&store, 2).await, 3);
499 }
500
501 #[tokio::test]
502 #[cfg(target_os = "linux")]
504 async fn list_with_delimiter_test() {
505 let inner = InMemory::new();
506 let store = ThrottledStore::new(inner, ThrottleConfig::default());
507
508 assert_bounds!(measure_list_with_delimiter(&store, 0).await, 0);
509 assert_bounds!(measure_list_with_delimiter(&store, 10).await, 0);
510
511 store.config_mut(|cfg| cfg.wait_list_with_delimiter_per_call = WAIT_TIME);
512 assert_bounds!(measure_list_with_delimiter(&store, 0).await, 1);
513 assert_bounds!(measure_list_with_delimiter(&store, 10).await, 1);
514
515 store.config_mut(|cfg| {
516 cfg.wait_list_with_delimiter_per_call = ZERO;
517 cfg.wait_list_with_delimiter_per_entry = WAIT_TIME;
518 });
519 assert_bounds!(measure_list_with_delimiter(&store, 2).await, 2);
520
521 store.config_mut(|cfg| {
522 cfg.wait_list_with_delimiter_per_call = WAIT_TIME;
523 cfg.wait_list_with_delimiter_per_entry = WAIT_TIME;
524 });
525 assert_bounds!(measure_list_with_delimiter(&store, 2).await, 3);
526 }
527
528 #[tokio::test]
529 async fn put_test() {
530 let inner = InMemory::new();
531 let store = ThrottledStore::new(inner, ThrottleConfig::default());
532
533 assert_bounds!(measure_put(&store, 0).await, 0);
534 assert_bounds!(measure_put(&store, 10).await, 0);
535
536 store.config_mut(|cfg| cfg.wait_put_per_call = WAIT_TIME);
537 assert_bounds!(measure_put(&store, 0).await, 1);
538 assert_bounds!(measure_put(&store, 10).await, 1);
539
540 store.config_mut(|cfg| cfg.wait_put_per_call = ZERO);
541 assert_bounds!(measure_put(&store, 0).await, 0);
542 }
543
544 async fn place_test_object(store: &ThrottledStore<InMemory>, n_bytes: Option<usize>) -> Path {
545 let path = Path::from("foo");
546
547 if let Some(n_bytes) = n_bytes {
548 let data: Vec<_> = std::iter::repeat_n(1u8, n_bytes).collect();
549 store.put(&path, data.into()).await.unwrap();
550 } else {
551 store.delete(&path).await.unwrap();
553 }
554
555 path
556 }
557
558 #[allow(dead_code)]
559 async fn place_test_objects(store: &ThrottledStore<InMemory>, n_entries: usize) -> Path {
560 let prefix = Path::from("foo");
561
562 let entries: Vec<_> = store.list(Some(&prefix)).try_collect().await.unwrap();
564
565 for entry in entries {
566 store.delete(&entry.location).await.unwrap();
567 }
568
569 for i in 0..n_entries {
571 let path = prefix.clone().join(i.to_string().as_str());
572 store.put(&path, "bar".into()).await.unwrap();
573 }
574
575 prefix
576 }
577
578 async fn measure_delete(store: &ThrottledStore<InMemory>, n_bytes: Option<usize>) -> Duration {
579 let path = place_test_object(store, n_bytes).await;
580
581 let t0 = Instant::now();
582 store.delete(&path).await.unwrap();
583
584 t0.elapsed()
585 }
586
587 #[allow(dead_code)]
588 async fn measure_delete_stream(store: &ThrottledStore<InMemory>, n_entries: usize) -> Duration {
589 let prefix = place_test_objects(store, n_entries).await;
590
591 let paths = store.list(Some(&prefix)).collect::<Vec<_>>().await;
593 let paths = futures_util::stream::iter(paths)
594 .map(|x| x.map(|m| m.location))
595 .boxed();
596
597 let t0 = Instant::now();
598 store
599 .delete_stream(paths)
600 .try_collect::<Vec<_>>()
601 .await
602 .unwrap();
603
604 t0.elapsed()
605 }
606
607 #[allow(dead_code)]
608 #[cfg(target_os = "linux")]
609 async fn measure_get(store: &ThrottledStore<InMemory>, n_bytes: Option<usize>) -> Duration {
610 let path = place_test_object(store, n_bytes).await;
611
612 let t0 = Instant::now();
613 let res = store.get(&path).await;
614 if n_bytes.is_some() {
615 let s = match res.unwrap().payload {
617 GetResultPayload::Stream(s) => s,
618 GetResultPayload::File(_, _) => unimplemented!(),
619 };
620
621 s.map_ok(|b| bytes::BytesMut::from(&b[..]))
622 .try_concat()
623 .await
624 .unwrap();
625 } else {
626 assert!(res.is_err());
627 }
628
629 t0.elapsed()
630 }
631
632 #[allow(dead_code)]
633 async fn measure_list(store: &ThrottledStore<InMemory>, n_entries: usize) -> Duration {
634 let prefix = place_test_objects(store, n_entries).await;
635
636 let t0 = Instant::now();
637 store
638 .list(Some(&prefix))
639 .try_collect::<Vec<_>>()
640 .await
641 .unwrap();
642
643 t0.elapsed()
644 }
645
646 #[allow(dead_code)]
647 async fn measure_list_with_delimiter(
648 store: &ThrottledStore<InMemory>,
649 n_entries: usize,
650 ) -> Duration {
651 let prefix = place_test_objects(store, n_entries).await;
652
653 let t0 = Instant::now();
654 store.list_with_delimiter(Some(&prefix)).await.unwrap();
655
656 t0.elapsed()
657 }
658
659 async fn measure_put(store: &ThrottledStore<InMemory>, n_bytes: usize) -> Duration {
660 let data: Vec<_> = std::iter::repeat_n(1u8, n_bytes).collect();
661
662 let t0 = Instant::now();
663 store.put(&Path::from("foo"), data.into()).await.unwrap();
664
665 t0.elapsed()
666 }
667}