use std::{error::Error, thread::sleep};
use tracing::info;
use super::continuation_tracker::{ContinuationGrant, ContinuationTrackerTrait};
use crate::build::chunking::checkpoint::Progress;
pub fn process_while_resource_is_available<Action, ParamIter, Param, E>(
mut action: Action,
params: ParamIter,
continuation_checker: Box<dyn ContinuationTrackerTrait>,
) -> Result<Progress, E>
where
ParamIter: Iterator<Item = Param>,
Action: FnMut(Param) -> Result<(), E>,
E: Error,
{
for (idx, param) in params.enumerate() {
loop {
match continuation_checker.get_continuation_grant() {
ContinuationGrant::Continue => {
info!("Continue processing.");
action(param)?;
break;
}
ContinuationGrant::Yield(duration) => {
info!(
"Continuation checker asks to yield for {} ms.",
duration.as_millis()
);
sleep(duration);
}
ContinuationGrant::Stop => {
info!("Continuation checker asks to stop. Breaking the loop.");
return Ok(Progress::Processed(idx));
}
}
}
}
Ok(Progress::Completed)
}
pub async fn process_while_resource_is_available_async<Action, ParamIter, Param, Fut, E>(
mut action: Action,
params: ParamIter,
continuation_checker: Box<dyn ContinuationTrackerTrait>,
) -> Result<Progress, E>
where
ParamIter: Iterator<Item = Param>,
Action: FnMut(Param) -> Fut,
Fut: core::future::Future<Output = Result<(), E>>,
E: Error,
{
for (idx, param) in params.enumerate() {
loop {
match continuation_checker.get_continuation_grant() {
ContinuationGrant::Continue => {
info!("Continue processing.");
action(param).await?;
break;
}
ContinuationGrant::Yield(duration) => {
info!(
"Continuation checker asks to yield for {} ms.",
duration.as_millis()
);
sleep(duration);
}
ContinuationGrant::Stop => {
info!("Continuation checker asks to stop. Breaking the loop.");
return Ok(Progress::Processed(idx));
}
}
}
}
Ok(Progress::Completed)
}
#[cfg(test)]
mod tests {
use super::super::continuation_tracker::NaiveContinuationTracker;
use super::*;
use std::fmt;
#[derive(Debug)]
struct TestError;
impl fmt::Display for TestError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "TestError")
}
}
impl Error for TestError {}
#[test]
fn test_process_while_resource_is_available_completes() {
let checker = Box::new(NaiveContinuationTracker::default());
let items = vec![1, 2, 3, 4, 5];
let mut processed = Vec::new();
let result = process_while_resource_is_available(
|item| {
processed.push(item);
Ok::<(), TestError>(())
},
items.into_iter(),
checker,
);
assert!(matches!(result.unwrap(), Progress::Completed));
assert_eq!(processed, vec![1, 2, 3, 4, 5]);
}
#[test]
fn test_process_while_resource_is_available_empty_iter() {
let checker = Box::new(NaiveContinuationTracker::default());
let items: Vec<i32> = vec![];
let result = process_while_resource_is_available(
|_item| Ok::<(), TestError>(()),
items.into_iter(),
checker,
);
assert!(matches!(result.unwrap(), Progress::Completed));
}
#[derive(Clone)]
struct StopAfterTracker {
count: std::sync::Arc<std::sync::Mutex<usize>>,
stop_after: usize,
}
impl ContinuationTrackerTrait for StopAfterTracker {
fn get_continuation_grant(&self) -> ContinuationGrant {
let mut count = self.count.lock().unwrap();
if *count >= self.stop_after {
ContinuationGrant::Stop
} else {
*count += 1;
ContinuationGrant::Continue
}
}
}
#[test]
fn test_process_while_resource_is_available_stops_early() {
let tracker = StopAfterTracker {
count: std::sync::Arc::new(std::sync::Mutex::new(0)),
stop_after: 3,
};
let items = vec![10, 20, 30, 40, 50];
let mut processed = Vec::new();
let result = process_while_resource_is_available(
|item| {
processed.push(item);
Ok::<(), TestError>(())
},
items.into_iter(),
Box::new(tracker),
);
assert!(matches!(
result.unwrap(),
Progress::Processed(processed_count) if processed_count == 3
));
assert_eq!(processed, vec![10, 20, 30]);
}
#[derive(Clone)]
struct YieldOnceThenContinueTracker {
yielded: std::sync::Arc<std::sync::Mutex<bool>>,
}
impl ContinuationTrackerTrait for YieldOnceThenContinueTracker {
fn get_continuation_grant(&self) -> ContinuationGrant {
let mut yielded = self.yielded.lock().unwrap();
if !*yielded {
*yielded = true;
ContinuationGrant::Yield(std::time::Duration::ZERO)
} else {
ContinuationGrant::Continue
}
}
}
#[test]
fn test_process_while_resource_is_available_yield_then_continue() {
let tracker = YieldOnceThenContinueTracker {
yielded: std::sync::Arc::new(std::sync::Mutex::new(false)),
};
let items = vec![1, 2];
let mut processed = Vec::new();
let result = process_while_resource_is_available(
|item| {
processed.push(item);
Ok::<(), TestError>(())
},
items.into_iter(),
Box::new(tracker),
);
assert!(matches!(result.unwrap(), Progress::Completed));
assert_eq!(processed, vec![1, 2]);
}
#[test]
fn test_process_while_resource_is_available_action_error() {
let checker = Box::new(NaiveContinuationTracker::default());
let items = vec![1, 2, 3];
let result = process_while_resource_is_available(
|item| {
if item == 2 {
Err(TestError)
} else {
Ok(())
}
},
items.into_iter(),
checker,
);
assert!(result.is_err());
}
#[tokio::test]
async fn test_process_while_resource_is_available_async_stops_early() {
let tracker = StopAfterTracker {
count: std::sync::Arc::new(std::sync::Mutex::new(0)),
stop_after: 2,
};
let items = vec![1, 2, 3, 4, 5];
let processed = std::sync::Arc::new(tokio::sync::Mutex::new(Vec::new()));
let result = process_while_resource_is_available_async(
|item| {
let processed = processed.clone();
async move {
processed.lock().await.push(item);
Ok::<(), TestError>(())
}
},
items.into_iter(),
Box::new(tracker),
)
.await;
assert!(matches!(
result.unwrap(),
Progress::Processed(processed_count) if processed_count == 2
));
let processed = processed.lock().await;
assert_eq!(*processed, vec![1, 2]);
}
#[tokio::test]
async fn test_process_while_resource_is_available_async_completes() {
let checker = Box::new(NaiveContinuationTracker::default());
let items = vec![1, 2, 3];
let processed = std::sync::Arc::new(tokio::sync::Mutex::new(Vec::new()));
let result = process_while_resource_is_available_async(
|item| {
let processed = processed.clone();
async move {
processed.lock().await.push(item);
Ok::<(), TestError>(())
}
},
items.into_iter(),
checker,
)
.await;
assert!(matches!(result.unwrap(), Progress::Completed));
let processed = processed.lock().await;
assert_eq!(*processed, vec![1, 2, 3]);
}
}