use std::future::Future;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use rocketmq_common::common::message::message_enum::MessageRequestMode;
use rocketmq_error::RocketMQError;
use rocketmq_rust::ArcMut;
use rocketmq_rust::Shutdown;
use tracing::error;
use tracing::info;
use tracing::warn;
use crate::consumer::consumer_impl::message_request::MessageRequest;
use crate::consumer::consumer_impl::pop_request::PopRequest;
use crate::consumer::consumer_impl::pull_request::PullRequest;
use crate::factory::mq_client_instance::MQClientInstance;
const DEFAULT_QUEUE_CAPACITY: usize = 4096;
const DEFAULT_SHUTDOWN_TIMEOUT_MS: u64 = 1000;
#[derive(Clone)]
pub struct PullMessageService {
tx: Option<tokio::sync::mpsc::Sender<Box<dyn MessageRequest + Send + 'static>>>,
tx_shutdown: Option<tokio::sync::broadcast::Sender<()>>,
stopped: Arc<AtomicBool>,
queue_capacity: usize,
}
impl PullMessageService {
pub fn new() -> Self {
Self::with_capacity(DEFAULT_QUEUE_CAPACITY)
}
pub fn with_capacity(queue_capacity: usize) -> Self {
PullMessageService {
tx: None,
tx_shutdown: None,
stopped: Arc::new(AtomicBool::new(false)),
queue_capacity,
}
}
#[inline]
pub fn is_stopped(&self) -> bool {
self.stopped.load(Ordering::Acquire)
}
#[inline]
pub fn get_service_name(&self) -> &'static str {
"PullMessageService"
}
pub async fn start(&mut self, mut instance: ArcMut<MQClientInstance>) -> Result<(), RocketMQError> {
if self.tx.is_some() {
warn!("{} already started", self.get_service_name());
return Ok(());
}
let (tx, mut rx) = tokio::sync::mpsc::channel::<Box<dyn MessageRequest + Send + 'static>>(self.queue_capacity);
let (mut shutdown, tx_shutdown) = Shutdown::new(1);
self.tx = Some(tx);
self.tx_shutdown = Some(tx_shutdown);
self.stopped.store(false, Ordering::Release);
spawn_pull_message_task("rocketmq-client-pull-message-service", async move {
info!("{} service started", "PullMessageService");
loop {
tokio::select! {
_ = shutdown.recv() => {
info!("{} received shutdown signal", "PullMessageService");
break;
}
Some(request) = rx.recv() => {
if let Err(e) = Self::process_request(request, &mut instance).await {
error!("{} failed to process request: {:?}", "PullMessageService", e);
}
}
}
}
info!("{} service end", "PullMessageService");
});
Ok(())
}
async fn process_request(
request: Box<dyn MessageRequest + Send + 'static>,
instance: &mut MQClientInstance,
) -> Result<(), RocketMQError> {
match request.get_message_request_mode() {
MessageRequestMode::Pull => {
if let Ok(pull_request) = request.into_any().downcast::<PullRequest>() {
Self::pull_message(*pull_request, instance).await;
Ok(())
} else {
Err(RocketMQError::Internal("Failed to downcast to PullRequest".to_string()))
}
}
MessageRequestMode::Pop => {
if let Ok(pop_request) = request.into_any().downcast::<PopRequest>() {
Self::pop_message(*pop_request, instance).await;
Ok(())
} else {
Err(RocketMQError::Internal("Failed to downcast to PopRequest".to_string()))
}
}
}
}
async fn pull_message(request: PullRequest, instance: &mut MQClientInstance) {
if let Some(mut consumer) = instance.select_consumer(request.get_consumer_group()).await {
consumer.pull_message(request).await;
} else {
warn!("No matched consumer for the PullRequest {}, drop it", request)
}
}
async fn pop_message(request: PopRequest, instance: &mut MQClientInstance) {
if let Some(mut consumer) = instance.select_consumer(request.get_consumer_group()).await {
consumer.pop_message(request).await;
} else {
warn!("No matched consumer for the PopRequest {}, drop it", request)
}
}
pub fn execute_pull_request_later(&self, pull_request: PullRequest, time_delay: u64) {
if self.is_stopped() {
warn!("{} has shutdown, cannot execute later task", self.get_service_name());
return;
}
let this = self.clone();
let request = pull_request.clone();
spawn_pull_message_task("rocketmq-client-pull-request-delay", async move {
tokio::time::sleep(Duration::from_millis(time_delay)).await;
if this.is_stopped() {
return;
}
if let Some(tx) = &this.tx {
if let Err(e) = tx.send(Box::new(request)).await {
warn!("Failed to send pull request: {:?}", e);
}
}
});
}
pub async fn execute_pull_request_immediately(&self, pull_request: PullRequest) {
if self.is_stopped() {
warn!("PullMessageService has shutdown");
return;
}
if let Some(tx) = &self.tx {
if let Err(e) = tx.send(Box::new(pull_request)).await {
error!("executePullRequestImmediately messageRequestQueue.put error: {:?}", e);
}
} else {
warn!("PullMessageService not started");
}
}
pub fn execute_pop_pull_request_later(&self, pop_request: PopRequest, time_delay: u64) {
if self.is_stopped() {
warn!("{} has shutdown, cannot execute later task", self.get_service_name());
return;
}
let this = self.clone();
let request = pop_request.clone();
spawn_pull_message_task("rocketmq-client-pop-request-delay", async move {
tokio::time::sleep(Duration::from_millis(time_delay)).await;
if this.is_stopped() {
return;
}
if let Some(tx) = &this.tx {
if let Err(e) = tx.send(Box::new(request)).await {
warn!("Failed to send pop request: {:?}", e);
}
}
});
}
pub async fn execute_pop_pull_request_immediately(&self, pop_request: PopRequest) {
if self.is_stopped() {
warn!("PullMessageService has shutdown");
return;
}
if let Some(tx) = &self.tx {
if let Err(e) = tx.send(Box::new(pop_request)).await {
error!(
"executePopPullRequestImmediately messageRequestQueue.put error: {:?}",
e
);
}
} else {
warn!("PullMessageService not started");
}
}
pub fn execute_task_later<F>(&self, task: F, time_delay: u64)
where
F: FnOnce() + Send + 'static,
{
if self.is_stopped() {
warn!("{} has shutdown, cannot execute task", self.get_service_name());
return;
}
let stopped = self.stopped.clone();
spawn_pull_message_task("rocketmq-client-pull-task-delay", async move {
tokio::time::sleep(Duration::from_millis(time_delay)).await;
if stopped.load(Ordering::Acquire) {
return;
}
task();
});
}
pub fn execute_task<F>(&self, task: F)
where
F: FnOnce() + Send + 'static,
{
if self.is_stopped() {
warn!("{} has shutdown, cannot execute task", self.get_service_name());
return;
}
spawn_pull_message_task("rocketmq-client-pull-task", async move {
task();
});
}
pub async fn shutdown(&self, timeout_ms: u64) -> Result<(), RocketMQError> {
if self.is_stopped() {
warn!("{} already stopped", self.get_service_name());
return Ok(());
}
info!("{} shutting down...", self.get_service_name());
self.stopped.store(true, Ordering::Release);
if let Some(tx_shutdown) = &self.tx_shutdown {
tx_shutdown
.send(())
.map_err(|_| RocketMQError::Internal("Failed to send shutdown signal".to_string()))?;
}
let wait_result = tokio::time::timeout(Duration::from_millis(timeout_ms), async {
while self.tx.as_ref().map(|tx| !tx.is_closed()).unwrap_or(false) {
tokio::time::sleep(Duration::from_millis(10)).await;
}
})
.await;
if wait_result.is_err() {
warn!(
"{} shutdown timeout after {}ms, forcing exit",
self.get_service_name(),
timeout_ms
);
}
info!("{} shutdown completed", self.get_service_name());
Ok(())
}
pub async fn shutdown_default(&self) -> Result<(), RocketMQError> {
self.shutdown(DEFAULT_SHUTDOWN_TIMEOUT_MS).await
}
}
impl Default for PullMessageService {
fn default() -> Self {
Self::new()
}
}
fn spawn_pull_message_task<F>(thread_name: &'static str, task: F)
where
F: Future<Output = ()> + Send + 'static,
{
if let Ok(handle) = tokio::runtime::Handle::try_current() {
drop(handle.spawn(task));
return;
}
match thread::Builder::new().name(thread_name.to_string()).spawn(move || {
match tokio::runtime::Builder::new_current_thread().enable_all().build() {
Ok(runtime) => runtime.block_on(task),
Err(error) => error!("Failed to build {} runtime: {}", thread_name, error),
}
}) {
Ok(_handle) => {}
Err(error) => error!("Failed to spawn {} background thread: {}", thread_name, error),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn execute_task_without_tokio_runtime_does_not_spawn_panic() {
let service = PullMessageService::new();
let ran = Arc::new(AtomicBool::new(false));
let ran_clone = ran.clone();
service.execute_task(move || ran_clone.store(true, Ordering::Release));
std::thread::sleep(Duration::from_millis(50));
assert!(ran.load(Ordering::Acquire));
}
#[test]
fn execute_task_later_without_tokio_runtime_does_not_spawn_panic() {
let service = PullMessageService::new();
let ran = Arc::new(AtomicBool::new(false));
let ran_clone = ran.clone();
service.execute_task_later(move || ran_clone.store(true, Ordering::Release), 1);
std::thread::sleep(Duration::from_millis(50));
assert!(ran.load(Ordering::Acquire));
}
}