use std::future::Future;
use std::time::Duration;
use crate::ClientError;
use crate::ClientResult;
use tokio::time::timeout;
pub async fn with_timeout<F, T>(duration: Duration, future: F) -> ClientResult<T>
where
F: Future<Output = ClientResult<T>>,
{
timeout(duration, future)
.await
.map_err(|_| ClientError::timeout("async operation", duration.as_millis() as u64))?
}
pub async fn with_timeout_named<F, T>(operation: &'static str, duration: Duration, future: F) -> ClientResult<T>
where
F: Future<Output = ClientResult<T>>,
{
timeout(duration, future)
.await
.map_err(|_| ClientError::timeout(operation, duration.as_millis() as u64))?
}
pub async fn with_timeout_millis<F, T>(operation: &'static str, timeout_ms: u64, future: F) -> ClientResult<T>
where
F: Future<Output = ClientResult<T>>,
{
with_timeout_named(operation, Duration::from_millis(timeout_ms), future).await
}
pub async fn with_timeout_all<F, T>(duration: Duration, futures: Vec<F>) -> ClientResult<Vec<T>>
where
F: Future<Output = ClientResult<T>> + Send + 'static,
T: Send + 'static,
{
let joined = async move {
let mut results = Vec::with_capacity(futures.len());
for future in futures {
results.push(future.await?);
}
ClientResult::Ok(results)
};
with_timeout(duration, joined).await
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[tokio::test]
async fn test_with_timeout_success() {
async fn quick_operation() -> ClientResult<String> {
tokio::time::sleep(Duration::from_millis(10)).await;
Ok("Success".to_string())
}
let result = with_timeout(Duration::from_millis(100), quick_operation()).await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), "Success");
}
#[tokio::test]
async fn test_with_timeout_failure() {
async fn slow_operation() -> ClientResult<String> {
tokio::time::sleep(Duration::from_millis(200)).await;
Ok("Success".to_string())
}
let result = with_timeout(Duration::from_millis(50), slow_operation()).await;
assert!(result.is_err());
assert!(result.unwrap_err().is(&rocketmq_error::CORE_OPERATION_TIMED_OUT));
}
#[tokio::test]
async fn test_with_timeout_named() {
async fn slow_operation() -> ClientResult<String> {
tokio::time::sleep(Duration::from_millis(200)).await;
Ok("Success".to_string())
}
let result = with_timeout_named("test_operation", Duration::from_millis(50), slow_operation()).await;
assert!(result.is_err());
assert!(result.unwrap_err().is(&rocketmq_error::CORE_OPERATION_TIMED_OUT));
}
#[tokio::test]
async fn test_with_timeout_millis() {
async fn quick_operation() -> ClientResult<String> {
tokio::time::sleep(Duration::from_millis(10)).await;
Ok("Success".to_string())
}
let result = with_timeout_millis("test", 100, quick_operation()).await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), "Success");
}
#[tokio::test]
async fn test_with_timeout_all_success() {
async fn operation(id: usize) -> ClientResult<usize> {
tokio::time::sleep(Duration::from_millis(10)).await;
Ok(id)
}
let futures = vec![operation(1), operation(2), operation(3)];
let result = with_timeout_all(Duration::from_millis(100), futures).await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), vec![1, 2, 3]);
}
#[tokio::test]
async fn test_with_timeout_all_timeout() {
async fn slow_operation(id: usize) -> ClientResult<usize> {
tokio::time::sleep(Duration::from_millis(100)).await;
Ok(id)
}
let futures = vec![slow_operation(1), slow_operation(2), slow_operation(3)];
let result = with_timeout_all(Duration::from_millis(50), futures).await;
assert!(result.is_err());
assert!(result.unwrap_err().is(&rocketmq_error::CORE_OPERATION_TIMED_OUT));
}
#[tokio::test]
async fn test_with_timeout_propagates_error() {
async fn failing_operation() -> ClientResult<String> {
Err(ClientError::invalid_state(
"ready test operation",
"failed test operation",
))
}
let result = with_timeout(Duration::from_millis(100), failing_operation()).await;
assert!(result.is_err());
assert!(
result.unwrap_err().is(&rocketmq_error::CLIENT_LIFECYCLE_INVALID_STATE),
"expected client invalid state error"
);
}
}