1#[cfg(test)]
4mod tests;
5
6use crate::thread_pool_manager::{PlottingThreadPoolManager, PlottingThreadPoolPair};
7use futures::channel::oneshot;
8use futures::channel::oneshot::Canceled;
9use futures::future::Either;
10use gdt_cpus::{AffinityMask, CpuInfo, ThreadPriority, set_thread_affinity, set_thread_priority};
11use rayon::{
12 ThreadBuilder, ThreadPool, ThreadPoolBuildError, ThreadPoolBuilder, current_thread_index,
13};
14use std::collections::HashMap;
15use std::future::Future;
16use std::num::NonZeroUsize;
17use std::ops::Deref;
18use std::pin::{Pin, pin};
19use std::process::exit;
20use std::sync::Arc;
21use std::task::{Context, Poll};
22use std::{fmt, io, iter, thread};
23use tokio::runtime::Handle;
24use tokio::task;
25use tracing::{debug, warn};
26
27const MAX_DEFAULT_FARMING_THREADS: usize = 32;
29
30#[derive(Debug)]
32pub struct AsyncJoinOnDrop<T> {
33 handle: Option<task::JoinHandle<T>>,
34 abort_on_drop: bool,
35}
36
37impl<T> Drop for AsyncJoinOnDrop<T> {
38 #[inline]
39 fn drop(&mut self) {
40 if let Some(handle) = self.handle.take() {
41 if self.abort_on_drop {
42 handle.abort();
43 }
44
45 if !handle.is_finished() {
46 task::block_in_place(move || {
47 let _: Result<_, _> = Handle::current().block_on(handle);
48 });
49 }
50 }
51 }
52}
53
54impl<T> AsyncJoinOnDrop<T> {
55 #[inline]
57 pub fn new(handle: task::JoinHandle<T>, abort_on_drop: bool) -> Self {
58 Self {
59 handle: Some(handle),
60 abort_on_drop,
61 }
62 }
63}
64
65impl<T> Future for AsyncJoinOnDrop<T> {
66 type Output = Result<T, task::JoinError>;
67
68 #[inline]
69 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
70 Pin::new(
71 self.handle
72 .as_mut()
73 .expect("Only dropped in Drop impl; qed"),
74 )
75 .poll(cx)
76 }
77}
78
79pub(crate) struct JoinOnDrop(Option<thread::JoinHandle<()>>);
81
82impl Drop for JoinOnDrop {
83 #[inline]
84 fn drop(&mut self) {
85 self.0
86 .take()
87 .expect("Always called exactly once; qed")
88 .join()
89 .expect("Panic if background thread panicked");
90 }
91}
92
93impl JoinOnDrop {
94 #[inline]
96 pub(crate) fn new(handle: thread::JoinHandle<()>) -> Self {
97 Self(Some(handle))
98 }
99}
100
101impl Deref for JoinOnDrop {
102 type Target = thread::JoinHandle<()>;
103
104 #[inline]
105 fn deref(&self) -> &Self::Target {
106 self.0.as_ref().expect("Only dropped in Drop impl; qed")
107 }
108}
109
110pub fn run_future_in_dedicated_thread<CreateFut, Fut, T>(
113 create_future: CreateFut,
114 thread_name: String,
115) -> io::Result<impl Future<Output = Result<T, Canceled>> + Send>
116where
117 CreateFut: (FnOnce() -> Fut) + Send + 'static,
118 Fut: Future<Output = T> + 'static,
119 T: Send + 'static,
120{
121 let (drop_tx, drop_rx) = oneshot::channel::<()>();
122 let (result_tx, result_rx) = oneshot::channel();
123 let handle = Handle::current();
124 let join_handle = thread::Builder::new().name(thread_name).spawn(move || {
125 let _tokio_handle_guard = handle.enter();
126
127 let future = pin!(create_future());
128
129 let result = match handle.block_on(futures::future::select(future, drop_rx)) {
130 Either::Left((result, _)) => result,
131 Either::Right(_) => {
132 return;
134 }
135 };
136 if let Err(_error) = result_tx.send(result) {
137 debug!(
138 thread_name = ?thread::current().name(),
139 "Future finished, but receiver was already dropped",
140 );
141 }
142 })?;
143 let join_on_drop = JoinOnDrop::new(join_handle);
145
146 Ok(async move {
147 let result = result_rx.await;
148 drop(drop_tx);
149 drop(join_on_drop);
150 result
151 })
152}
153
154#[derive(Clone)]
156pub struct CpuCoreSet {
157 affinity_mask: AffinityMask,
159 cpu_info: Option<Arc<CpuInfo>>,
160}
161
162impl fmt::Debug for CpuCoreSet {
163 #[inline]
164 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165 f.debug_struct("CpuCoreSet")
166 .field("affinity_mask", &self.affinity_mask)
167 .finish_non_exhaustive()
168 }
169}
170
171impl CpuCoreSet {
172 pub fn cpu_cores(&self) -> &AffinityMask {
174 &self.affinity_mask
175 }
176
177 pub fn truncate(&mut self, num_cores: usize) {
182 let num_cores = num_cores.clamp(1, self.affinity_mask.count());
183
184 let Some(cpu_info) = &self.cpu_info else {
185 self.affinity_mask = self.affinity_mask.iter().take(num_cores).collect();
186 return;
187 };
188
189 let mut grouped_by_l2_cache_size_and_core_count = HashMap::<_, Vec<_>>::new();
190 for domain in &cpu_info.l2_domains {
191 let domain_mask = domain.mask.intersection(&self.affinity_mask);
192
193 if domain_mask.is_empty() {
194 continue;
195 }
196
197 grouped_by_l2_cache_size_and_core_count
198 .entry((domain.size_bytes, domain_mask.count()))
199 .or_default()
200 .extend(domain_mask.iter());
201 }
202
203 if grouped_by_l2_cache_size_and_core_count
205 .values()
206 .flatten()
207 .count()
208 != self.affinity_mask.count()
209 {
210 self.affinity_mask = self.affinity_mask.iter().take(num_cores).collect();
211 return;
212 }
213
214 self.affinity_mask = grouped_by_l2_cache_size_and_core_count
218 .into_values()
219 .flat_map(|cores| {
220 let limit = (cores.len() * num_cores).div_ceil(self.affinity_mask.count());
221 cores.into_iter().take(limit)
223 })
224 .take(num_cores)
225 .collect();
226 }
227
228 pub fn pin_current_thread(&self) {
230 if let Err(error) = set_thread_affinity(&self.affinity_mask) {
231 warn!(%error, cpu_cores = ?self.affinity_mask, "Failed to pin thread to CPU cores");
232 }
233 }
234}
235
236pub fn recommended_number_of_farming_threads() -> usize {
239 match CpuInfo::detect() {
240 Ok(cpu_info) => (0..cpu_info.numa_node_count)
241 .map(|numa_node| cpu_info.numa_node_mask(numa_node).count())
242 .find(|&count| count > 0)
243 .unwrap_or_else(num_cpus::get)
244 .min(MAX_DEFAULT_FARMING_THREADS),
245 Err(error) => {
246 warn!(%error, "Failed to get CPU info");
247
248 num_cpus::get().min(MAX_DEFAULT_FARMING_THREADS)
249 }
250 }
251}
252
253pub fn all_cpu_cores() -> Vec<CpuCoreSet> {
258 match CpuInfo::detect() {
259 Ok(cpu_info) => {
260 let cpu_info = Arc::new(cpu_info);
261 let cpu_cores = cpu_info
262 .l3_domains
263 .iter()
264 .map(|domain| domain.mask)
265 .filter(|affinity_mask| !affinity_mask.is_empty())
266 .map(|affinity_mask| CpuCoreSet {
267 affinity_mask,
268 cpu_info: Some(Arc::clone(&cpu_info)),
269 })
270 .collect::<Vec<_>>();
271
272 if cpu_cores.is_empty() {
273 vec![CpuCoreSet {
274 affinity_mask: (0..num_cpus::get()).collect(),
275 cpu_info: Some(cpu_info),
276 }]
277 } else {
278 cpu_cores
279 }
280 }
281 Err(error) => {
282 warn!(%error, "Failed to get L3 cache topology");
283
284 vec![CpuCoreSet {
285 affinity_mask: (0..num_cpus::get()).collect(),
286 cpu_info: None,
287 }]
288 }
289 }
290}
291
292pub fn parse_cpu_cores_sets(
295 s: &str,
296) -> Result<Vec<CpuCoreSet>, Box<dyn std::error::Error + Send + Sync>> {
297 let cpu_info = if cfg!(miri) {
298 None
299 } else {
300 CpuInfo::detect().ok().map(Arc::new)
301 };
302
303 s.split(' ')
304 .map(|s| {
305 let mut cores = AffinityMask::empty();
306 for s in s.split(',') {
307 let mut parts = s.split('-');
308 let range_start = parts
309 .next()
310 .ok_or(
311 "Bad string format, must be comma separated list of CPU cores or ranges",
312 )?
313 .parse()?;
314
315 if let Some(range_end) = parts.next() {
316 let range_end = range_end.parse()?;
317
318 for core in range_start..=range_end {
321 cores.add(core);
322 }
323 } else {
324 cores.add(range_start);
325 }
326 }
327
328 Ok(CpuCoreSet {
329 affinity_mask: cores,
330 cpu_info: cpu_info.clone(),
331 })
332 })
333 .collect()
334}
335
336pub fn thread_pool_core_indices(
338 thread_pool_size: Option<NonZeroUsize>,
339 thread_pools: Option<NonZeroUsize>,
340) -> Vec<CpuCoreSet> {
341 thread_pool_core_indices_internal(all_cpu_cores(), thread_pool_size, thread_pools)
342}
343
344fn thread_pool_core_indices_internal(
345 all_cpu_cores: Vec<CpuCoreSet>,
346 thread_pool_size: Option<NonZeroUsize>,
347 thread_pools: Option<NonZeroUsize>,
348) -> Vec<CpuCoreSet> {
349 let cpu_info = &all_cpu_cores
350 .first()
351 .expect("Not empty according to function description; qed")
352 .cpu_info;
353
354 let thread_pools = thread_pools
357 .map(NonZeroUsize::get)
358 .or_else(|| thread_pool_size.map(|_| all_cpu_cores.len()));
359
360 if let Some(thread_pools) = thread_pools {
361 let all_cpu_cores = &all_cpu_cores;
362 let mut thread_pool_core_indices = Vec::<CpuCoreSet>::with_capacity(thread_pools);
363
364 let total_cpu_cores = all_cpu_cores.iter().flat_map(CpuCoreSet::cpu_cores).count();
365
366 if let Some(thread_pool_size) = thread_pool_size {
367 let mut cpu_cores_iterator = iter::repeat_with(|| {
370 all_cpu_cores
371 .iter()
372 .flat_map(|cpu_core_set| cpu_core_set.affinity_mask.iter())
373 })
374 .flatten();
375
376 for _ in 0..thread_pools {
377 let cpu_cores = cpu_cores_iterator
378 .by_ref()
379 .take(thread_pool_size.get())
380 .map(|core_index| core_index % total_cpu_cores)
383 .collect();
384
385 thread_pool_core_indices.push(CpuCoreSet {
386 affinity_mask: cpu_cores,
387 cpu_info: cpu_info.clone(),
388 });
389 }
390 } else {
391 let all_cpu_cores = all_cpu_cores
395 .iter()
396 .flat_map(|cpu_core_set| cpu_core_set.affinity_mask.iter())
397 .collect::<Vec<_>>();
398
399 thread_pool_core_indices = all_cpu_cores
400 .chunks(total_cpu_cores.div_ceil(thread_pools))
401 .map(|cpu_cores| CpuCoreSet {
402 affinity_mask: AffinityMask::from_cores(cpu_cores),
403 cpu_info: cpu_info.clone(),
404 })
405 .collect();
406 }
407 thread_pool_core_indices
408 } else {
409 all_cpu_cores
411 }
412}
413
414fn create_plotting_thread_pool_manager_thread_pool_pair(
415 thread_prefix: &'static str,
416 thread_pool_index: usize,
417 cpu_core_set: CpuCoreSet,
418 thread_priority: Option<ThreadPriority>,
419) -> Result<ThreadPool, ThreadPoolBuildError> {
420 let thread_name =
421 move |thread_index| format!("{thread_prefix}-{thread_pool_index}.{thread_index}");
422 let panic_handler = move |panic_info| {
426 if let Some(index) = current_thread_index() {
427 eprintln!("panic on thread {}: {:?}", thread_name(index), panic_info);
428 } else {
429 eprintln!("rayon panic handler called on non-rayon thread: {panic_info:?}");
431 }
432 exit(1);
433 };
434
435 ThreadPoolBuilder::new()
436 .thread_name(thread_name)
437 .num_threads(cpu_core_set.cpu_cores().count())
438 .panic_handler(panic_handler)
439 .spawn_handler({
440 let handle = Handle::current();
441
442 rayon_custom_spawn_handler(move |thread| {
443 let cpu_core_set = cpu_core_set.clone();
444 let handle = handle.clone();
445
446 move || {
447 cpu_core_set.pin_current_thread();
448 if let Some(thread_priority) = thread_priority
449 && let Err(error) = set_thread_priority(thread_priority)
450 {
451 warn!(%error, "Failed to set thread priority");
452 }
453 drop(cpu_core_set);
454
455 let _guard = handle.enter();
456
457 task::block_in_place(|| thread.run());
458 }
459 })
460 })
461 .build()
462}
463
464pub fn create_plotting_thread_pool_manager<I>(
475 mut cpu_core_sets: I,
476 thread_priority: Option<ThreadPriority>,
477) -> Result<PlottingThreadPoolManager, ThreadPoolBuildError>
478where
479 I: ExactSizeIterator<Item = (CpuCoreSet, CpuCoreSet)>,
480{
481 let total_thread_pools = cpu_core_sets.len();
482
483 PlottingThreadPoolManager::new(
484 |thread_pool_index| {
485 let (plotting_cpu_core_set, replotting_cpu_core_set) = cpu_core_sets
486 .next()
487 .expect("Number of thread pools is the same as cpu core sets; qed");
488
489 Ok(PlottingThreadPoolPair {
490 plotting: create_plotting_thread_pool_manager_thread_pool_pair(
491 "plotting",
492 thread_pool_index,
493 plotting_cpu_core_set,
494 thread_priority,
495 )?,
496 replotting: create_plotting_thread_pool_manager_thread_pool_pair(
497 "replotting",
498 thread_pool_index,
499 replotting_cpu_core_set,
500 thread_priority,
501 )?,
502 })
503 },
504 NonZeroUsize::new(total_thread_pools)
505 .expect("Thread pool is guaranteed to be non-empty; qed"),
506 )
507}
508
509pub fn rayon_custom_spawn_handler<SpawnHandlerBuilder, SpawnHandler, SpawnHandlerResult>(
515 mut spawn_handler_builder: SpawnHandlerBuilder,
516) -> impl FnMut(ThreadBuilder) -> io::Result<()>
517where
518 SpawnHandlerBuilder: (FnMut(ThreadBuilder) -> SpawnHandler) + Clone,
519 SpawnHandler: (FnOnce() -> SpawnHandlerResult) + Send + 'static,
520 SpawnHandlerResult: Send + 'static,
521{
522 move |thread: ThreadBuilder| {
523 let mut b = thread::Builder::new();
524 if let Some(name) = thread.name() {
525 b = b.name(name.to_owned());
526 }
527 if let Some(stack_size) = thread.stack_size() {
528 b = b.stack_size(stack_size);
529 }
530
531 b.spawn(spawn_handler_builder(thread))?;
532 Ok(())
533 }
534}
535
536pub fn tokio_rayon_spawn_handler() -> impl FnMut(ThreadBuilder) -> io::Result<()> {
539 let handle = Handle::current();
540
541 rayon_custom_spawn_handler(move |thread| {
542 let handle = handle.clone();
543
544 move || {
545 let _guard = handle.enter();
546
547 task::block_in_place(|| thread.run());
548 }
549 })
550}