use crate::batch::BatchOperation;
use crate::cluster::{Cluster, Node};
use crate::commands::BatchOperateCommand;
use crate::errors::Result;
use crate::policy::{BatchPolicy, Concurrency};
use crate::Error;
use crate::{BatchRecord, Policy, ResultCode};
use aerospike_rt::time::Duration;
use std::sync::Arc;
pub struct BatchExecutor {
cluster: Arc<Cluster>,
}
type IndexedOp = (BatchOperation, usize);
type NodeGroup = (Arc<Node>, Vec<IndexedOp>);
struct BatchSplit {
groups: Vec<NodeGroup>,
unroutable: Vec<IndexedOp>,
}
impl BatchExecutor {
pub const fn new(cluster: Arc<Cluster>) -> Self {
BatchExecutor { cluster }
}
#[allow(clippy::option_if_let_else)]
pub async fn execute(
&self,
policy: &BatchPolicy,
batch_ops: &[BatchOperation],
) -> Result<Vec<BatchRecord>> {
if policy.total_timeout() > 0 {
match aerospike_rt::timeout(
Duration::from_millis(u64::from(policy.total_timeout())),
self.execute_batch_operate(policy, batch_ops),
)
.await
{
Ok(res) => res,
Err(_) => Err(Error::Timeout("Timeout".to_string())),
}
} else {
self.execute_batch_operate(policy, batch_ops).await
}
}
pub async fn execute_batch_operate(
&self,
policy: &BatchPolicy,
batch_ops: &[BatchOperation],
) -> Result<Vec<BatchRecord>> {
let BatchSplit {
groups: batch_nodes,
unroutable,
} = self.get_batch_operate_nodes(batch_ops, policy.replica)?;
let jobs = batch_nodes
.into_iter()
.map(|(node, ops)| BatchOperateCommand::new(policy.clone(), node, ops))
.collect();
let ops = self
.execute_batch_operate_jobs(jobs, policy.concurrency)
.await?;
let mut slots: Vec<Option<BatchRecord>> = (0..batch_ops.len()).map(|_| None).collect();
let mut place = |(op, index): (BatchOperation, usize)| {
if let Some(slot) = slots.get_mut(index) {
*slot = Some(op.into_batch_record());
}
};
for cmd in ops {
cmd.batch_ops.into_iter().for_each(&mut place);
}
unroutable.into_iter().for_each(&mut place);
debug_assert!(
slots.iter().all(Option::is_some),
"every batch index must be filled exactly once"
);
Ok(slots.into_iter().flatten().collect())
}
async fn execute_batch_operate_jobs(
&self,
jobs: Vec<BatchOperateCommand>,
concurrency: Concurrency,
) -> Result<Vec<BatchOperateCommand>> {
let handles = jobs
.into_iter()
.map(|job| job.execute(self.cluster.clone()));
match concurrency {
Concurrency::Sequential => futures::future::join_all(handles)
.await
.into_iter()
.collect(),
#[cfg(feature = "rt-async-std")]
Concurrency::Parallel => futures::future::join_all(handles)
.await
.into_iter()
.map(|value| value.map_err(|e| Error::ClientError(e.to_string())))
.collect(),
#[cfg(feature = "rt-tokio")]
Concurrency::Parallel => futures::future::join_all(handles.map(aerospike_rt::spawn))
.await
.into_iter()
.map(|value| value.map_err(|e| Error::ClientError(e.to_string()))?)
.collect(),
}
}
fn get_batch_operate_nodes(
&self,
batch_ops: &[BatchOperation],
replica: crate::policy::Replica,
) -> Result<BatchSplit> {
let mut routed: Vec<Result<Arc<Node>>> = Vec::with_capacity(batch_ops.len());
self.cluster.route_keys(
batch_ops.iter().map(|op| (op.key(), None)),
replica,
|node| routed.push(node),
);
let mut counts: Vec<(Arc<Node>, usize)> = Vec::new();
let mut unroutable_count = 0;
for node in &routed {
match node {
Ok(node) => match counts
.iter_mut()
.find(|(existing, _)| Arc::ptr_eq(existing, node))
{
Some((_, count)) => *count += 1,
None => counts.push((node.clone(), 1)),
},
Err(_) => unroutable_count += 1,
}
}
let mut groups: Vec<NodeGroup> = counts
.into_iter()
.map(|(node, count)| (node, Vec::with_capacity(count)))
.collect();
let mut unroutable: Vec<IndexedOp> = Vec::with_capacity(unroutable_count);
let mut first_err: Option<Error> = None;
for (index, (batch_op, node)) in batch_ops.iter().zip(routed).enumerate() {
match node {
Ok(node) => {
let bucket = groups
.iter_mut()
.find(|(existing, _)| Arc::ptr_eq(existing, &node))
.map(|(_, bucket)| bucket)
.expect("counting pass registered every routable node");
bucket.push((batch_op.clone(), index));
}
Err(err) => {
let mut op = batch_op.clone();
op.set_result_code(ResultCode::PartitionUnavailable, false);
unroutable.push((op, index));
first_err.get_or_insert(err);
}
}
}
if groups.is_empty() {
if let Some(err) = first_err {
return Err(err);
}
}
Ok(BatchSplit { groups, unroutable })
}
}