use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use cheetah_string::CheetahString;
use rocketmq_model::common::message::message_queue::MessageQueue;
use rocketmq_runtime::ChildServiceContext;
use crate::base::client_config::ClientConfig;
use crate::latency::latency_fault_tolerance::LatencyFaultTolerance;
#[cfg(any(test, feature = "test-support"))]
use crate::latency::latency_fault_tolerance_impl::FaultClock;
use crate::latency::latency_fault_tolerance_impl::LatencyFaultToleranceImpl;
use crate::producer::producer_impl::default_mq_producer_impl::DefaultResolver;
use crate::producer::producer_impl::default_mq_producer_impl::DefaultServiceDetector;
use crate::producer::producer_impl::queue_filter::QueueFilter;
use crate::producer::producer_impl::topic_publish_info::TopicPublishInfo;
#[derive(Clone)]
pub struct MQFaultStrategy {
latency_fault_tolerance: Arc<LatencyFaultToleranceImpl<DefaultResolver, DefaultServiceDetector>>,
send_latency_fault_enable: Arc<AtomicBool>,
start_detector_enable: Arc<AtomicBool>,
latency_max: Vec<u64>,
not_available_duration: Vec<u64>,
}
impl MQFaultStrategy {
pub const DEFAULT_LATENCY_MAX: [u64; 7] = [50, 100, 550, 1800, 3000, 5000, 15000];
pub const DEFAULT_NOT_AVAILABLE_DURATION: [u64; 7] = [0, 0, 2000, 5000, 6000, 10000, 30000];
const ISOLATION_LATENCY_MS: u64 = 10_000;
pub fn new(service_context: ChildServiceContext, client_config: &ClientConfig) -> Self {
let tolerance_impl = LatencyFaultToleranceImpl::new(service_context);
Self::from_tolerance_impl(tolerance_impl, client_config)
}
#[cfg(any(test, feature = "test-support"))]
pub(crate) fn new_with_fault_clock(
service_context: ChildServiceContext,
client_config: &ClientConfig,
clock: Arc<dyn FaultClock>,
) -> Self {
let tolerance_impl = LatencyFaultToleranceImpl::new_with_clock(service_context, clock);
Self::from_tolerance_impl(tolerance_impl, client_config)
}
fn from_tolerance_impl(
tolerance_impl: LatencyFaultToleranceImpl<DefaultResolver, DefaultServiceDetector>,
client_config: &ClientConfig,
) -> Self {
tolerance_impl.set_detect_interval(client_config.detect_interval);
tolerance_impl.set_detect_timeout(client_config.detect_timeout);
tolerance_impl.set_start_detector_enable(client_config.start_detector_enable);
let latency_fault_tolerance = Arc::new(tolerance_impl);
Self {
latency_fault_tolerance: Arc::clone(&latency_fault_tolerance),
send_latency_fault_enable: Arc::new(AtomicBool::new(client_config.send_latency_enable)),
start_detector_enable: Arc::new(AtomicBool::new(client_config.start_detector_enable)),
latency_max: Self::DEFAULT_LATENCY_MAX.to_vec(),
not_available_duration: Self::DEFAULT_NOT_AVAILABLE_DURATION.to_vec(),
}
}
pub fn start_detector(&self) {
LatencyFaultTolerance::start_detector(self.latency_fault_tolerance.clone());
}
pub(crate) fn set_resolve(&self, resolver: DefaultResolver) {
self.latency_fault_tolerance.set_resolver(resolver);
}
pub(crate) fn set_service_detector(&self, service_detector: DefaultServiceDetector) {
self.latency_fault_tolerance.set_service_detector(service_detector);
}
pub fn shutdown(&self) {
self.latency_fault_tolerance.shutdown();
}
pub async fn shutdown_async(&self) -> bool {
self.latency_fault_tolerance.shutdown_detector().await
}
pub fn is_start_detector_enable(&self) -> bool {
self.start_detector_enable.load(Ordering::Relaxed)
}
pub fn set_start_detector_enable(&self, start_detector_enable: bool) {
self.start_detector_enable
.store(start_detector_enable, Ordering::Relaxed);
self.latency_fault_tolerance
.set_start_detector_enable(start_detector_enable);
}
pub fn is_send_latency_fault_enable(&self) -> bool {
self.send_latency_fault_enable.load(Ordering::Relaxed)
}
pub fn select_one_message_queue(
&self,
tp_info: &TopicPublishInfo,
last_broker_name: Option<&CheetahString>,
reset_index: bool,
) -> Option<MessageQueue> {
let broker_filter = BrokerFilter::with_last_broker_name(last_broker_name.cloned());
if self.send_latency_fault_enable.load(Ordering::Relaxed) {
if reset_index {
tp_info.reset_index();
}
let available_filter = AvailableFilter {
latency_fault_tolerance: Arc::clone(&self.latency_fault_tolerance),
};
let filter = &[
&available_filter as &dyn QueueFilter,
&broker_filter as &dyn QueueFilter,
];
if let Some(mq) = tp_info.select_one_message_queue_filters(filter) {
return Some(mq);
}
let reachable_filter = ReachableFilter {
latency_fault_tolerance: Arc::clone(&self.latency_fault_tolerance),
};
let filter = &[
&reachable_filter as &dyn QueueFilter,
&broker_filter as &dyn QueueFilter,
];
if let Some(mq) = tp_info.select_one_message_queue_filters(filter) {
return Some(mq);
}
return tp_info.select_one_message_queue_filters(&[]);
}
if let Some(mq) = tp_info.select_one_message_queue_filters(&[&broker_filter]) {
return Some(mq);
}
tp_info.select_one_message_queue_filters(&[])
}
pub fn get_latency_max(&self) -> &[u64] {
&self.latency_max
}
pub fn set_latency_max(&mut self, latency_max: impl Into<Vec<u64>>) {
self.latency_max = latency_max.into();
}
pub fn get_not_available_duration(&self) -> &[u64] {
&self.not_available_duration
}
pub fn set_not_available_duration(&mut self, not_available_duration: impl Into<Vec<u64>>) {
self.not_available_duration = not_available_duration.into();
}
pub async fn update_fault_item(
&self,
broker_name: CheetahString,
current_latency: u64,
isolation: bool,
reachable: bool,
) {
if self.send_latency_fault_enable.load(Ordering::Relaxed) {
let effective_latency = if isolation {
Self::ISOLATION_LATENCY_MS
} else {
current_latency
};
let duration = self.compute_not_available_duration(effective_latency);
self.latency_fault_tolerance
.update_fault_item(broker_name, current_latency, duration, reachable)
.await;
}
}
fn compute_not_available_duration(&self, current_latency: u64) -> u64 {
let len = self.latency_max.len().min(self.not_available_duration.len());
for i in (0..len).rev() {
if current_latency >= self.latency_max[i] {
return self.not_available_duration[i];
}
}
0
}
#[cfg(any(test, feature = "test-support"))]
pub(crate) fn not_available_duration_for_test(&self, current_latency: u64, isolation: bool) -> u64 {
self.compute_not_available_duration(if isolation {
Self::ISOLATION_LATENCY_MS
} else {
current_latency
})
}
#[cfg(any(test, feature = "test-support"))]
pub(crate) fn fault_item_state_for_test(&self, broker_name: &CheetahString) -> Option<(bool, bool, u64, u64)> {
self.latency_fault_tolerance.fault_item_state_for_test(broker_name)
}
#[inline]
pub fn set_send_latency_fault_enable(&self, send_latency_fault_enable: bool) {
self.send_latency_fault_enable
.store(send_latency_fault_enable, Ordering::Relaxed);
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct BrokerFilter {
last_broker_name: Option<CheetahString>,
}
impl BrokerFilter {
pub fn new() -> Self {
Self::default()
}
pub fn with_last_broker_name(last_broker_name: Option<CheetahString>) -> Self {
Self { last_broker_name }
}
pub fn last_broker_name(&self) -> Option<&CheetahString> {
self.last_broker_name.as_ref()
}
pub fn get_last_broker_name(&self) -> Option<&CheetahString> {
self.last_broker_name()
}
pub fn set_last_broker_name(&mut self, last_broker_name: impl Into<CheetahString>) {
self.last_broker_name = Some(last_broker_name.into());
}
pub fn clear_last_broker_name(&mut self) {
self.last_broker_name = None;
}
}
impl QueueFilter for BrokerFilter {
fn filter(&self, message_queue: &MessageQueue) -> bool {
if let Some(last_broker_name) = &self.last_broker_name {
message_queue.broker_name() != last_broker_name
} else {
true
}
}
}
struct ReachableFilter {
latency_fault_tolerance: Arc<LatencyFaultToleranceImpl<DefaultResolver, DefaultServiceDetector>>,
}
impl QueueFilter for ReachableFilter {
fn filter(&self, message_queue: &MessageQueue) -> bool {
self.latency_fault_tolerance.is_reachable(message_queue.broker_name())
}
}
struct AvailableFilter {
latency_fault_tolerance: Arc<LatencyFaultToleranceImpl<DefaultResolver, DefaultServiceDetector>>,
}
impl QueueFilter for AvailableFilter {
fn filter(&self, message_queue: &MessageQueue) -> bool {
self.latency_fault_tolerance.is_available(message_queue.broker_name())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn broker_filter_matches_java_last_broker_name_rule() {
let mq = MessageQueue::from_parts("topic", "broker-a", 0);
let mut filter = BrokerFilter::new();
assert!(filter.filter(&mq));
assert!(filter.get_last_broker_name().is_none());
filter.set_last_broker_name("broker-a");
assert_eq!(
filter.get_last_broker_name(),
Some(&CheetahString::from_static_str("broker-a"))
);
assert!(!filter.filter(&mq));
filter.set_last_broker_name("broker-b");
assert!(filter.filter(&mq));
filter.clear_last_broker_name();
assert!(filter.filter(&mq));
}
#[test]
fn mq_fault_strategy_copies_detector_config_like_java_constructor() {
let mut client_config = ClientConfig::default();
client_config.set_detect_interval(12_345);
client_config.set_detect_timeout(321);
client_config.set_start_detector_enable(true);
let strategy = MQFaultStrategy::new(
crate::runtime::test_service_context("mq-fault-strategy-test"),
&client_config,
);
let (detect_interval, detect_timeout) = strategy.latency_fault_tolerance.detector_config_for_test();
assert_eq!(detect_interval, 12_345);
assert_eq!(detect_timeout, 321);
assert!(strategy.is_start_detector_enable());
assert!(strategy.latency_fault_tolerance.is_start_detector_enable());
}
#[test]
fn cloned_strategy_shares_runtime_switches_and_snapshots_thresholds() {
let mut strategy = MQFaultStrategy::new(
crate::runtime::test_service_context("mq-fault-strategy-clone-test"),
&ClientConfig::default(),
);
strategy.set_latency_max(vec![10, 20, 30]);
strategy.set_not_available_duration(vec![0, 100, 200]);
strategy.set_send_latency_fault_enable(true);
strategy.set_start_detector_enable(true);
let snapshot = strategy.clone();
strategy.set_latency_max(vec![40, 50, 60]);
strategy.set_not_available_duration(vec![300, 400, 500]);
strategy.set_send_latency_fault_enable(false);
strategy.set_start_detector_enable(false);
assert_eq!(snapshot.get_latency_max(), &[10, 20, 30]);
assert_eq!(snapshot.get_not_available_duration(), &[0, 100, 200]);
assert!(!snapshot.is_send_latency_fault_enable());
assert!(!snapshot.is_start_detector_enable());
}
}