use std::collections::{HashMap, HashSet};
use serde::{Deserialize, Serialize};
use zenoh::{
bytes::{Encoding, ZBytes},
key_expr::keyexpr_tree::IKeyExprTree,
query::Query,
};
use super::aligner_reply::AlignmentReply;
use crate::replication::{
classification::{IntervalIdx, SubIntervalIdx},
core::Replication,
digest::DigestDiff,
log::{Action, EventMetadata},
};
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
pub(crate) enum AlignmentQuery {
Discovery,
All,
Diff(DigestDiff),
Intervals(HashSet<IntervalIdx>),
SubIntervals(HashMap<IntervalIdx, HashSet<SubIntervalIdx>>),
Events(Vec<EventMetadata>),
}
impl Replication {
pub(crate) async fn aligner(&self, query: Query) {
let attachment = match query.attachment() {
Some(attachment) => attachment,
None => {
tracing::debug!("Skipping query with empty attachment");
return;
}
};
let alignment_query = match bincode::deserialize::<AlignmentQuery>(&attachment.to_bytes()) {
Ok(alignment) => alignment,
Err(e) => {
tracing::error!(
"Failed to deserialize `attachment` of received Query into AlignmentQuery: \
{e:?}"
);
return;
}
};
match alignment_query {
AlignmentQuery::Discovery => {
tracing::trace!("Processing `AlignmentQuery::Discovery`");
reply_to_query(
&query,
AlignmentReply::Discovery(self.zenoh_session.zid()),
None,
)
.await;
}
AlignmentQuery::All => {
tracing::trace!("Processing `AlignmentQuery::All`");
let idx_intervals = self
.replication_log
.read()
.await
.intervals
.keys()
.copied()
.collect::<Vec<_>>();
for interval_idx in idx_intervals {
let mut events_to_retrieve = Vec::default();
if let Some(interval) = self
.replication_log
.read()
.await
.intervals
.get(&interval_idx)
{
interval.sub_intervals().for_each(|(_, sub_interval)| {
events_to_retrieve.extend(sub_interval.events().map(Into::into));
});
}
for event_to_retrieve in events_to_retrieve {
self.reply_event_retrieval(&query, event_to_retrieve).await;
}
}
}
AlignmentQuery::Diff(digest_diff) => {
tracing::trace!("Processing `AlignmentQuery::Diff`");
if digest_diff.cold_eras_differ {
self.reply_cold_era(&query).await;
}
if !digest_diff.warm_eras_differences.is_empty() {
self.reply_sub_intervals(&query, digest_diff.warm_eras_differences)
.await;
}
if !digest_diff.hot_eras_differences.is_empty() {
self.reply_events_metadata(&query, digest_diff.hot_eras_differences)
.await;
}
}
AlignmentQuery::Intervals(different_intervals) => {
tracing::trace!("Processing `AlignmentQuery::Intervals`");
if !different_intervals.is_empty() {
self.reply_sub_intervals(&query, different_intervals).await;
}
}
AlignmentQuery::SubIntervals(different_sub_intervals) => {
tracing::trace!("Processing `AlignmentQuery::SubIntervals`");
if !different_sub_intervals.is_empty() {
self.reply_events_metadata(&query, different_sub_intervals)
.await;
}
}
AlignmentQuery::Events(events_to_retrieve) => {
tracing::trace!("Processing `AlignmentQuery::Events`");
for event_to_retrieve in events_to_retrieve {
self.reply_event_retrieval(&query, event_to_retrieve).await;
}
}
}
}
pub(crate) async fn reply_cold_era(&self, query: &Query) {
let log = self.replication_log.read().await;
let configuration = log.configuration();
let last_elapsed_interval = match configuration.last_elapsed_interval() {
Ok(last_elapsed_idx) => last_elapsed_idx,
Err(e) => {
tracing::error!(
"Fatal error: failed to obtain the index of the last elapsed interval: {e:?}"
);
return;
}
};
let warm_era_lower_bound = configuration.warm_era_lower_bound(last_elapsed_interval);
let reply = AlignmentReply::Intervals({
log.intervals
.iter()
.filter(|(&idx, _)| idx < warm_era_lower_bound)
.map(|(idx, interval)| (*idx, interval.fingerprint()))
.collect::<HashMap<_, _>>()
});
reply_to_query(query, reply, None).await;
}
pub(crate) async fn reply_sub_intervals(
&self,
query: &Query,
different_intervals: HashSet<IntervalIdx>,
) {
let mut sub_intervals_fingerprints = HashMap::with_capacity(different_intervals.len());
{
let log = self.replication_log.read().await;
different_intervals.iter().for_each(|interval_idx| {
if let Some(interval) = log.intervals.get(interval_idx) {
sub_intervals_fingerprints
.insert(*interval_idx, interval.sub_intervals_fingerprints());
}
});
}
let reply = AlignmentReply::SubIntervals(sub_intervals_fingerprints);
reply_to_query(query, reply, None).await;
}
pub(crate) async fn reply_events_metadata(
&self,
query: &Query,
different_sub_intervals: HashMap<IntervalIdx, HashSet<SubIntervalIdx>>,
) {
let mut events = Vec::default();
{
let log = self.replication_log.read().await;
different_sub_intervals
.iter()
.for_each(|(interval_idx, sub_intervals)| {
if let Some(interval) = log.intervals.get(interval_idx) {
sub_intervals.iter().for_each(|sub_interval_idx| {
if let Some(sub_interval) = interval.sub_interval_at(sub_interval_idx) {
events.extend(sub_interval.events().map(Into::into));
}
});
}
});
}
let reply = AlignmentReply::EventsMetadata(events);
reply_to_query(query, reply, None).await;
}
pub(crate) async fn reply_event_retrieval(
&self,
query: &Query,
event_to_retrieve: EventMetadata,
) {
let value = match &event_to_retrieve.action {
Action::Delete | Action::WildcardDelete(_) => None,
Action::Put => {
let stored_data = {
let mut storage = self.storage_service.storage.lock().await;
match storage
.get(event_to_retrieve.stripped_key.clone(), "")
.await
{
Ok(stored_data) => stored_data,
Err(e) => {
tracing::error!(
"Failed to retrieve data associated to key < {:?} >: {e:?}",
event_to_retrieve.key_expr()
);
return;
}
}
};
let requested_data = stored_data
.into_iter()
.find(|data| data.timestamp == *event_to_retrieve.timestamp());
match requested_data {
Some(data) => Some((data.payload, data.encoding)),
None => {
tracing::debug!(
"Found no data in the Storage associated to key < {:?} > with a \
Timestamp equal to: {}",
event_to_retrieve.key_expr(),
event_to_retrieve.timestamp()
);
return;
}
}
}
Action::WildcardPut(wildcard_ke) => {
let wildcard_puts_guard = self.storage_service.wildcard_puts.read().await;
if let Some(update) = wildcard_puts_guard.weight_at(wildcard_ke) {
Some((update.payload().clone(), update.encoding().clone()))
} else {
tracing::error!(
"Ignoring Wildcard Update < {wildcard_ke} >: found no associated `Update`."
);
return;
}
}
};
reply_to_query(query, AlignmentReply::Retrieval(event_to_retrieve), value).await;
}
}
async fn reply_to_query(query: &Query, reply: AlignmentReply, value: Option<(ZBytes, Encoding)>) {
let attachment = match bincode::serialize(&reply) {
Ok(attachment) => attachment,
Err(e) => {
tracing::error!("Failed to serialize AlignmentReply: {e:?}");
return;
}
};
let reply_fut = if let Some(value) = value {
query
.reply(query.key_expr(), value.0)
.encoding(value.1)
.attachment(attachment)
} else {
query
.reply(query.key_expr(), ZBytes::new())
.attachment(attachment)
};
if let Err(e) = reply_fut.await {
tracing::error!("Failed to reply to Query: {e:?}");
}
}