@@ -279,10 +279,15 @@ pub fn available_parallelism() -> io::Result<NonZeroUsize> {
279
279
) ) ] {
280
280
#[ cfg( any( target_os = "android" , target_os = "linux" ) ) ]
281
281
{
282
+ let quota = cgroup2_quota( ) . max( 1 ) ;
282
283
let mut set: libc:: cpu_set_t = unsafe { mem:: zeroed( ) } ;
283
- if unsafe { libc:: sched_getaffinity( 0 , mem:: size_of:: <libc:: cpu_set_t>( ) , & mut set) } == 0 {
284
- let count = unsafe { libc:: CPU_COUNT ( & set) } ;
285
- return Ok ( unsafe { NonZeroUsize :: new_unchecked( count as usize ) } ) ;
284
+ unsafe {
285
+ if libc:: sched_getaffinity( 0 , mem:: size_of:: <libc:: cpu_set_t>( ) , & mut set) == 0 {
286
+ let count = libc:: CPU_COUNT ( & set) as usize ;
287
+ let count = count. min( quota) ;
288
+ // SAFETY: affinity mask can't be empty and the quota gets clamped to a minimum of 1
289
+ return Ok ( NonZeroUsize :: new_unchecked( count) ) ;
290
+ }
286
291
}
287
292
}
288
293
match unsafe { libc:: sysconf( libc:: _SC_NPROCESSORS_ONLN) } {
@@ -368,6 +373,80 @@ pub fn available_parallelism() -> io::Result<NonZeroUsize> {
368
373
}
369
374
}
370
375
376
+ /// Returns cgroup CPU quota in core-equivalents, rounded down, or usize::MAX if the quota cannot
377
+ /// be determined or is not set.
378
+ #[ cfg( any( target_os = "android" , target_os = "linux" ) ) ]
379
+ fn cgroup2_quota ( ) -> usize {
380
+ use crate :: ffi:: OsString ;
381
+ use crate :: fs:: { try_exists, File } ;
382
+ use crate :: io:: Read ;
383
+ use crate :: os:: unix:: ffi:: OsStringExt ;
384
+ use crate :: path:: PathBuf ;
385
+
386
+ let mut quota = usize:: MAX ;
387
+
388
+ let _: Option < ( ) > = try {
389
+ let mut buf = Vec :: with_capacity ( 128 ) ;
390
+ // find our place in the cgroup hierarchy
391
+ File :: open ( "/proc/self/cgroup" ) . ok ( ) ?. read_to_end ( & mut buf) . ok ( ) ?;
392
+ let cgroup_path = buf
393
+ . split ( |& c| c == b'\n' )
394
+ . filter_map ( |line| {
395
+ let mut fields = line. splitn ( 3 , |& c| c == b':' ) ;
396
+ // expect cgroupv2 which has an empty 2nd field
397
+ if fields. nth ( 1 ) != Some ( b"" ) {
398
+ return None ;
399
+ }
400
+ let path = fields. last ( ) ?;
401
+ // skip leading slash
402
+ Some ( path[ 1 ..] . to_owned ( ) )
403
+ } )
404
+ . next ( ) ?;
405
+ let cgroup_path = PathBuf :: from ( OsString :: from_vec ( cgroup_path) ) ;
406
+
407
+ let mut path = PathBuf :: with_capacity ( 128 ) ;
408
+ let mut read_buf = String :: with_capacity ( 20 ) ;
409
+
410
+ let cgroup_mount = "/sys/fs/cgroup" ;
411
+
412
+ path. push ( cgroup_mount) ;
413
+ path. push ( & cgroup_path) ;
414
+
415
+ path. push ( "cgroup.controllers" ) ;
416
+
417
+ // skip if we're not looking at cgroup2
418
+ if matches ! ( try_exists( & path) , Err ( _) | Ok ( false ) ) {
419
+ return usize:: MAX ;
420
+ } ;
421
+
422
+ path. pop ( ) ;
423
+
424
+ while path. starts_with ( cgroup_mount) {
425
+ path. push ( "cpu.max" ) ;
426
+
427
+ read_buf. clear ( ) ;
428
+
429
+ if File :: open ( & path) . and_then ( |mut f| f. read_to_string ( & mut read_buf) ) . is_ok ( ) {
430
+ let raw_quota = read_buf. lines ( ) . next ( ) ?;
431
+ let mut raw_quota = raw_quota. split ( ' ' ) ;
432
+ let limit = raw_quota. next ( ) ?;
433
+ let period = raw_quota. next ( ) ?;
434
+ match ( limit. parse :: < usize > ( ) , period. parse :: < usize > ( ) ) {
435
+ ( Ok ( limit) , Ok ( period) ) => {
436
+ quota = quota. min ( limit / period) ;
437
+ }
438
+ _ => { }
439
+ }
440
+ }
441
+
442
+ path. pop ( ) ; // pop filename
443
+ path. pop ( ) ; // pop dir
444
+ }
445
+ } ;
446
+
447
+ quota
448
+ }
449
+
371
450
#[ cfg( all(
372
451
not( target_os = "linux" ) ,
373
452
not( target_os = "freebsd" ) ,
0 commit comments