1 // Copyright (c) 2023 Huawei Device Co., Ltd.
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 //     http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13 
14 //! A builder to configure the runtime, and thread pool of the runtime.
15 //!
16 //! Ylong-runtime provides two kinds of runtime.
17 //! `CurrentThread`: Runtime which runs on the current thread.
18 //! `MultiThread`: Runtime which runs on multiple threads.
19 //!
20 //! After configuring the builder, a call to `build` will return the actual
21 //! runtime instance. [`MultiThreadBuilder`] could also be used for configuring
22 //! the global singleton runtime.
23 //!
24 //! For thread pool, the builder allows the user to set the thread number, stack
25 //! size and name prefix of each thread.
26 
27 pub(crate) mod common_builder;
28 #[cfg(feature = "current_thread_runtime")]
29 pub(crate) mod current_thread_builder;
30 pub(crate) mod multi_thread_builder;
31 
32 use std::fmt::Debug;
33 use std::sync::Arc;
34 
35 #[cfg(feature = "current_thread_runtime")]
36 pub use current_thread_builder::CurrentThreadBuilder;
37 pub use multi_thread_builder::MultiThreadBuilder;
38 
39 pub(crate) use crate::builder::common_builder::CommonBuilder;
40 
41 cfg_not_ffrt!(
42     use crate::error::ScheduleError;
43     use crate::executor::async_pool::AsyncPoolSpawner;
44     use crate::executor::blocking_pool::BlockPoolSpawner;
45     use std::io;
46 );
47 
48 /// A callback function to be executed in different stages of a thread's
49 /// life-cycle
50 pub type CallbackHook = Arc<dyn Fn() + Send + Sync + 'static>;
51 
52 /// Schedule Policy.
53 #[derive(Debug, Clone, Copy, PartialOrd, PartialEq, Eq)]
54 pub enum ScheduleAlgo {
55     /// Bounded local queues which adopts FIFO order.
56     FifoBound,
57 }
58 
59 /// Builder to build the runtime. Provides methods to customize the runtime,
60 /// such as setting thread pool size, worker thread stack size, work thread
61 /// prefix and etc.
62 ///
63 /// If `multi_instance_runtime` or `current_thread_runtime` feature is turned
64 /// on: After setting the RuntimeBuilder, a call to build will initialize the
65 /// actual runtime and returns its instance. If there is an invalid parameter
66 /// during the build, an error would be returned.
67 ///
68 /// Otherwise:
69 /// RuntimeBuilder will not have the `build()` method, instead, this builder
70 /// should be passed to set the global executor.
71 ///
72 /// # Examples
73 ///
74 /// ```no run
75 /// #![cfg(feature = "multi_instance_runtime")]
76 ///
77 /// use ylong_runtime::builder::RuntimeBuilder;
78 /// use ylong_runtime::executor::Runtime;
79 ///
80 /// let runtime = RuntimeBuilder::new_multi_thread()
81 ///     .worker_num(4)
82 ///     .worker_stack_size(1024 * 300)
83 ///     .build()
84 ///     .unwrap();
85 /// ```
86 pub struct RuntimeBuilder;
87 
88 impl RuntimeBuilder {
89     /// Initializes a new RuntimeBuilder with current_thread settings.
90     ///
91     /// All tasks will run on the current thread, which means it does not create
92     /// any other worker threads.
93     ///
94     /// # Examples
95     ///
96     /// ```
97     /// use ylong_runtime::builder::RuntimeBuilder;
98     ///
99     /// let builder = RuntimeBuilder::new_current_thread()
100     ///     .worker_stack_size(1024 * 3)
101     ///     .max_blocking_pool_size(4);
102     /// ```
103     #[cfg(feature = "current_thread_runtime")]
new_current_thread() -> CurrentThreadBuilder104     pub fn new_current_thread() -> CurrentThreadBuilder {
105         CurrentThreadBuilder::new()
106     }
107 
108     /// Initializes a new RuntimeBuilder with multi_thread settings.
109     ///
110     /// When running, worker threads will be created according to the builder
111     /// configuration, and tasks will be allocated and run in the newly
112     /// created thread pool.
113     ///
114     /// # Examples
115     ///
116     /// ```
117     /// use ylong_runtime::builder::RuntimeBuilder;
118     ///
119     /// let builder = RuntimeBuilder::new_multi_thread();
120     /// ```
new_multi_thread() -> MultiThreadBuilder121     pub fn new_multi_thread() -> MultiThreadBuilder {
122         MultiThreadBuilder::new()
123     }
124 }
125 
126 cfg_not_ffrt! {
127     pub(crate) fn initialize_async_spawner(
128         builder: &MultiThreadBuilder,
129     ) -> io::Result<AsyncPoolSpawner> {
130         AsyncPoolSpawner::new(builder)
131     }
132 
133     pub(crate) fn initialize_blocking_spawner(
134         builder: &CommonBuilder,
135     ) -> Result<BlockPoolSpawner, ScheduleError> {
136         let blocking_spawner = BlockPoolSpawner::new(builder);
137         blocking_spawner.create_permanent_threads()?;
138         Ok(blocking_spawner)
139     }
140 }
141 
142 #[cfg(test)]
143 mod test {
144     use crate::builder::RuntimeBuilder;
145     #[cfg(not(feature = "ffrt"))]
146     use crate::builder::ScheduleAlgo;
147 
148     /// UT test cases for RuntimeBuilder::new_multi_thread()
149     ///
150     /// # Brief
151     /// 1. Checks if the object name property is None
152     /// 2. Checks if the object core_pool_size property is None
153     /// 3. Checks if the object is_steal property is true
154     /// 4. Checks if the object is_affinity property is true
155     /// 5. Checks if the object permanent_blocking_thread_num property is 4
156     /// 6. Checks if the object max_pool_size property is Some(50)
157     /// 7. Checks if the object keep_alive_time property is None
158     /// 8. Checks if the object schedule_algo property is
159     ///    ScheduleAlgo::FifoBound
160     /// 9. Checks if the object stack_size property is None
161     /// 10. Checks if the object after_start property is None
162     /// 11. Checks if the object before_stop property is None
163     #[test]
ut_thread_pool_builder_new()164     fn ut_thread_pool_builder_new() {
165         let thread_pool_builder = RuntimeBuilder::new_multi_thread();
166         assert_eq!(thread_pool_builder.common.worker_name, None);
167         #[cfg(not(feature = "ffrt"))]
168         {
169             assert_eq!(thread_pool_builder.common.blocking_permanent_thread_num, 0);
170             assert_eq!(thread_pool_builder.common.max_blocking_pool_size, None);
171             assert_eq!(thread_pool_builder.common.keep_alive_time, None);
172             assert_eq!(thread_pool_builder.core_thread_size, None);
173             assert_eq!(thread_pool_builder.common.stack_size, None);
174             assert_eq!(
175                 thread_pool_builder.common.schedule_algo,
176                 ScheduleAlgo::FifoBound
177             );
178         }
179     }
180 
181     /// UT test cases for RuntimeBuilder::name()
182     ///
183     /// # Brief
184     /// 1. Checks if the object name property is modified value
185     #[test]
ut_thread_pool_builder_name()186     fn ut_thread_pool_builder_name() {
187         let name = String::from("worker_name");
188         let thread_pool_builder = RuntimeBuilder::new_multi_thread().worker_name(name.clone());
189         assert_eq!(thread_pool_builder.common.worker_name, Some(name));
190     }
191 
192     /// UT test cases for RuntimeBuilder::core_pool_size()
193     ///
194     /// # Brief
195     /// 1. core_pool_size set to 1, Check if the return value is Some(1)
196     /// 2. core_pool_size set to 64, Check if the return value is Some(64)
197     /// 3. core_pool_size set to 0, Check if the return value is Some(1)
198     /// 4. core_pool_size set to 65, Check if the return value is Some(64)
199     #[test]
200     #[cfg(not(feature = "ffrt"))]
ut_thread_pool_builder_core_pool_size()201     fn ut_thread_pool_builder_core_pool_size() {
202         let thread_pool_builder = RuntimeBuilder::new_multi_thread().worker_num(1);
203         assert_eq!(thread_pool_builder.core_thread_size, Some(1));
204 
205         let thread_pool_builder = RuntimeBuilder::new_multi_thread().worker_num(64);
206         assert_eq!(thread_pool_builder.core_thread_size, Some(64));
207 
208         let thread_pool_builder = RuntimeBuilder::new_multi_thread().worker_num(0);
209         assert_eq!(thread_pool_builder.core_thread_size, Some(1));
210 
211         let thread_pool_builder = RuntimeBuilder::new_multi_thread().worker_num(65);
212         assert_eq!(thread_pool_builder.core_thread_size, Some(64));
213     }
214 
215     /// UT test cases for RuntimeBuilder::stack_size()
216     ///
217     /// # Brief
218     /// 1. stack_size set to 0, Check if the return value is Some(1)
219     /// 2. stack_size set to 1, Check if the return value is Some(1)
220     #[test]
221     #[cfg(not(feature = "ffrt"))]
ut_thread_pool_builder_stack_size()222     fn ut_thread_pool_builder_stack_size() {
223         let thread_pool_builder = RuntimeBuilder::new_multi_thread().worker_stack_size(0);
224         assert_eq!(thread_pool_builder.common.stack_size.unwrap(), 1);
225 
226         let thread_pool_builder = RuntimeBuilder::new_multi_thread().worker_stack_size(1);
227         assert_eq!(thread_pool_builder.common.stack_size.unwrap(), 1);
228     }
229 }
230 
231 #[cfg(test)]
232 #[cfg(feature = "current_thread_runtime")]
233 mod current_thread_test {
234     use crate::builder::RuntimeBuilder;
235 
236     /// UT test cases for new_current_thread.
237     ///
238     /// # Brief
239     /// 1. Verify the result when multiple tasks are inserted to the current
240     ///    thread at a time.
241     /// 2. Insert the task for multiple times, wait until the task is complete,
242     ///    verify the result, and then perform the operation again.
243     /// 3. Spawn nest thread.
244     #[test]
ut_thread_pool_builder_current_thread()245     fn ut_thread_pool_builder_current_thread() {
246         let runtime = RuntimeBuilder::new_current_thread().build().unwrap();
247         let mut handles = vec![];
248         for index in 0..1000 {
249             let handle = runtime.spawn(async move { index });
250             handles.push(handle);
251         }
252         for (index, handle) in handles.into_iter().enumerate() {
253             let result = runtime.block_on(handle).unwrap();
254             assert_eq!(result, index);
255         }
256 
257         let runtime = RuntimeBuilder::new_current_thread().build().unwrap();
258         for index in 0..1000 {
259             let handle = runtime.spawn(async move { index });
260             let result = runtime.block_on(handle).unwrap();
261             assert_eq!(result, index);
262         }
263 
264         let runtime = RuntimeBuilder::new_current_thread().build().unwrap();
265         let handle = runtime.spawn_blocking(|| {
266             let runtime = RuntimeBuilder::new_current_thread().build().unwrap();
267             let handle = runtime.spawn(async move { 1_usize });
268             let result = runtime.block_on(handle).unwrap();
269             assert_eq!(result, 1);
270             result
271         });
272         let result = runtime.block_on(handle).unwrap();
273         assert_eq!(result, 1);
274     }
275 }
276 
277 #[cfg(not(feature = "ffrt"))]
278 #[cfg(test)]
279 mod ylong_executor_test {
280     use crate::builder::{RuntimeBuilder, ScheduleAlgo};
281     use crate::util::num_cpus::get_cpu_num;
282 
283     /// UT test cases for ThreadPoolBuilder::is_affinity()
284     ///
285     /// # Brief
286     /// 1. is_affinity set to true, check if it is a modified value
287     /// 2. is_affinity set to false, check if it is a modified value
288     #[test]
ut_thread_pool_builder_is_affinity()289     fn ut_thread_pool_builder_is_affinity() {
290         let thread_pool_builder = RuntimeBuilder::new_multi_thread().is_affinity(true);
291         assert!(thread_pool_builder.common.is_affinity);
292 
293         let thread_pool_builder = RuntimeBuilder::new_multi_thread().is_affinity(false);
294         assert!(!thread_pool_builder.common.is_affinity);
295     }
296 
297     /// UT test cases for RuntimeBuilder::blocking_permanent_thread_num()
298     ///
299     /// # Brief
300     /// 1. permanent_blocking_thread_num set to 1, check if the return value is
301     ///    1.
302     /// 2. permanent_blocking_thread_num set to max_thread_num, check if the
303     ///    return value is max_blocking_pool_size.
304     /// 3. permanent_blocking_thread_num set to 0, check if the return value is
305     ///    1.
306     /// 4. permanent_blocking_thread_num set to max_thread_num + 1, Check if the
307     ///    return value O is max_blocking_pool_size.
308     #[test]
ut_thread_pool_builder_permanent_blocking_thread_num()309     fn ut_thread_pool_builder_permanent_blocking_thread_num() {
310         let thread_pool_builder =
311             RuntimeBuilder::new_multi_thread().blocking_permanent_thread_num(1);
312         assert_eq!(thread_pool_builder.common.blocking_permanent_thread_num, 1);
313 
314         let blocking_permanent_thread_num = get_cpu_num() as u8;
315         let thread_pool_builder = RuntimeBuilder::new_multi_thread()
316             .blocking_permanent_thread_num(blocking_permanent_thread_num);
317         assert_eq!(
318             thread_pool_builder.common.blocking_permanent_thread_num,
319             blocking_permanent_thread_num
320         );
321 
322         let thread_pool_builder =
323             RuntimeBuilder::new_multi_thread().blocking_permanent_thread_num(0);
324         assert_eq!(thread_pool_builder.common.blocking_permanent_thread_num, 0);
325 
326         let permanent_blocking_thread_num = get_cpu_num() as u8 + 1;
327         let thread_pool_builder = RuntimeBuilder::new_multi_thread()
328             .blocking_permanent_thread_num(permanent_blocking_thread_num);
329         assert_eq!(
330             thread_pool_builder.common.blocking_permanent_thread_num,
331             permanent_blocking_thread_num
332         );
333     }
334 
335     /// UT test cases for RuntimeBuilder::max_pool_size()
336     ///
337     /// # Brief
338     /// 1. max_pool_size set to 1, check if the return value is Some(1)
339     /// 2. max_pool_size set to 64, check if the return value is Some(64)
340     /// 3. max_pool_size set to 0, check if the return value is Some(1)
341     /// 4. max_pool_size set to 65, check if the return value is Some(64)
342     #[test]
ut_thread_pool_builder_max_pool_size()343     fn ut_thread_pool_builder_max_pool_size() {
344         let thread_pool_builder = RuntimeBuilder::new_multi_thread().max_blocking_pool_size(1);
345         assert_eq!(
346             thread_pool_builder.common.max_blocking_pool_size.unwrap(),
347             1
348         );
349 
350         let thread_pool_builder = RuntimeBuilder::new_multi_thread().max_blocking_pool_size(64);
351         assert_eq!(
352             thread_pool_builder.common.max_blocking_pool_size.unwrap(),
353             64
354         );
355 
356         let thread_pool_builder = RuntimeBuilder::new_multi_thread().max_blocking_pool_size(0);
357         assert_eq!(
358             thread_pool_builder.common.max_blocking_pool_size.unwrap(),
359             1
360         );
361 
362         let thread_pool_builder = RuntimeBuilder::new_multi_thread().max_blocking_pool_size(65);
363         assert_eq!(
364             thread_pool_builder.common.max_blocking_pool_size.unwrap(),
365             64
366         );
367     }
368 
369     /// UT test cases for RuntimeBuilder::keep_alive_time()
370     ///
371     /// # Brief
372     /// 1. keep_alive_time set to 0, check if the return value is
373     ///    Some(Duration::from_secs(0))
374     /// 2. keep_alive_time set to 1, check if the return value is
375     ///    Some(Duration::from_secs(1))
376     #[test]
ut_thread_pool_builder_keep_alive_time()377     fn ut_thread_pool_builder_keep_alive_time() {
378         use std::time::Duration;
379 
380         let keep_alive_time = Duration::from_secs(0);
381         let thread_pool_builder =
382             RuntimeBuilder::new_multi_thread().keep_alive_time(keep_alive_time);
383         assert_eq!(
384             thread_pool_builder.common.keep_alive_time.unwrap(),
385             keep_alive_time
386         );
387 
388         let keep_alive_time = Duration::from_secs(1);
389         let thread_pool_builder =
390             RuntimeBuilder::new_multi_thread().keep_alive_time(keep_alive_time);
391         assert_eq!(
392             thread_pool_builder.common.keep_alive_time.unwrap(),
393             keep_alive_time
394         );
395     }
396 
397     /// UT test cases for RuntimeBuilder::schedule_algo()
398     ///
399     /// # Brief
400     /// 1. schedule_algo set to FifoBound, check if it is the modified value
401     #[cfg(not(feature = "ffrt"))]
402     #[test]
ut_thread_pool_builder_schedule_algo_test()403     fn ut_thread_pool_builder_schedule_algo_test() {
404         let schedule_algo = ScheduleAlgo::FifoBound;
405         let thread_pool_builder = RuntimeBuilder::new_multi_thread().schedule_algo(schedule_algo);
406         assert_eq!(thread_pool_builder.common.schedule_algo, schedule_algo);
407     }
408 }
409