rocketmq_client_rust/implementation/
mq_client_api_factory.rs1use std::future::Future;
16use std::sync::atomic::AtomicBool;
17use std::sync::atomic::Ordering;
18use std::sync::Arc;
19use std::thread;
20
21use cheetah_string::CheetahString;
22use rand::RngExt;
23use rocketmq_error::RocketMQError;
24use rocketmq_error::RocketMQResult;
25use rocketmq_rust::ArcMut;
26use tokio::task::JoinHandle;
27use tokio::time::Duration;
28use tokio::time::Instant;
29
30use crate::common::nameserver_access_config::NameserverAccessConfig;
31use crate::implementation::mq_client_api_impl::MQClientAPIImpl;
32
33pub struct MQClientAPIFactory {
34 nameserver_access_config: NameserverAccessConfig,
35 name_prefix: CheetahString,
36 clients: Vec<ArcMut<MQClientAPIImpl>>,
37 namesrv_refresh_task: Option<NamesrvRefreshTaskHandle>,
38}
39
40enum NamesrvRefreshTaskHandle {
41 Tokio(JoinHandle<()>),
42 Thread {
43 stop_signal: Arc<AtomicBool>,
44 handle: thread::JoinHandle<()>,
45 },
46}
47
48impl NamesrvRefreshTaskHandle {
49 fn abort(self) {
50 match self {
51 Self::Tokio(handle) => handle.abort(),
52 Self::Thread { stop_signal, handle } => {
53 stop_signal.store(true, Ordering::Release);
54 drop(handle);
55 }
56 }
57 }
58
59 fn is_finished(&self) -> bool {
60 match self {
61 Self::Tokio(handle) => handle.is_finished(),
62 Self::Thread { handle, .. } => handle.is_finished(),
63 }
64 }
65}
66
67impl MQClientAPIFactory {
68 const NAMESRV_DOMAIN_FETCH_INITIAL_DELAY: Duration = Duration::from_secs(10);
69 const NAMESRV_DOMAIN_FETCH_INTERVAL: Duration = Duration::from_secs(120);
70
71 pub fn new(
72 nameserver_access_config: NameserverAccessConfig,
73 name_prefix: impl Into<CheetahString>,
74 clients: Vec<ArcMut<MQClientAPIImpl>>,
75 ) -> RocketMQResult<Self> {
76 validate_nameserver_access_config(&nameserver_access_config)?;
77 if clients.is_empty() {
78 return Err(RocketMQError::illegal_argument(
79 "MQClientAPIFactory requires at least one MQClientAPIImpl",
80 ));
81 }
82
83 Ok(Self {
84 nameserver_access_config,
85 name_prefix: name_prefix.into(),
86 clients,
87 namesrv_refresh_task: None,
88 })
89 }
90
91 #[inline]
92 pub fn nameserver_access_config(&self) -> &NameserverAccessConfig {
93 &self.nameserver_access_config
94 }
95
96 #[inline]
97 pub fn name_prefix(&self) -> &CheetahString {
98 &self.name_prefix
99 }
100
101 #[inline]
102 pub fn client_num(&self) -> usize {
103 self.clients.len()
104 }
105
106 #[inline]
107 pub fn get_clients(&self) -> &[ArcMut<MQClientAPIImpl>] {
108 &self.clients
109 }
110
111 pub fn get_client(&self) -> RocketMQResult<ArcMut<MQClientAPIImpl>> {
112 match self.clients.len() {
113 0 => Err(RocketMQError::not_initialized("MQClientAPIFactory clients")),
114 1 => Ok(self.clients[0].clone()),
115 len => {
116 let index = rand::rng().random_range(0..len);
117 Ok(self.clients[index].clone())
118 }
119 }
120 }
121
122 pub async fn create_and_start(
123 nameserver_access_config: NameserverAccessConfig,
124 name_prefix: impl Into<CheetahString>,
125 clients: Vec<ArcMut<MQClientAPIImpl>>,
126 ) -> RocketMQResult<Self> {
127 let mut factory = Self::new(nameserver_access_config, name_prefix, clients)?;
128 factory.start().await?;
129 Ok(factory)
130 }
131
132 pub async fn start(&mut self) -> RocketMQResult<()> {
133 self.apply_nameserver_access_config().await?;
134 self.start_nameserver_domain_refresh();
135 for client in &self.clients {
136 client.start().await;
137 }
138 Ok(())
139 }
140
141 pub fn shutdown(&mut self) {
142 self.stop_nameserver_domain_refresh();
143 for client in &mut self.clients {
144 client.shutdown();
145 }
146 }
147
148 pub async fn on_name_server_address_change(&mut self, namesrv_address: impl Into<String>) {
149 let namesrv_address = namesrv_address.into();
150 for client in &mut self.clients {
151 client
152 .on_name_server_address_change(Some(namesrv_address.clone()))
153 .await;
154 }
155 }
156
157 pub async fn apply_nameserver_access_config(&mut self) -> RocketMQResult<()> {
158 validate_nameserver_access_config(&self.nameserver_access_config)?;
159 if !self.nameserver_access_config.namesrv_domain().is_empty() {
160 for client in &mut self.clients {
161 client.fetch_name_server_addr().await;
162 }
163 return Ok(());
164 }
165
166 let namesrv_addr = self.nameserver_access_config.namesrv_addr().as_str();
167 for client in &self.clients {
168 client.update_name_server_address_list(namesrv_addr).await;
169 }
170 Ok(())
171 }
172
173 fn start_nameserver_domain_refresh(&mut self) {
174 self.stop_nameserver_domain_refresh();
175 if self.nameserver_access_config.namesrv_domain().is_empty() {
176 return;
177 }
178
179 let clients = self.clients.clone();
180 let stop_signal = Arc::new(AtomicBool::new(false));
181 self.namesrv_refresh_task =
182 spawn_namesrv_refresh_task("rocketmq-client-namesrv-refresh", stop_signal.clone(), async move {
183 if !sleep_or_stop(&stop_signal, Self::NAMESRV_DOMAIN_FETCH_INITIAL_DELAY).await {
184 return;
185 }
186
187 loop {
188 for client in &clients {
189 let mut client = client.clone();
190 client.fetch_name_server_addr().await;
191 }
192
193 if !sleep_or_stop(&stop_signal, Self::NAMESRV_DOMAIN_FETCH_INTERVAL).await {
194 break;
195 }
196 }
197 });
198 }
199
200 fn stop_nameserver_domain_refresh(&mut self) {
201 if let Some(task) = self.namesrv_refresh_task.take() {
202 task.abort();
203 }
204 }
205}
206
207fn validate_nameserver_access_config(config: &NameserverAccessConfig) -> RocketMQResult<()> {
208 if config.namesrv_domain().is_empty() && config.namesrv_addr().is_empty() {
209 return Err(RocketMQError::illegal_argument(
210 "The configuration item NamesrvAddr is not configured",
211 ));
212 }
213 Ok(())
214}
215
216fn spawn_namesrv_refresh_task<F>(
217 thread_name: &'static str,
218 stop_signal: Arc<AtomicBool>,
219 task: F,
220) -> Option<NamesrvRefreshTaskHandle>
221where
222 F: Future<Output = ()> + Send + 'static,
223{
224 if let Ok(handle) = tokio::runtime::Handle::try_current() {
225 return Some(NamesrvRefreshTaskHandle::Tokio(handle.spawn(task)));
226 }
227
228 match tokio::runtime::Builder::new_current_thread().enable_all().build() {
229 Ok(runtime) => match thread::Builder::new()
230 .name(thread_name.to_string())
231 .spawn(move || runtime.block_on(task))
232 {
233 Ok(handle) => Some(NamesrvRefreshTaskHandle::Thread { stop_signal, handle }),
234 Err(error) => {
235 tracing::warn!("Failed to spawn {} background thread: {}", thread_name, error);
236 None
237 }
238 },
239 Err(error) => {
240 tracing::warn!("Failed to build {} runtime: {}", thread_name, error);
241 None
242 }
243 }
244}
245
246async fn sleep_or_stop(stop_signal: &Arc<AtomicBool>, delay: Duration) -> bool {
247 let deadline = Instant::now() + delay;
248
249 loop {
250 if stop_signal.load(Ordering::Acquire) {
251 return false;
252 }
253
254 let now = Instant::now();
255 if now >= deadline {
256 return true;
257 }
258
259 tokio::time::sleep((deadline - now).min(Duration::from_millis(50))).await;
260 }
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266
267 #[test]
268 fn factory_rejects_missing_nameserver_like_java() {
269 let error = validate_nameserver_access_config(&NameserverAccessConfig::default())
270 .expect_err("missing namesrvAddr and namesrvDomain should be invalid");
271
272 assert!(error.to_string().contains("NamesrvAddr is not configured"));
273 }
274
275 #[test]
276 fn factory_accepts_static_nameserver_address() {
277 validate_nameserver_access_config(&NameserverAccessConfig::new("127.0.0.1:9876", "", ""))
278 .expect("static namesrvAddr should be valid");
279 }
280
281 #[test]
282 fn factory_accepts_nameserver_domain() {
283 validate_nameserver_access_config(&NameserverAccessConfig::new("", "domain", "subgroup"))
284 .expect("namesrvDomain should be valid");
285 }
286
287 #[tokio::test]
288 async fn create_and_start_validates_before_starting() {
289 let result = MQClientAPIFactory::create_and_start(
290 NameserverAccessConfig::new("127.0.0.1:9876", "", ""),
291 "factory-test",
292 Vec::new(),
293 )
294 .await;
295 let error = match result {
296 Ok(_) => panic!("empty client list should be rejected before start"),
297 Err(error) => error,
298 };
299
300 assert!(error.to_string().contains("requires at least one MQClientAPIImpl"));
301 }
302
303 #[tokio::test]
304 async fn domain_nameserver_config_starts_periodic_refresh_like_java() {
305 let mut factory = MQClientAPIFactory {
306 nameserver_access_config: NameserverAccessConfig::new("", "example.com", "default"),
307 name_prefix: CheetahString::from_static_str("factory-test"),
308 clients: Vec::new(),
309 namesrv_refresh_task: None,
310 };
311
312 factory.start_nameserver_domain_refresh();
313
314 assert!(factory
315 .namesrv_refresh_task
316 .as_ref()
317 .is_some_and(|task| !task.is_finished()));
318
319 factory.shutdown();
320
321 assert!(factory.namesrv_refresh_task.is_none());
322 }
323
324 #[tokio::test]
325 async fn static_nameserver_config_does_not_start_periodic_refresh() {
326 let mut factory = MQClientAPIFactory {
327 nameserver_access_config: NameserverAccessConfig::new("127.0.0.1:9876", "", ""),
328 name_prefix: CheetahString::from_static_str("factory-test"),
329 clients: Vec::new(),
330 namesrv_refresh_task: None,
331 };
332
333 factory.start_nameserver_domain_refresh();
334
335 assert!(factory.namesrv_refresh_task.is_none());
336 }
337
338 #[test]
339 fn domain_refresh_without_tokio_runtime_does_not_panic() {
340 let mut factory = MQClientAPIFactory {
341 nameserver_access_config: NameserverAccessConfig::new("", "example.com", "default"),
342 name_prefix: CheetahString::from_static_str("factory-test"),
343 clients: Vec::new(),
344 namesrv_refresh_task: None,
345 };
346
347 factory.start_nameserver_domain_refresh();
348
349 assert!(factory
350 .namesrv_refresh_task
351 .as_ref()
352 .is_some_and(|task| !task.is_finished()));
353
354 factory.shutdown();
355 std::thread::sleep(Duration::from_millis(80));
356
357 assert!(factory.namesrv_refresh_task.is_none());
358 }
359}