Skip to main content

rocketmq_client_rust/implementation/
mq_client_api_factory.rs

1// Copyright 2023 The RocketMQ Rust Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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}