Skip to main content

common_runtime/
global.rs

1// Copyright 2023 Greptime Team
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
15//! Global runtimes
16use 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/// The options for the global runtimes.
32#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
33#[serde(default)]
34pub struct RuntimeOptions {
35    /// The number of threads for the global default runtime.
36    pub global_rt_size: usize,
37    /// The number of threads to execute the runtime for compact operations.
38    pub compact_rt_size: usize,
39    /// The maximum number of blocking threads for compact operations.
40    pub compact_rt_max_blocking_threads: usize,
41    /// The number of threads to execute datanode query operations.
42    pub query_rt_size: usize,
43    /// The number of threads to execute datanode ingestion operations.
44    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
190/// Initialize the global runtimes
191///
192/// # Panics
193/// Panics when the global runtimes are already initialized.
194/// You should call this function before using any runtime functions.
195pub 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
215/// Initialize the datanode-specific global runtimes.
216///
217/// # Panics
218/// Panics when the global runtimes are already initialized.
219/// You should call this function before using any runtime functions.
220pub 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}