1use std::future::Future;
17use std::sync::{Mutex, Once};
18
19use common_telemetry::info;
20use once_cell::sync::Lazy;
21use paste::paste;
22use serde::{Deserialize, Serialize};
23
24use crate::runtime::{BuilderBuild, RuntimeTrait};
25use crate::{Builder, JoinHandle, Runtime};
26
27const GLOBAL_WORKERS: usize = 8;
28const COMPACT_WORKERS: usize = 4;
29const HB_WORKERS: usize = 2;
30
31#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
33#[serde(default)]
34pub struct RuntimeOptions {
35 pub global_rt_size: usize,
37 pub compact_rt_size: usize,
39 pub compact_rt_max_blocking_threads: usize,
41 pub query_rt_size: usize,
43 pub ingest_rt_size: usize,
45}
46
47impl Default for RuntimeOptions {
48 fn default() -> Self {
49 let cpus = num_cpus::get();
50 Self {
51 global_rt_size: cpus,
52 compact_rt_size: usize::max(cpus / 2, 1),
53 compact_rt_max_blocking_threads: usize::max(cpus / 2, 1),
54 query_rt_size: usize::max(cpus.saturating_sub(1), 1),
55 ingest_rt_size: cpus,
56 }
57 }
58}
59
60pub fn create_runtime(runtime_name: &str, thread_name: &str, worker_threads: usize) -> Runtime {
61 info!(
62 "Creating runtime with runtime_name: {runtime_name}, thread_name: {thread_name}, work_threads: {worker_threads}."
63 );
64 Builder::default()
65 .runtime_name(runtime_name)
66 .thread_name(thread_name)
67 .worker_threads(worker_threads)
68 .build()
69 .expect("Fail to create runtime")
70}
71
72fn create_compact_runtime(
73 runtime_name: &str,
74 thread_name: &str,
75 worker_threads: usize,
76 max_blocking_threads: usize,
77) -> Runtime {
78 let max_blocking_threads = max_blocking_threads.max(1);
79 info!(
80 "Creating compact runtime with runtime_name: {runtime_name}, thread_name: {thread_name}, work_threads: {worker_threads}, max_blocking_threads: {max_blocking_threads}."
81 );
82 Builder::default()
83 .runtime_name(runtime_name)
84 .thread_name(thread_name)
85 .worker_threads(worker_threads)
86 .max_blocking_threads(max_blocking_threads)
87 .build()
88 .expect("Fail to create runtime")
89}
90
91struct GlobalRuntimes {
92 global_runtime: Runtime,
93 compact_runtime: Runtime,
94 hb_runtime: Runtime,
95 query_runtime: Runtime,
96 ingest_runtime: Runtime,
97}
98
99macro_rules! define_spawn {
100 ($type: ident) => {
101 paste! {
102
103 fn [<spawn_ $type>]<F>(&self, future: F) -> JoinHandle<F::Output>
104 where
105 F: Future + Send + 'static,
106 F::Output: Send + 'static,
107 {
108 self.[<$type _runtime>].spawn(future)
109 }
110
111 fn [<spawn_blocking_ $type>]<F, R>(&self, future: F) -> JoinHandle<R>
112 where
113 F: FnOnce() -> R + Send + 'static,
114 R: Send + 'static,
115 {
116 self.[<$type _runtime>].spawn_blocking(future)
117 }
118
119 fn [<block_on_ $type>]<F: Future>(&self, future: F) -> F::Output {
120 self.[<$type _runtime>].block_on(future)
121 }
122 }
123 };
124}
125
126impl GlobalRuntimes {
127 define_spawn!(global);
128 define_spawn!(compact);
129 define_spawn!(hb);
130 define_spawn!(query);
131 define_spawn!(ingest);
132
133 fn new(
134 global: Option<Runtime>,
135 compact: Option<Runtime>,
136 heartbeat: Option<Runtime>,
137 query: Option<Runtime>,
138 ingest: Option<Runtime>,
139 ) -> Self {
140 let global_runtime =
141 global.unwrap_or_else(|| create_runtime("global", "global-worker", GLOBAL_WORKERS));
142 let query_runtime = query.unwrap_or_else(|| global_runtime.clone());
143 let ingest_runtime = ingest.unwrap_or_else(|| global_runtime.clone());
144
145 Self {
146 global_runtime,
147 compact_runtime: compact.unwrap_or_else(|| {
148 let max_blocking_threads =
149 RuntimeOptions::default().compact_rt_max_blocking_threads;
150 create_compact_runtime(
151 "compact",
152 "compact-worker",
153 COMPACT_WORKERS,
154 max_blocking_threads,
155 )
156 }),
157 hb_runtime: heartbeat
158 .unwrap_or_else(|| create_runtime("heartbeat", "hb-worker", HB_WORKERS)),
159 query_runtime,
160 ingest_runtime,
161 }
162 }
163}
164
165#[derive(Default)]
166struct ConfigRuntimes {
167 global_runtime: Option<Runtime>,
168 compact_runtime: Option<Runtime>,
169 hb_runtime: Option<Runtime>,
170 query_runtime: Option<Runtime>,
171 ingest_runtime: Option<Runtime>,
172 already_init: bool,
173}
174
175static GLOBAL_RUNTIMES: Lazy<GlobalRuntimes> = Lazy::new(|| {
176 let mut c = CONFIG_RUNTIMES.lock().unwrap();
177 let global = c.global_runtime.take();
178 let compact = c.compact_runtime.take();
179 let heartbeat = c.hb_runtime.take();
180 let query = c.query_runtime.take();
181 let ingest = c.ingest_runtime.take();
182 c.already_init = true;
183
184 GlobalRuntimes::new(global, compact, heartbeat, query, ingest)
185});
186
187static CONFIG_RUNTIMES: Lazy<Mutex<ConfigRuntimes>> =
188 Lazy::new(|| Mutex::new(ConfigRuntimes::default()));
189
190pub fn init_global_runtimes(options: &RuntimeOptions) {
196 static START: Once = Once::new();
197 START.call_once(move || {
198 let mut c = CONFIG_RUNTIMES.lock().unwrap();
199 assert!(!c.already_init, "Global runtimes already initialized");
200 c.global_runtime = Some(create_runtime(
201 "global",
202 "global-worker",
203 options.global_rt_size,
204 ));
205 c.compact_runtime = Some(create_compact_runtime(
206 "compact",
207 "compact-worker",
208 options.compact_rt_size,
209 options.compact_rt_max_blocking_threads,
210 ));
211 c.hb_runtime = Some(create_runtime("heartbeat", "hb-worker", HB_WORKERS));
212 });
213}
214
215pub fn init_datanode_runtimes(options: &RuntimeOptions) {
221 static START: Once = Once::new();
222 START.call_once(move || {
223 let mut c = CONFIG_RUNTIMES.lock().unwrap();
224 assert!(!c.already_init, "Global runtimes already initialized");
225 c.query_runtime = Some(create_runtime(
226 "query",
227 "query-worker",
228 options.query_rt_size,
229 ));
230 c.ingest_runtime = Some(create_runtime(
231 "ingest",
232 "ingest-worker",
233 options.ingest_rt_size,
234 ));
235 });
236}
237
238macro_rules! define_global_runtime_spawn {
239 ($type: ident) => {
240 paste! {
241 #[doc = "Returns the global `" $type "` thread pool."]
242 pub fn [<$type _runtime>]() -> Runtime {
243 GLOBAL_RUNTIMES.[<$type _runtime>].clone()
244 }
245
246 #[doc = "Spawn a future and execute it in `" $type "` thread pool."]
247 pub fn [<spawn_ $type>]<F>(future: F) -> JoinHandle<F::Output>
248 where
249 F: Future + Send + 'static,
250 F::Output: Send + 'static,
251 {
252 GLOBAL_RUNTIMES.[<spawn_ $type>](future)
253 }
254
255 #[doc = "Run the blocking operation in `" $type "` thread pool."]
256 pub fn [<spawn_blocking_ $type>]<F, R>(future: F) -> JoinHandle<R>
257 where
258 F: FnOnce() -> R + Send + 'static,
259 R: Send + 'static,
260 {
261 GLOBAL_RUNTIMES.[<spawn_blocking_ $type>](future)
262 }
263
264 #[doc = "Run a future to complete in `" $type "` thread pool."]
265 pub fn [<block_on_ $type>]<F: Future>(future: F) -> F::Output {
266 GLOBAL_RUNTIMES.[<block_on_ $type>](future)
267 }
268 }
269 };
270}
271
272define_global_runtime_spawn!(global);
273define_global_runtime_spawn!(compact);
274define_global_runtime_spawn!(hb);
275define_global_runtime_spawn!(query);
276define_global_runtime_spawn!(ingest);
277
278#[cfg(test)]
279mod tests {
280 use std::sync::mpsc;
281 use std::time::Duration;
282
283 use tokio_test::assert_ok;
284
285 use super::*;
286
287 #[test]
288 fn test_datanode_runtime_options_default() {
289 let options = RuntimeOptions::default();
290 let cpus = num_cpus::get();
291
292 assert_eq!(cpus, options.global_rt_size);
293 assert_eq!(usize::max(cpus / 2, 1), options.compact_rt_size);
294 assert_eq!(
295 usize::max(cpus / 2, 1),
296 options.compact_rt_max_blocking_threads
297 );
298 assert_eq!(usize::max(cpus.saturating_sub(1), 1), options.query_rt_size);
299 assert_eq!(cpus, options.ingest_rt_size);
300 }
301
302 #[test]
303 fn test_datanode_runtimes_fallback_to_global_runtime() {
304 let runtimes = GlobalRuntimes::new(
305 Some(create_runtime("test-global", "test-global-worker", 1)),
306 None,
307 None,
308 None,
309 None,
310 );
311
312 assert_eq!("test-global", runtimes.global_runtime.name());
313 assert_eq!("test-global", runtimes.query_runtime.name());
314 assert_eq!("test-global", runtimes.ingest_runtime.name());
315 }
316
317 #[test]
318 fn test_create_compact_runtime_with_zero_max_blocking_threads() {
319 let runtime = create_compact_runtime("test-compact", "test-compact-worker", 1, 0);
320 let handle = runtime.spawn_blocking(|| 1 + 1);
321
322 assert_eq!(2, runtime.block_on(handle).unwrap());
323 }
324
325 #[test]
326 fn test_compact_runtime_limits_blocking_threads() {
327 let runtime = create_compact_runtime("test-compact", "test-compact-worker", 1, 1);
328 let (first_started_tx, first_started_rx) = mpsc::channel();
329 let (release_first_tx, release_first_rx) = mpsc::channel();
330 let first = runtime.spawn_blocking(move || {
331 first_started_tx.send(()).unwrap();
332 release_first_rx.recv().unwrap();
333 });
334 first_started_rx
335 .recv_timeout(Duration::from_secs(5))
336 .unwrap();
337
338 let (second_started_tx, second_started_rx) = mpsc::channel();
339 let second = runtime.spawn_blocking(move || second_started_tx.send(()).unwrap());
340 assert!(
341 second_started_rx
342 .recv_timeout(Duration::from_secs(1))
343 .is_err()
344 );
345
346 release_first_tx.send(()).unwrap();
347 second_started_rx
348 .recv_timeout(Duration::from_secs(5))
349 .unwrap();
350 runtime.block_on(async {
351 first.await.unwrap();
352 second.await.unwrap();
353 });
354 }
355
356 #[test]
357 fn test_datanode_runtime_spawn_block_on() {
358 let handle = spawn_query(async { 1 + 1 });
359 assert_eq!(2, block_on_query(handle).unwrap());
360
361 let handle = spawn_ingest(async { 2 + 2 });
362 assert_eq!(4, block_on_ingest(handle).unwrap());
363 }
364
365 #[test]
366 fn test_spawn_block_on() {
367 let handle = spawn_global(async { 1 + 1 });
368 assert_eq!(2, block_on_global(handle).unwrap());
369
370 let handle = spawn_compact(async { 2 + 2 });
371 assert_eq!(4, block_on_compact(handle).unwrap());
372
373 let handle = spawn_hb(async { 4 + 4 });
374 assert_eq!(8, block_on_hb(handle).unwrap());
375 }
376
377 macro_rules! define_spawn_blocking_test {
378 ($type: ident) => {
379 paste! {
380 #[test]
381 fn [<test_spawn_ $type _from_blocking>]() {
382 let runtime = [<$type _runtime>]();
383 let out = runtime.block_on(async move {
384 let inner = assert_ok!(
385 [<spawn_blocking_ $type>](move || {
386 [<spawn_ $type>](async move { "hello" })
387 }).await
388 );
389
390 assert_ok!(inner.await)
391 });
392
393 assert_eq!(out, "hello")
394 }
395 }
396 };
397 }
398
399 define_spawn_blocking_test!(global);
400 define_spawn_blocking_test!(compact);
401 define_spawn_blocking_test!(hb);
402}