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 use std::io; 15 use std::sync::Mutex; 16 17 cfg_ffrt!( 18 use ylong_ffrt::{ffrt_set_cpu_worker_max_num, ffrt_set_worker_stack_size, Qos}; 19 use std::collections::HashMap; 20 use libc::{c_uint, c_ulong}; 21 ); 22 23 use crate::builder::common_builder::impl_common; 24 use crate::builder::CommonBuilder; 25 #[cfg(feature = "multi_instance_runtime")] 26 use crate::executor::{AsyncHandle, Runtime}; 27 28 pub(crate) static GLOBAL_BUILDER: Mutex<Option<MultiThreadBuilder>> = Mutex::new(None); 29 30 /// Runtime builder that configures a multi-threaded runtime, or the global 31 /// runtime. 32 pub struct MultiThreadBuilder { 33 pub(crate) common: CommonBuilder, 34 35 #[cfg(not(feature = "ffrt"))] 36 /// Maximum thread number for core thread pool 37 pub(crate) core_thread_size: Option<usize>, 38 39 #[cfg(feature = "ffrt")] 40 /// Thread number for each qos 41 pub(crate) thread_num_by_qos: HashMap<Qos, u32>, 42 43 #[cfg(feature = "ffrt")] 44 /// Thread stack size for each qos 45 pub(crate) stack_size_by_qos: HashMap<Qos, usize>, 46 } 47 48 impl MultiThreadBuilder { new() -> Self49 pub(crate) fn new() -> Self { 50 MultiThreadBuilder { 51 common: CommonBuilder::new(), 52 #[cfg(not(feature = "ffrt"))] 53 core_thread_size: None, 54 #[cfg(feature = "ffrt")] 55 thread_num_by_qos: HashMap::new(), 56 #[cfg(feature = "ffrt")] 57 stack_size_by_qos: HashMap::new(), 58 } 59 } 60 61 /// Configures the global runtime. 62 /// 63 /// # Error 64 /// If the global runtime is already running or this method has been called 65 /// before, then it will return an `AlreadyExists` error. build_global(self) -> io::Result<()>66 pub fn build_global(self) -> io::Result<()> { 67 let mut builder = GLOBAL_BUILDER.lock().unwrap(); 68 if builder.is_some() { 69 return Err(io::ErrorKind::AlreadyExists.into()); 70 } 71 72 #[cfg(feature = "ffrt")] 73 unsafe { 74 for (qos, worker_num) in self.thread_num_by_qos.iter() { 75 ffrt_set_cpu_worker_max_num(*qos, *worker_num as c_uint); 76 } 77 78 for (qos, stack_size) in self.thread_num_by_qos.iter() { 79 ffrt_set_worker_stack_size(*qos, *stack_size as c_ulong); 80 } 81 } 82 83 *builder = Some(self); 84 Ok(()) 85 } 86 } 87 88 #[cfg(feature = "ffrt")] 89 impl MultiThreadBuilder { 90 /// Sets the maximum worker number for a specific qos group. 91 /// 92 /// If a worker number has already been set for a qos, calling the method 93 /// with the same qos will overwrite the old value. 94 /// 95 /// # Error 96 /// The accepted worker number range for each qos is [1, 20]. If 0 is passed 97 /// in, then the maximum worker number will be set to 1. If a number 98 /// greater than 20 is passed in, then the maximum worker number will be 99 /// set to 20. max_worker_num_by_qos(mut self, qos: Qos, num: u32) -> Self100 pub fn max_worker_num_by_qos(mut self, qos: Qos, num: u32) -> Self { 101 let worker = match num { 102 0 => 1, 103 n if n > 20 => 20, 104 n => n, 105 }; 106 self.thread_num_by_qos.insert(qos, worker); 107 self 108 } 109 110 /// Sets the thread stack size for a specific qos group. 111 /// 112 /// If a stack size has already been set for a qos, calling the method 113 /// with the same qos will overwrite the old value 114 /// 115 /// # Error 116 /// The lowest accepted stack size is 16k. If a value under 16k is passed 117 /// in, then the stack size will be set to 16k instead. stack_size_by_qos(mut self, qos: Qos, stack_size: usize) -> Self118 pub fn stack_size_by_qos(mut self, qos: Qos, stack_size: usize) -> Self { 119 const PTHREAD_STACK_MIN: usize = 16 * 1000; 120 121 let stack_size = match stack_size { 122 n if n < PTHREAD_STACK_MIN => PTHREAD_STACK_MIN, 123 n => n, 124 }; 125 self.stack_size_by_qos.insert(qos, stack_size); 126 self 127 } 128 } 129 130 #[cfg(not(feature = "ffrt"))] 131 impl MultiThreadBuilder { 132 /// Initializes the runtime and returns its instance. 133 #[cfg(feature = "multi_instance_runtime")] build(&mut self) -> io::Result<Runtime>134 pub fn build(&mut self) -> io::Result<Runtime> { 135 use crate::builder::initialize_async_spawner; 136 let async_spawner = initialize_async_spawner(self)?; 137 138 Ok(Runtime { 139 async_spawner: AsyncHandle::MultiThread(async_spawner), 140 }) 141 } 142 143 /// Sets the number of core worker threads. 144 /// 145 /// 146 /// The boundary of thread number is 1-64: 147 /// If sets a number smaller than 1, then thread number would be set to 1. 148 /// If sets a number larger than 64, then thread number would be set to 64. 149 /// The default value is the number of cores of the cpu. 150 /// 151 /// # Examples 152 /// ``` 153 /// use crate::ylong_runtime::builder::RuntimeBuilder; 154 /// 155 /// let runtime = RuntimeBuilder::new_multi_thread().worker_num(8); 156 /// ``` worker_num(mut self, core_pool_size: usize) -> Self157 pub fn worker_num(mut self, core_pool_size: usize) -> Self { 158 if core_pool_size < 1 { 159 self.core_thread_size = Some(1); 160 } else if core_pool_size > 64 { 161 self.core_thread_size = Some(64); 162 } else { 163 self.core_thread_size = Some(core_pool_size); 164 } 165 self 166 } 167 } 168 169 impl_common!(MultiThreadBuilder); 170 171 #[cfg(feature = "full")] 172 #[cfg(test)] 173 mod test { 174 use crate::builder::RuntimeBuilder; 175 use crate::executor::{global_default_async, AsyncHandle}; 176 177 /// UT test cases for blocking on a time sleep without initializing the 178 /// runtime. 179 /// 180 /// # Brief 181 /// 1. Configure the global runtime to make it have six core threads 182 /// 2. Get the global runtime 183 /// 3. Check the core thread number of the runtime 184 /// 4. Call build_global once more 185 /// 5. Check the error 186 #[test] ut_build_global()187 fn ut_build_global() { 188 let ret = RuntimeBuilder::new_multi_thread() 189 .worker_num(6) 190 .max_blocking_pool_size(3) 191 .build_global(); 192 assert!(ret.is_ok()); 193 194 let async_pool = global_default_async(); 195 match &async_pool.async_spawner { 196 AsyncHandle::CurrentThread(_) => unreachable!(), 197 AsyncHandle::MultiThread(x) => { 198 assert_eq!(x.inner.total, 6); 199 } 200 } 201 202 let ret = RuntimeBuilder::new_multi_thread() 203 .worker_num(2) 204 .max_blocking_pool_size(3) 205 .build_global(); 206 assert!(ret.is_err()); 207 } 208 } 209 210 #[cfg(feature = "ffrt")] 211 #[cfg(test)] 212 mod ffrt_test { 213 use ylong_ffrt::Qos::{Default, UserInitiated, UserInteractive}; 214 215 use crate::builder::MultiThreadBuilder; 216 217 /// UT test cases for max_worker_num_by_qos 218 /// 219 /// # Brief 220 /// 1. Sets UserInteractive qos group to have 0 maximum worker number. 221 /// 2. Checks if the actual value is 1 222 /// 3. Sets UserInteractive qos group to have 21 maximum worker number. 223 /// 4. Checks if the actual value is 20 224 /// 5. Set Default qos group to have 8 maximum worker number. 225 /// 6. Checks if the actual value is 8. 226 /// 7. Calls build_global on the builder, check if the return value is Ok 227 #[test] ut_set_max_worker()228 fn ut_set_max_worker() { 229 let builder = MultiThreadBuilder::new(); 230 let builder = builder.max_worker_num_by_qos(UserInteractive, 0); 231 let num = builder.thread_num_by_qos.get(&UserInteractive).unwrap(); 232 assert_eq!(*num, 1); 233 234 let builder = builder.max_worker_num_by_qos(UserInteractive, 21); 235 let num = builder.thread_num_by_qos.get(&UserInteractive).unwrap(); 236 assert_eq!(*num, 20); 237 238 let builder = MultiThreadBuilder::new().max_worker_num_by_qos(Default, 8); 239 let num = builder.thread_num_by_qos.get(&Default).unwrap(); 240 assert_eq!(*num, 8); 241 } 242 243 /// UT cases for stack_size_by_qos 244 /// 245 /// # Brief 246 /// 1. Sets UserInitiated qos group's stack size to 16k - 1 247 /// 2. Checks if the actual stack size is 16k 248 /// 3. Sets UserInteractive qos group's stack size to 16k 249 /// 4. Checks if the actual stack size is 16k 250 /// 5. Sets Default qos group's stack size to 16M 251 /// 6. Checks if the actual stack size is 16M 252 /// 7. Sets UserInteractive qos group's stack size to 16k + 1 253 /// 8. Checks if the actual stack size is 16k + 1 254 #[test] ut_set_stack_size()255 fn ut_set_stack_size() { 256 let builder = MultiThreadBuilder::new(); 257 let builder = builder.stack_size_by_qos(UserInitiated, 16 * 1000 - 1); 258 let num = builder.stack_size_by_qos.get(&UserInitiated).unwrap(); 259 assert_eq!(*num, 16 * 1000); 260 261 let builder = MultiThreadBuilder::new(); 262 let builder = builder.stack_size_by_qos(UserInteractive, 16 * 1000); 263 let num = builder.stack_size_by_qos.get(&UserInteractive).unwrap(); 264 assert_eq!(*num, 16 * 1000); 265 266 let builder = MultiThreadBuilder::new(); 267 let builder = builder.stack_size_by_qos(Default, 16 * 1000 * 1000); 268 let num = builder.stack_size_by_qos.get(&Default).unwrap(); 269 assert_eq!(*num, 16 * 1000 * 1000); 270 271 let builder = MultiThreadBuilder::new(); 272 let builder = builder.stack_size_by_qos(UserInteractive, 16 * 1000 + 1); 273 let num = builder.stack_size_by_qos.get(&UserInteractive).unwrap(); 274 assert_eq!(*num, 16 * 1000 + 1); 275 } 276 } 277