Project

General

Profile

Actions

Feature #21797

open

Make Etc.nprocessors cgroup-aware on Linux

Feature #21797: Make Etc.nprocessors cgroup-aware on Linux
1

Added by moznion (Taiki Kawakami) 2 days ago. Updated about 17 hours ago.

Status:
Open
Assignee:
-
Target version:
-
[ruby-core:124315]

Description

Currently, Etc.nprocessors ignores cgroup CPU quotas. This causes issues in containers where CPU limits differ from the host CPU count.
I have written a gem for this purpose (https://github.com/moznion/maxprocs-ruby), but it would be preferable if the Ruby core implementation respected cgroup configuration.

Additionally, concurrent-ruby provides similar functionality, but if the language itself offered this capability, language users would not need to implement it individually.
And some parts of the Ruby language handle the number of processors using hard-coded values (e.g., https://github.com/ruby/ruby/blob/8efaf5e6b6a25e0d237f3d71b75865661ae98268/thread_pthread.c#L1737-L1738), so this could also be useful for Ruby language development.

Extending Etc.nprocessors to respect cgroups is one option, but that would be a breaking change, so adding a new API (e.g., Etc.cpu_quota or something?) might be a better approach.

Prior Art

Updated by moznion (Taiki Kawakami) 2 days ago Actions #1

  • Description updated (diff)

Updated by hsbt (Hiroshi SHIBATA) 2 days ago Actions #2 [ruby-core:124318]

RubyGems 4.0.x support -j option for building C extension gem. But It causes in container environment like Circle CI.

https://github.com/ruby/rubygems/issues/9170

If cgroup provides the correct number of CPU for Cicle CI or others, I'm positive to support this.

Updated by moznion (Taiki Kawakami) 2 days ago Actions #3

  • Description updated (diff)

Updated by osyoyu (Daisuke Aritomo) 2 days ago Actions #4 [ruby-core:124320]

It'd be nice if RUBY_MAX_CPU would be autoconfigured based on this, just like Go 1.25 GOMAXPROCS.

Its default value is currently fixed to 8, but not many cloud containers have 8 cores worth of processors.
https://github.com/ruby/ruby/blob/8efaf5e6b6a25e0d237f3d71b75865661ae98268/thread_pthread.c#L1737-L1738

Updated by moznion (Taiki Kawakami) 1 day ago Actions #6 [ruby-core:124331]

osyoyu (Daisuke Aritomo) wrote in #note-4:

It'd be nice if RUBY_MAX_CPU would be autoconfigured based on this, just like Go 1.25 GOMAXPROCS.

Its default value is currently fixed to 8, but not many cloud containers have 8 cores worth of processors.
https://github.com/ruby/ruby/blob/8efaf5e6b6a25e0d237f3d71b75865661ae98268/thread_pthread.c#L1737-L1738

This sounds very good, but it should probably be a separate ticket though.

nobu (Nobuyoshi Nakada) wrote in #note-5:

How about https://github.com/ruby/etc/tree/linux-cgroup-from-maxprocs?

Thank you for your quick action. I left a comment on the pull request; overall, it looks good.

Updated by Eregon (Benoit Daloze) about 20 hours ago Actions #7 [ruby-core:124338]

CPU quotas can be floating point numbers though, not just integers.
But Etc.nprocessors returns an Integer.
So it seems having a new method is worth it.

I think it's good if Etc.nprocessors rounds it though for convenience and so it applies to existing usages of Etc.nprocessors.

FYI concurrent-ruby already has Concurret.cpu_quota (returns Float or nil):
https://github.com/ruby-concurrency/concurrent-ruby/blob/129cf004294af68ac53e53a2f1197621b303570a/lib/concurrent-ruby/concurrent/utility/processor_counter.rb#L198-L211

Updated by moznion (Taiki Kawakami) about 17 hours ago Actions #8 [ruby-core:124339]

Thank you for your comments. Let me summarize the discussion.

1. Introducing a new API under Etc

Introducing a method such as Etc.cpu_quota, which returns Float | nil, seems reasonable.

2. Changing the behavior of Etc.nprocessors

Changing the behavior of Etc.nprocessors to respect cgroup-based values and fall back to the original logic when cgroups are unavailable.
This would be a breaking change, but it would likely be beneficial for most use cases, and the negative impact should be minimal.

3. Injecting the number of actually available CPUs into RUBY_MAX_CPU

This would be useful, but it should be handled as a separate ticket after this one is resolved.

4. Implementation approach

Which is preferred: pure Ruby as a part of ruby/etc or C?


What do you think?

Actions

Also available in: PDF Atom