use std::any::{Any, type_name};
use std::fmt;
use std::sync::Arc;
use crate::RawMessage;
#[derive(Clone)]
pub(crate) struct RecordedOptions {
pub(crate) type_name: &'static str,
pub(crate) value: Option<Arc<dyn Any + Send + Sync>>,
}
impl RecordedOptions {
pub(crate) fn capture<Options: Clone + Send + Sync + 'static>(
options: Option<&Options>,
) -> Self {
Self {
type_name: type_name::<Options>(),
value: options.map(|options| Arc::new(options.clone()) as Arc<dyn Any + Send + Sync>),
}
}
pub(crate) fn downcast<Options: 'static>(&self, channel: &str) -> Option<&Options> {
let value = self.value.as_ref()?;
Some(value.as_ref().downcast_ref::<Options>().unwrap_or_else(|| {
panic!(
"channel {channel:?} published with per-message options of type `{}`, not `{}`",
self.type_name,
type_name::<Options>(),
)
}))
}
fn certainly_same_as(&self, other: &Self) -> bool {
self.type_name == other.type_name && self.value.is_none() && other.value.is_none()
}
}
impl fmt::Debug for RecordedOptions {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RecordedOptions")
.field("type_name", &self.type_name)
.field("set", &self.value.is_some())
.finish()
}
}
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct Span {
pub(crate) started: u64,
pub(crate) finished: u64,
}
impl Span {
const fn overlaps(self, other: Self) -> bool {
self.started < other.finished && other.started < self.finished
}
}
#[derive(Clone, Debug)]
pub(crate) struct RuntimePublish {
pub(crate) message: RawMessage,
pub(crate) options: RecordedOptions,
pub(crate) span: Span,
}
#[derive(Clone, Debug)]
pub(crate) enum PublishedEntry {
Test { message: RawMessage, span: Span },
Runtime(RuntimePublish),
}
impl PublishedEntry {
pub(crate) const fn message(&self) -> &RawMessage {
match self {
Self::Test { message, .. } => message,
Self::Runtime(publish) => &publish.message,
}
}
const fn span(&self) -> Span {
match self {
Self::Test { span, .. } => *span,
Self::Runtime(publish) => publish.span,
}
}
pub(crate) fn options(&self) -> MessageOptions {
match self {
Self::Test { .. } => MessageOptions::Unrecorded,
Self::Runtime(publish) => MessageOptions::Recorded(publish.options.clone()),
}
}
fn certainly_same_as(&self, other: &Self) -> bool {
match (self, other) {
(Self::Test { .. }, Self::Test { .. }) => true,
(Self::Runtime(one), Self::Runtime(other)) => {
one.options.certainly_same_as(&other.options)
}
_ => false,
}
}
}
#[derive(Clone, Debug)]
pub(crate) enum MessageOptions {
Recorded(RecordedOptions),
Unrecorded,
Ambiguous,
}
pub(crate) fn attribute(
log: &[RawMessage],
records: &[PublishedEntry],
evicted: usize,
) -> Vec<MessageOptions> {
if evicted > 0 && records.len() != evicted + log.len() {
return log
.iter()
.map(|message| {
if records
.iter()
.any(|record| record.message().payload() == message.payload())
{
MessageOptions::Ambiguous
} else {
MessageOptions::Unrecorded
}
})
.collect();
}
let (dropped, kept) = records.split_at(evicted);
let candidates: Vec<&PublishedEntry> = dropped
.iter()
.filter(|one| kept.iter().any(|other| one.span().overlaps(other.span())))
.chain(kept)
.collect();
pair_by_payload(log, &candidates)
}
fn pair_by_payload(log: &[RawMessage], records: &[&PublishedEntry]) -> Vec<MessageOptions> {
let mut attributed = vec![MessageOptions::Unrecorded; log.len()];
let mut seen = vec![false; log.len()];
for first in 0..log.len() {
if seen[first] {
continue;
}
let payload = log[first].payload();
let in_log: Vec<usize> = (first..log.len())
.filter(|&index| log[index].payload() == payload)
.collect();
for &index in &in_log {
seen[index] = true;
}
let candidates: Vec<&PublishedEntry> = records
.iter()
.copied()
.filter(|record| record.message().payload() == payload)
.collect();
let Some(earliest) = candidates.first() else {
continue;
};
let concurrent = candidates.iter().enumerate().any(|(at, one)| {
candidates[at + 1..]
.iter()
.any(|other| one.span().overlaps(other.span()))
});
if candidates.len() == in_log.len() && !concurrent {
for (&index, record) in in_log.iter().zip(&candidates) {
attributed[index] = record.options();
}
} else if candidates.len() >= in_log.len()
&& candidates
.iter()
.all(|record| record.certainly_same_as(earliest))
{
for &index in &in_log {
attributed[index] = earliest.options();
}
} else {
for &index in &in_log {
attributed[index] = MessageOptions::Ambiguous;
}
}
}
attributed
}
#[cfg(test)]
mod tests {
use super::{MessageOptions, PublishedEntry, RecordedOptions, RuntimePublish, Span, attribute};
use crate::RawMessage;
fn message(payload: &[u8]) -> RawMessage {
RawMessage::new("orders".to_owned(), payload.to_vec())
}
fn sent(payload: &[u8], options: Option<u8>, started: u64, finished: u64) -> PublishedEntry {
PublishedEntry::Runtime(RuntimePublish {
message: message(payload),
options: RecordedOptions::capture(options.as_ref()),
span: Span { started, finished },
})
}
fn injected(payload: &[u8], started: u64, finished: u64) -> PublishedEntry {
PublishedEntry::Test {
message: message(payload),
span: Span { started, finished },
}
}
fn read(attributed: &[MessageOptions]) -> Vec<Result<Option<u8>, &'static str>> {
attributed
.iter()
.map(|one| match one {
MessageOptions::Recorded(options) => Ok(options.downcast::<u8>("orders").copied()),
MessageOptions::Unrecorded => Err("unrecorded"),
MessageOptions::Ambiguous => Err("ambiguous"),
})
.collect()
}
#[test]
fn a_message_outside_the_record_has_no_options_and_the_rest_keep_theirs() {
let log = [message(b"reply"), message(b"own"), message(b"slot")];
let records = [sent(b"reply", Some(1), 0, 1), sent(b"slot", None, 2, 3)];
assert_eq!(
read(&attribute(&log, &records, 0)),
[Ok(Some(1)), Err("unrecorded"), Ok(None)],
);
}
#[test]
fn identical_payloads_published_in_turn_pair_in_order() {
let log = [message(b"order"), message(b"order")];
let records = [sent(b"order", None, 0, 1), sent(b"order", Some(9), 2, 3)];
assert_eq!(read(&attribute(&log, &records, 0)), [Ok(None), Ok(Some(9))]);
}
#[test]
fn identical_payloads_in_flight_at_once_are_ambiguous() {
let log = [message(b"order"), message(b"order")];
let records = [sent(b"order", None, 0, 3), sent(b"order", Some(9), 1, 2)];
assert_eq!(
read(&attribute(&log, &records, 0)),
[Err("ambiguous"), Err("ambiguous")],
);
}
#[test]
fn an_unrecorded_copy_of_a_recorded_payload_is_ambiguous() {
let log = [message(b"order"), message(b"order")];
let records = [sent(b"order", Some(9), 0, 1)];
assert_eq!(
read(&attribute(&log, &records, 0)),
[Err("ambiguous"), Err("ambiguous")],
);
}
#[test]
fn identical_payloads_with_default_options_are_not_ambiguous() {
let log = [message(b"order"), message(b"order")];
let records = [sent(b"order", None, 0, 3), sent(b"order", None, 1, 2)];
assert_eq!(read(&attribute(&log, &records, 0)), [Ok(None), Ok(None)]);
}
#[test]
fn a_copy_that_evicted_the_reply_does_not_take_its_options() {
let log = [message(b"order")];
let records = [sent(b"order", Some(9), 0, 1), injected(b"order", 2, 3)];
assert_eq!(read(&attribute(&log, &records, 1)), [Err("unrecorded")]);
}
#[test]
fn eviction_keeps_the_options_of_the_publishes_it_kept() {
let log = [message(b"order"), message(b"other")];
let records = [
sent(b"order", Some(1), 0, 1),
sent(b"order", Some(9), 2, 3),
sent(b"other", None, 4, 5),
];
assert_eq!(read(&attribute(&log, &records, 1)), [Ok(Some(9)), Ok(None)],);
}
#[test]
fn an_unseen_publish_after_eviction_leaves_the_bytes_ambiguous() {
let log = [message(b"order"), message(b"own")];
let records = [sent(b"order", Some(9), 0, 1)];
assert_eq!(
read(&attribute(&log, &records, 1)),
[Err("ambiguous"), Err("unrecorded")],
);
}
#[test]
fn eviction_across_publishes_in_flight_at_once_is_ambiguous() {
let log = [message(b"order")];
let records = [sent(b"order", Some(1), 0, 3), sent(b"order", Some(9), 1, 2)];
assert_eq!(read(&attribute(&log, &records, 1)), [Err("ambiguous")]);
}
}