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