Repository: libuv/libuv
Pull request #4746: linux: align CPU quota calculation with Rust's cgroup heuristics
Author: login: juanarbol
Created: 2025-03-30T05:34:00Z | Merged: 2025-04-22T20:12:30Z

Fixes: https://github.com/libuv/libuv/issues/4740


Diff:
diff --git a/src/unix/core.c b/src/unix/core.c
index e64ea81d6ab..b6e0c57d03a 100644
--- a/src/unix/core.c
+++ b/src/unix/core.c
@@ -2023,14 +2023,11 @@ unsigned int uv_available_parallelism(void) {
 
 #ifdef __linux__
   {
-    double rc_with_cgroup;
-    uv__cpu_constraint c = {0, 0, 0.0};
+    long long quota = 0;
 
-    if (uv__get_constrained_cpu(&c) == 0 && c.period_length > 0) {
-      rc_with_cgroup = (double)c.quota_per_period / c.period_length * c.proportions;
-      if (rc_with_cgroup < rc)
-        rc = (long)rc_with_cgroup; /* Casting is safe since rc_with_cgroup < rc < LONG_MAX */
-    }
+    if (uv__get_constrained_cpu(&quota) == 0)
+      if (quota > 0 && quota < rc)
+        rc = quota;
   }
 #endif  /* __linux__ */
 
diff --git a/src/unix/internal.h b/src/unix/internal.h
index b1d2b21756d..ff189f6b9c0 100644
--- a/src/unix/internal.h
+++ b/src/unix/internal.h
@@ -489,13 +489,7 @@ uv__fs_copy_file_range(int fd_in,
 #endif
 
 #ifdef __linux__
-typedef struct {
-  long long quota_per_period;
-  long long period_length;
-  double proportions;
-} uv__cpu_constraint;
-
-int uv__get_constrained_cpu(uv__cpu_constraint* constraint);
+int uv__get_constrained_cpu(long long* quota);
 #endif
 
 #if defined(__sun) && !defined(__illumos__)
diff --git a/src/unix/linux.c b/src/unix/linux.c
index 86c12b4f086..bc4a6304c0f 100644
--- a/src/unix/linux.c
+++ b/src/unix/linux.c
@@ -2305,49 +2305,83 @@ uint64_t uv_get_available_memory(void) {
 
 
 static int uv__get_cgroupv2_constrained_cpu(const char* cgroup,
-                                            uv__cpu_constraint* constraint) {
-  char path[256];
-  char buf[1024];
-  unsigned int weight;
-  int cgroup_size;
+                                            long long* quota) {
+  static const char cgroup_mount[] = "/sys/fs/cgroup";
   const char* cgroup_trimmed;
+  char buf[1024];
+  char full_path[256];
+  char path[256];
   char quota_buf[16];
+  char* last_slash;
+  int cgroup_size;
+  long long limit;
+  long long min_quota;
+  long long period;
 
   if (strncmp(cgroup, "0::/", 4) != 0)
     return UV_EINVAL;
 
   /* Trim ending \n by replacing it with a 0 */
   cgroup_trimmed = cgroup + sizeof("0::/") - 1;      /* Skip the prefix "0::/" */
-  cgroup_size = (int)strcspn(cgroup_trimmed, "\n");  /* Find the first slash */
+  cgroup_size = (int)strcspn(cgroup_trimmed, "\n");  /* Find the first \n */
+  min_quota = LLONG_MAX;
 
-  /* Construct the path to the cpu.max file */
-  snprintf(path, sizeof(path), "/sys/fs/cgroup/%.*s/cpu.max", cgroup_size,
-           cgroup_trimmed);
+  /* Construct the path to the cpu.max files */
+  snprintf(path, sizeof(path), "%s/%.*s/cgroup.controllers", cgroup_mount,
+           cgroup_size, cgroup_trimmed);
 
-  /* Read cpu.max */
+  /* Read controllers, if not exists, not really a cgroup */
   if (uv__slurp(path, buf, sizeof(buf)) < 0)
     return UV_EIO;
 
-  if (sscanf(buf, "%15s %llu", quota_buf, &constraint->period_length) != 2)
-    return UV_EINVAL;
+  snprintf(path, sizeof(path), "%s/%.*s", cgroup_mount, cgroup_size,
+           cgroup_trimmed);
 
-  if (strncmp(quota_buf, "max", 3) == 0)
-    constraint->quota_per_period = LLONG_MAX;
-  else if (sscanf(quota_buf, "%lld", &constraint->quota_per_period) != 1)
-    return UV_EINVAL; // conversion failed
+  /*
+   * Traverse up the cgroup v2 hierarchy, starting from the current cgroup path.
+   * At each level, attempt to read the "cpu.max" file, which defines the CPU
+   * quota and period.
+   *
+   * This reflects how Linux applies cgroup limits hierarchically.
+   *
+   * e.g: given a path like /sys/fs/cgroup/foo/bar/baz, we check:
+   *   - /sys/fs/cgroup/foo/bar/baz/cpu.max
+   *   - /sys/fs/cgroup/foo/bar/cpu.max
+   *   - /sys/fs/cgroup/foo/cpu.max
+   *   - /sys/fs/cgroup/cpu.max
+   */
+  while (strncmp(path, cgroup_mount, strlen(cgroup_mount)) == 0) {
+    snprintf(full_path, sizeof(full_path), "%s/cpu.max", path);
 
-  /* Construct the path to the cpu.weight file */
-  snprintf(path, sizeof(path), "/sys/fs/cgroup/%.*s/cpu.weight", cgroup_size,
-           cgroup_trimmed);
+    /* Silently ignore and continue if the file does not exist */
+    if (uv__slurp(full_path, quota_buf, sizeof(quota_buf)) < 0)
+      goto next;
 
-  /* Read cpu.weight */
-  if (uv__slurp(path, buf, sizeof(buf)) < 0)
-    return UV_EIO;
+    /* No limit, move on */
+    if (strncmp(quota_buf, "max", 3) == 0)
+      goto next;
 
-  if (sscanf(buf, "%u", &weight) != 1)
-    return UV_EINVAL;
+    /* Read cpu.max */
+    if (sscanf(quota_buf, "%lld %lld", &limit, &period) != 2)
+      goto next;
 
-  constraint->proportions = (double)weight / 100.0;
+    /* Can't divide by 0 */
+    if (period == 0)
+      goto next;
+
+    *quota = limit / period;
+    if (*quota < min_quota)
+      min_quota = *quota;
+
+next:
+    /* Move up one level in the cgroup hierarchy by trimming the last path.
+     * The loop ends once we reach the cgroup root mount point.
+     */
+    last_slash = strrchr(path, '/');
+    if (last_slash == NULL || strcmp(path, cgroup_mount) == 0)
+      break;
+    *last_slash = '\0';
+  }
 
   return 0;
 }
@@ -2368,12 +2402,13 @@ static char* uv__cgroup1_find_cpu_controller(const char* cgroup,
 }
 
 static int uv__get_cgroupv1_constrained_cpu(const char* cgroup,
-                                            uv__cpu_constraint* constraint) {
+                                            long long* quota) {
   char path[256];
   char buf[1024];
-  unsigned int shares;
   int cgroup_size;
   char* cgroup_cpu;
+  long long period_length;
+  long long quota_per_period;
 
   cgroup_cpu = uv__cgroup1_find_cpu_controller(cgroup, &cgroup_size);
 
@@ -2384,10 +2419,11 @@ static int uv__get_cgroupv1_constrained_cpu(const char* cgroup,
   snprintf(path, sizeof(path), "/sys/fs/cgroup/%.*s/cpu.cfs_quota_us",
            cgroup_size, cgroup_cpu);
 
+  /* Read cpu.cfs_quota_us */
   if (uv__slurp(path, buf, sizeof(buf)) < 0)
     return UV_EIO;
 
-  if (sscanf(buf, "%lld", &constraint->quota_per_period) != 1)
+  if (sscanf(buf, "%lld", &quota_per_period) != 1)
     return UV_EINVAL;
 
   /* Construct the path to the cpu.cfs_period_us file */
@@ -2398,26 +2434,19 @@ static int uv__get_cgroupv1_constrained_cpu(const char* cgroup,
   if (uv__slurp(path, buf, sizeof(buf)) < 0)
     return UV_EIO;
 
-  if (sscanf(buf, "%lld", &constraint->period_length) != 1)
+  if (sscanf(buf, "%lld", &period_length) != 1)
     return UV_EINVAL;
 
-  /* Construct the path to the cpu.shares file */
-  snprintf(path, sizeof(path), "/sys/fs/cgroup/%.*s/cpu.shares", cgroup_size,
-           cgroup_cpu);
-
-  /* Read cpu.shares */
-  if (uv__slurp(path, buf, sizeof(buf)) < 0)
-    return UV_EIO;
-
-  if (sscanf(buf, "%u", &shares) != 1)
+  /* Can't divide by 0 */
+  if (period_length == 0)
     return UV_EINVAL;
 
-  constraint->proportions = (double)shares / 1024.0;
+  *quota = quota_per_period / period_length;
 
   return 0;
 }
 
-int uv__get_constrained_cpu(uv__cpu_constraint* constraint) {
+int uv__get_constrained_cpu(long long* quota) {
   char cgroup[1024];
 
   /* Read the cgroup from /proc/self/cgroup */
@@ -2428,9 +2457,9 @@ int uv__get_constrained_cpu(uv__cpu_constraint* constraint) {
    * The entry for cgroup v2 is always in the format "0::$PATH"
    * see https://docs.kernel.org/admin-guide/cgroup-v2.html */
   if (strncmp(cgroup, "0::/", 4) == 0)
-    return uv__get_cgroupv2_constrained_cpu(cgroup, constraint);
+    return uv__get_cgroupv2_constrained_cpu(cgroup, quota);
   else
-    return uv__get_cgroupv1_constrained_cpu(cgroup, constraint);
+    return uv__get_cgroupv1_constrained_cpu(cgroup, quota);
 }
 
 


Reviews:
user: login: bnoordhuis
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-07T20:33:33Z
user: login: juanarbol
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-08T04:00:52Z
user: login: juanarbol
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-08T04:01:04Z
user: login: juanarbol
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-08T04:01:37Z
user: login: juanarbol
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-08T04:01:56Z
user: login: bnoordhuis
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-08T09:00:14Z
user: login: juanarbol
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-08T16:48:21Z
user: login: juanarbol
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-08T16:52:32Z
user: login: juanarbol
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-08T16:55:57Z
user: login: juanarbol
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-08T16:56:34Z
user: login: juanarbol
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-08T17:00:03Z
user: login: bnoordhuis
body: Mostly LGTM but could maybe use one more look-over from another maintainer.
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-16T12:53:38Z
user: login: juanarbol
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-16T15:35:14Z
user: login: juanarbol
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-16T15:35:35Z
user: login: saghul
body: LGTM with a tiny suggestion. Good work Juan!
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-16T19:05:42Z
user: login: juanarbol
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-16T19:28:45Z

Review comments:
diff_hunk: @@ -2024,10 +2024,10 @@ unsigned int uv_available_parallelism(void) {
 #ifdef __linux__
   {
     double rc_with_cgroup;
-    uv__cpu_constraint c = {0, 0, 0.0};
+    uv__cpu_constraint c = {0, 0, 0, 0.0};
 
-    if (uv__get_constrained_cpu(&c) == 0 && c.period_length > 0) {
-      rc_with_cgroup = (double)c.quota_per_period / c.period_length * c.proportions;
+    if (uv__get_constrained_cpu(&c) == 0 && c.quota > 0) {
+      rc_with_cgroup = (double)c.quota;
path: src/unix/core.c
user: login: bnoordhuis
body: I suggest getting rid of `rc_with_cgroup` and just use `c.quota` directly. All this type casting is superfluous and confusing at best, UB at worst (if `c.quota` cannot be represented as `double`)
created_at: 2025-04-07T20:13:08Z
author_association: MEMBER
diff_hunk: @@ -492,6 +492,7 @@ uv__fs_copy_file_range(int fd_in,
 typedef struct {
   long long quota_per_period;
   long long period_length;
+  long long quota;
   double proportions;
 } uv__cpu_constraint;
path: src/unix/internal.h
user: login: bnoordhuis
body: I'd simply remove this struct and return `quota` directly. Avoids confusion about what fields are and aren't used.
created_at: 2025-04-07T20:17:38Z
author_association: MEMBER
diff_hunk: @@ -2308,46 +2308,70 @@ static int uv__get_cgroupv2_constrained_cpu(const char* cgroup,
                                             uv__cpu_constraint* constraint) {
   char path[256];
   char buf[1024];
-  unsigned int weight;
   int cgroup_size;
+  long long min_quota;
+  long long quota;
+  long long limit;
+  long long period;
+  char* end;
+  const char* cgroup_mount;
path: src/unix/linux.c
user: login: bnoordhuis
body: ```suggestion
  static const char cgroup_mount[] = "/sys/fs/cgroup";
```
Since it never changes (and please move it to the top.)
created_at: 2025-04-07T20:30:09Z
author_association: MEMBER
diff_hunk: @@ -2308,46 +2308,70 @@ static int uv__get_cgroupv2_constrained_cpu(const char* cgroup,
                                             uv__cpu_constraint* constraint) {
   char path[256];
   char buf[1024];
-  unsigned int weight;
   int cgroup_size;
+  long long min_quota;
+  long long quota;
+  long long limit;
+  long long period;
+  char* end;
+  const char* cgroup_mount;
   const char* cgroup_trimmed;
   char quota_buf[16];
 
   if (strncmp(cgroup, "0::/", 4) != 0)
     return UV_EINVAL;
 
   /* Trim ending \n by replacing it with a 0 */
+  cgroup_mount = "/sys/fs/cgroup";
   cgroup_trimmed = cgroup + sizeof("0::/") - 1;      /* Skip the prefix "0::/" */
-  cgroup_size = (int)strcspn(cgroup_trimmed, "\n");  /* Find the first slash */
+  cgroup_size = (int)strcspn(cgroup_trimmed, "\n");  /* Find the first \n */
+  min_quota = LLONG_MAX;
 
-  /* Construct the path to the cpu.max file */
-  snprintf(path, sizeof(path), "/sys/fs/cgroup/%.*s/cpu.max", cgroup_size,
-           cgroup_trimmed);
+  /* Construct the path to the cpu.max files */
+  snprintf(path, sizeof(path), "%s/%.*s/cgroup.controllers", cgroup_mount,
+           cgroup_size, cgroup_trimmed);
 
-  /* Read cpu.max */
+  /* Read controllers, if not exists, not really a cgroup */
   if (uv__slurp(path, buf, sizeof(buf)) < 0)
     return UV_EIO;
 
-  if (sscanf(buf, "%15s %llu", quota_buf, &constraint->period_length) != 2)
-    return UV_EINVAL;
+  snprintf(path, sizeof(path), "%s/%.*s", cgroup_mount, cgroup_size,
+           cgroup_trimmed);
+  end = path + strlen(path);
 
-  if (strncmp(quota_buf, "max", 3) == 0)
-    constraint->quota_per_period = LLONG_MAX;
-  else if (sscanf(quota_buf, "%lld", &constraint->quota_per_period) != 1)
-    return UV_EINVAL; // conversion failed
+  /* Read cpu.max in the whole file-hierarchy */
+  while(strncmp(path, cgroup_mount, strlen(cgroup_mount)) == 0) {
+    snprintf(end, sizeof(path) - (end - path), "/cpu.max");
path: src/unix/linux.c
user: login: bnoordhuis
body: This doesn't look right. If you modify the buffer in-place, you can't safely iterate the next lines; you may have clobbered them.
created_at: 2025-04-07T20:33:25Z
author_association: MEMBER
diff_hunk: @@ -2024,10 +2024,10 @@ unsigned int uv_available_parallelism(void) {
 #ifdef __linux__
   {
     double rc_with_cgroup;
-    uv__cpu_constraint c = {0, 0, 0.0};
+    uv__cpu_constraint c = {0, 0, 0, 0.0};
 
-    if (uv__get_constrained_cpu(&c) == 0 && c.period_length > 0) {
-      rc_with_cgroup = (double)c.quota_per_period / c.period_length * c.proportions;
+    if (uv__get_constrained_cpu(&c) == 0 && c.quota > 0) {
+      rc_with_cgroup = (double)c.quota;
path: src/unix/core.c
user: login: juanarbol
body: Done!
created_at: 2025-04-08T04:00:51Z
author_association: MEMBER
diff_hunk: @@ -492,6 +492,7 @@ uv__fs_copy_file_range(int fd_in,
 typedef struct {
   long long quota_per_period;
   long long period_length;
+  long long quota;
   double proportions;
 } uv__cpu_constraint;
path: src/unix/internal.h
user: login: juanarbol
body: Done
created_at: 2025-04-08T04:01:04Z
author_association: MEMBER
diff_hunk: @@ -2308,46 +2308,70 @@ static int uv__get_cgroupv2_constrained_cpu(const char* cgroup,
                                             uv__cpu_constraint* constraint) {
   char path[256];
   char buf[1024];
-  unsigned int weight;
   int cgroup_size;
+  long long min_quota;
+  long long quota;
+  long long limit;
+  long long period;
+  char* end;
+  const char* cgroup_mount;
path: src/unix/linux.c
user: login: juanarbol
body: Done
created_at: 2025-04-08T04:01:37Z
author_association: MEMBER
diff_hunk: @@ -2308,46 +2308,70 @@ static int uv__get_cgroupv2_constrained_cpu(const char* cgroup,
                                             uv__cpu_constraint* constraint) {
   char path[256];
   char buf[1024];
-  unsigned int weight;
   int cgroup_size;
+  long long min_quota;
+  long long quota;
+  long long limit;
+  long long period;
+  char* end;
+  const char* cgroup_mount;
   const char* cgroup_trimmed;
   char quota_buf[16];
 
   if (strncmp(cgroup, "0::/", 4) != 0)
     return UV_EINVAL;
 
   /* Trim ending \n by replacing it with a 0 */
+  cgroup_mount = "/sys/fs/cgroup";
   cgroup_trimmed = cgroup + sizeof("0::/") - 1;      /* Skip the prefix "0::/" */
-  cgroup_size = (int)strcspn(cgroup_trimmed, "\n");  /* Find the first slash */
+  cgroup_size = (int)strcspn(cgroup_trimmed, "\n");  /* Find the first \n */
+  min_quota = LLONG_MAX;
 
-  /* Construct the path to the cpu.max file */
-  snprintf(path, sizeof(path), "/sys/fs/cgroup/%.*s/cpu.max", cgroup_size,
-           cgroup_trimmed);
+  /* Construct the path to the cpu.max files */
+  snprintf(path, sizeof(path), "%s/%.*s/cgroup.controllers", cgroup_mount,
+           cgroup_size, cgroup_trimmed);
 
-  /* Read cpu.max */
+  /* Read controllers, if not exists, not really a cgroup */
   if (uv__slurp(path, buf, sizeof(buf)) < 0)
     return UV_EIO;
 
-  if (sscanf(buf, "%15s %llu", quota_buf, &constraint->period_length) != 2)
-    return UV_EINVAL;
+  snprintf(path, sizeof(path), "%s/%.*s", cgroup_mount, cgroup_size,
+           cgroup_trimmed);
+  end = path + strlen(path);
 
-  if (strncmp(quota_buf, "max", 3) == 0)
-    constraint->quota_per_period = LLONG_MAX;
-  else if (sscanf(quota_buf, "%lld", &constraint->quota_per_period) != 1)
-    return UV_EINVAL; // conversion failed
+  /* Read cpu.max in the whole file-hierarchy */
+  while(strncmp(path, cgroup_mount, strlen(cgroup_mount)) == 0) {
+    snprintf(end, sizeof(path) - (end - path), "/cpu.max");
path: src/unix/linux.c
user: login: juanarbol
body: True that, made something else
created_at: 2025-04-08T04:01:56Z
author_association: MEMBER
diff_hunk: @@ -2305,49 +2305,73 @@ uint64_t uv_get_available_memory(void) {
 
 
 static int uv__get_cgroupv2_constrained_cpu(const char* cgroup,
-                                            uv__cpu_constraint* constraint) {
-  char path[256];
-  char buf[1024];
-  unsigned int weight;
-  int cgroup_size;
+                                            long long* quota) {
+  static const char cgroup_mount[] = "/sys/fs/cgroup";
   const char* cgroup_trimmed;
+  char buf[1024];
+  char full_path[256];
+  char path[256];
   char quota_buf[16];
+  char* last_slash;
+  int cgroup_size;
+  long long limit;
+  long long min_quota;
+  long long period;
 
   if (strncmp(cgroup, "0::/", 4) != 0)
     return UV_EINVAL;
 
   /* Trim ending \n by replacing it with a 0 */
   cgroup_trimmed = cgroup + sizeof("0::/") - 1;      /* Skip the prefix "0::/" */
-  cgroup_size = (int)strcspn(cgroup_trimmed, "\n");  /* Find the first slash */
+  cgroup_size = (int)strcspn(cgroup_trimmed, "\n");  /* Find the first \n */
+  min_quota = LLONG_MAX;
 
-  /* Construct the path to the cpu.max file */
-  snprintf(path, sizeof(path), "/sys/fs/cgroup/%.*s/cpu.max", cgroup_size,
-           cgroup_trimmed);
+  /* Construct the path to the cpu.max files */
+  snprintf(path, sizeof(path), "%s/%.*s/cgroup.controllers", cgroup_mount,
+           cgroup_size, cgroup_trimmed);
 
-  /* Read cpu.max */
+  /* Read controllers, if not exists, not really a cgroup */
   if (uv__slurp(path, buf, sizeof(buf)) < 0)
     return UV_EIO;
 
-  if (sscanf(buf, "%15s %llu", quota_buf, &constraint->period_length) != 2)
-    return UV_EINVAL;
+  snprintf(path, sizeof(path), "%s/%.*s", cgroup_mount, cgroup_size,
+           cgroup_trimmed);
 
-  if (strncmp(quota_buf, "max", 3) == 0)
-    constraint->quota_per_period = LLONG_MAX;
-  else if (sscanf(quota_buf, "%lld", &constraint->quota_per_period) != 1)
-    return UV_EINVAL; // conversion failed
+  /* Read cpu.max in the whole file-hierarchy */
+  while(strncmp(path, cgroup_mount, strlen(cgroup_mount)) == 0) {
path: src/unix/linux.c
user: login: bnoordhuis
body: ```suggestion
  while (strncmp(path, cgroup_mount, strlen(cgroup_mount)) == 0) {
```
I don't have a cgroupsv2 system at hand to check but can you have a file that looks like this?
```
/sys/fs/cgroup/foo
/sys/fs/cgroup/bar
/something/completely/different
/sys/fs/cgroup/baz
```
created_at: 2025-04-08T08:53:56Z
author_association: MEMBER
diff_hunk: @@ -2305,49 +2305,73 @@ uint64_t uv_get_available_memory(void) {
 
 
 static int uv__get_cgroupv2_constrained_cpu(const char* cgroup,
-                                            uv__cpu_constraint* constraint) {
-  char path[256];
-  char buf[1024];
-  unsigned int weight;
-  int cgroup_size;
+                                            long long* quota) {
+  static const char cgroup_mount[] = "/sys/fs/cgroup";
   const char* cgroup_trimmed;
+  char buf[1024];
+  char full_path[256];
+  char path[256];
   char quota_buf[16];
+  char* last_slash;
+  int cgroup_size;
+  long long limit;
+  long long min_quota;
+  long long period;
 
   if (strncmp(cgroup, "0::/", 4) != 0)
     return UV_EINVAL;
 
   /* Trim ending \n by replacing it with a 0 */
   cgroup_trimmed = cgroup + sizeof("0::/") - 1;      /* Skip the prefix "0::/" */
-  cgroup_size = (int)strcspn(cgroup_trimmed, "\n");  /* Find the first slash */
+  cgroup_size = (int)strcspn(cgroup_trimmed, "\n");  /* Find the first \n */
+  min_quota = LLONG_MAX;
 
-  /* Construct the path to the cpu.max file */
-  snprintf(path, sizeof(path), "/sys/fs/cgroup/%.*s/cpu.max", cgroup_size,
-           cgroup_trimmed);
+  /* Construct the path to the cpu.max files */
+  snprintf(path, sizeof(path), "%s/%.*s/cgroup.controllers", cgroup_mount,
+           cgroup_size, cgroup_trimmed);
 
-  /* Read cpu.max */
+  /* Read controllers, if not exists, not really a cgroup */
   if (uv__slurp(path, buf, sizeof(buf)) < 0)
     return UV_EIO;
 
-  if (sscanf(buf, "%15s %llu", quota_buf, &constraint->period_length) != 2)
-    return UV_EINVAL;
+  snprintf(path, sizeof(path), "%s/%.*s", cgroup_mount, cgroup_size,
+           cgroup_trimmed);
 
-  if (strncmp(quota_buf, "max", 3) == 0)
-    constraint->quota_per_period = LLONG_MAX;
-  else if (sscanf(quota_buf, "%lld", &constraint->quota_per_period) != 1)
-    return UV_EINVAL; // conversion failed
+  /* Read cpu.max in the whole file-hierarchy */
+  while(strncmp(path, cgroup_mount, strlen(cgroup_mount)) == 0) {
+    snprintf(full_path, sizeof(full_path), "%s/cpu.max", path);
 
-  /* Construct the path to the cpu.weight file */
-  snprintf(path, sizeof(path), "/sys/fs/cgroup/%.*s/cpu.weight", cgroup_size,
-           cgroup_trimmed);
+    /* Silently ignore and continue if the file does not exist */
+    if (uv__slurp(full_path, quota_buf, sizeof(quota_buf)) < 0)
+      goto next;
 
-  /* Read cpu.weight */
-  if (uv__slurp(path, buf, sizeof(buf)) < 0)
-    return UV_EIO;
+    /* No limit, move on */
+    if (strncmp(quota_buf, "max", 3) == 0)
+      goto next;
 
-  if (sscanf(buf, "%u", &weight) != 1)
-    return UV_EINVAL;
+    /* Read cpu.max */
+    if (sscanf(quota_buf, "%lld %lld", &limit, &period) != 2)
+      goto next;
 
-  constraint->proportions = (double)weight / 100.0;
+    /* Can't divide by 0 */
+    if (period == 0)
+      goto next;
+
+    if ((limit / period) < min_quota)
+      min_quota = limit / period;
+
+next:
+    /* Continue in the file hierarchy */
+    last_slash = strrchr(path, '/');
+    if (last_slash == NULL || strcmp(path, cgroup_mount) == 0)
+      break;
+    *last_slash = '\0';
path: src/unix/linux.c
user: login: bnoordhuis
body: Why is this necessary? Because it works backwards? E.g. goes from `/sys/fs/cgroup/foo/bar` to `/sys/fs/cgroup/foo` to `/sys/fs/cgroup`?
created_at: 2025-04-08T08:56:50Z
author_association: MEMBER
diff_hunk: @@ -2305,49 +2305,73 @@ uint64_t uv_get_available_memory(void) {
 
 
 static int uv__get_cgroupv2_constrained_cpu(const char* cgroup,
-                                            uv__cpu_constraint* constraint) {
-  char path[256];
-  char buf[1024];
-  unsigned int weight;
-  int cgroup_size;
+                                            long long* quota) {
+  static const char cgroup_mount[] = "/sys/fs/cgroup";
   const char* cgroup_trimmed;
+  char buf[1024];
+  char full_path[256];
+  char path[256];
   char quota_buf[16];
+  char* last_slash;
+  int cgroup_size;
+  long long limit;
+  long long min_quota;
+  long long period;
 
   if (strncmp(cgroup, "0::/", 4) != 0)
     return UV_EINVAL;
 
   /* Trim ending \n by replacing it with a 0 */
   cgroup_trimmed = cgroup + sizeof("0::/") - 1;      /* Skip the prefix "0::/" */
-  cgroup_size = (int)strcspn(cgroup_trimmed, "\n");  /* Find the first slash */
+  cgroup_size = (int)strcspn(cgroup_trimmed, "\n");  /* Find the first \n */
+  min_quota = LLONG_MAX;
 
-  /* Construct the path to the cpu.max file */
-  snprintf(path, sizeof(path), "/sys/fs/cgroup/%.*s/cpu.max", cgroup_size,
-           cgroup_trimmed);
+  /* Construct the path to the cpu.max files */
+  snprintf(path, sizeof(path), "%s/%.*s/cgroup.controllers", cgroup_mount,
+           cgroup_size, cgroup_trimmed);
 
-  /* Read cpu.max */
+  /* Read controllers, if not exists, not really a cgroup */
   if (uv__slurp(path, buf, sizeof(buf)) < 0)
     return UV_EIO;
 
-  if (sscanf(buf, "%15s %llu", quota_buf, &constraint->period_length) != 2)
-    return UV_EINVAL;
+  snprintf(path, sizeof(path), "%s/%.*s", cgroup_mount, cgroup_size,
+           cgroup_trimmed);
 
-  if (strncmp(quota_buf, "max", 3) == 0)
-    constraint->quota_per_period = LLONG_MAX;
-  else if (sscanf(quota_buf, "%lld", &constraint->quota_per_period) != 1)
-    return UV_EINVAL; // conversion failed
+  /* Read cpu.max in the whole file-hierarchy */
+  while(strncmp(path, cgroup_mount, strlen(cgroup_mount)) == 0) {
+    snprintf(full_path, sizeof(full_path), "%s/cpu.max", path);
 
-  /* Construct the path to the cpu.weight file */
-  snprintf(path, sizeof(path), "/sys/fs/cgroup/%.*s/cpu.weight", cgroup_size,
-           cgroup_trimmed);
+    /* Silently ignore and continue if the file does not exist */
+    if (uv__slurp(full_path, quota_buf, sizeof(quota_buf)) < 0)
+      goto next;
 
-  /* Read cpu.weight */
-  if (uv__slurp(path, buf, sizeof(buf)) < 0)
-    return UV_EIO;
+    /* No limit, move on */
+    if (strncmp(quota_buf, "max", 3) == 0)
+      goto next;
 
-  if (sscanf(buf, "%u", &weight) != 1)
-    return UV_EINVAL;
+    /* Read cpu.max */
+    if (sscanf(quota_buf, "%lld %lld", &limit, &period) != 2)
+      goto next;
 
-  constraint->proportions = (double)weight / 100.0;
+    /* Can't divide by 0 */
+    if (period == 0)
+      goto next;
+
+    if ((limit / period) < min_quota)
+      min_quota = limit / period;
+
+next:
+    /* Continue in the file hierarchy */
+    last_slash = strrchr(path, '/');
+    if (last_slash == NULL || strcmp(path, cgroup_mount) == 0)
+      break;
+    *last_slash = '\0';
+  }
+
+  if (min_quota == LLONG_MAX)
+    return UV_EIO;
+
+  *quota = min_quota;
path: src/unix/linux.c
user: login: bnoordhuis
body: What if min_quota is zero because limit was zero? Is that benign or not?
created_at: 2025-04-08T08:58:29Z
author_association: MEMBER
diff_hunk: @@ -2398,26 +2424,15 @@ static int uv__get_cgroupv1_constrained_cpu(const char* cgroup,
   if (uv__slurp(path, buf, sizeof(buf)) < 0)
     return UV_EIO;
 
-  if (sscanf(buf, "%lld", &constraint->period_length) != 1)
-    return UV_EINVAL;
-
-  /* Construct the path to the cpu.shares file */
-  snprintf(path, sizeof(path), "/sys/fs/cgroup/%.*s/cpu.shares", cgroup_size,
-           cgroup_cpu);
-
-  /* Read cpu.shares */
-  if (uv__slurp(path, buf, sizeof(buf)) < 0)
-    return UV_EIO;
-
-  if (sscanf(buf, "%u", &shares) != 1)
+  if (sscanf(buf, "%lld", &period_length) != 1)
     return UV_EINVAL;
 
-  constraint->proportions = (double)shares / 1024.0;
+  *quota = quota_per_period / period_length;
path: src/unix/linux.c
user: login: bnoordhuis
body: Should guard against `period_length == 0`.
created_at: 2025-04-08T08:59:53Z
author_association: MEMBER
diff_hunk: @@ -2398,26 +2424,15 @@ static int uv__get_cgroupv1_constrained_cpu(const char* cgroup,
   if (uv__slurp(path, buf, sizeof(buf)) < 0)
     return UV_EIO;
 
-  if (sscanf(buf, "%lld", &constraint->period_length) != 1)
-    return UV_EINVAL;
-
-  /* Construct the path to the cpu.shares file */
-  snprintf(path, sizeof(path), "/sys/fs/cgroup/%.*s/cpu.shares", cgroup_size,
-           cgroup_cpu);
-
-  /* Read cpu.shares */
-  if (uv__slurp(path, buf, sizeof(buf)) < 0)
-    return UV_EIO;
-
-  if (sscanf(buf, "%u", &shares) != 1)
+  if (sscanf(buf, "%lld", &period_length) != 1)
     return UV_EINVAL;
 
-  constraint->proportions = (double)shares / 1024.0;
+  *quota = quota_per_period / period_length;
path: src/unix/linux.c
user: login: juanarbol
body: Roger that.
created_at: 2025-04-08T16:48:21Z
author_association: MEMBER
diff_hunk: @@ -2305,49 +2305,73 @@ uint64_t uv_get_available_memory(void) {
 
 
 static int uv__get_cgroupv2_constrained_cpu(const char* cgroup,
-                                            uv__cpu_constraint* constraint) {
-  char path[256];
-  char buf[1024];
-  unsigned int weight;
-  int cgroup_size;
+                                            long long* quota) {
+  static const char cgroup_mount[] = "/sys/fs/cgroup";
   const char* cgroup_trimmed;
+  char buf[1024];
+  char full_path[256];
+  char path[256];
   char quota_buf[16];
+  char* last_slash;
+  int cgroup_size;
+  long long limit;
+  long long min_quota;
+  long long period;
 
   if (strncmp(cgroup, "0::/", 4) != 0)
     return UV_EINVAL;
 
   /* Trim ending \n by replacing it with a 0 */
   cgroup_trimmed = cgroup + sizeof("0::/") - 1;      /* Skip the prefix "0::/" */
-  cgroup_size = (int)strcspn(cgroup_trimmed, "\n");  /* Find the first slash */
+  cgroup_size = (int)strcspn(cgroup_trimmed, "\n");  /* Find the first \n */
+  min_quota = LLONG_MAX;
 
-  /* Construct the path to the cpu.max file */
-  snprintf(path, sizeof(path), "/sys/fs/cgroup/%.*s/cpu.max", cgroup_size,
-           cgroup_trimmed);
+  /* Construct the path to the cpu.max files */
+  snprintf(path, sizeof(path), "%s/%.*s/cgroup.controllers", cgroup_mount,
+           cgroup_size, cgroup_trimmed);
 
-  /* Read cpu.max */
+  /* Read controllers, if not exists, not really a cgroup */
   if (uv__slurp(path, buf, sizeof(buf)) < 0)
     return UV_EIO;
 
-  if (sscanf(buf, "%15s %llu", quota_buf, &constraint->period_length) != 2)
-    return UV_EINVAL;
+  snprintf(path, sizeof(path), "%s/%.*s", cgroup_mount, cgroup_size,
+           cgroup_trimmed);
 
-  if (strncmp(quota_buf, "max", 3) == 0)
-    constraint->quota_per_period = LLONG_MAX;
-  else if (sscanf(quota_buf, "%lld", &constraint->quota_per_period) != 1)
-    return UV_EINVAL; // conversion failed
+  /* Read cpu.max in the whole file-hierarchy */
+  while(strncmp(path, cgroup_mount, strlen(cgroup_mount)) == 0) {
path: src/unix/linux.c
user: login: juanarbol
body: That's not gonna happen. The `cgroup` argument will be the one giving us the file-hierarchy. Consider this simple diff. Also keep in mind that libuv double check the cgroup argument format, and also it is optained from reading `/proc/self/cgroup` (see `uv__get_constrained_cpu`)

```diff
diff --git a/src/unix/linux.c b/src/unix/linux.c
index 73431ec0..f9e43e39 100644
--- a/src/unix/linux.c
+++ b/src/unix/linux.c
@@ -2321,6 +2321,8 @@ static int uv__get_cgroupv2_constrained_cpu(const char* cgroup,
   if (strncmp(cgroup, "0::/", 4) != 0)
     return UV_EINVAL;

+  fprintf(stderr, "cgroup: %s\n", cgroup);
+
   /* Trim ending \n by replacing it with a 0 */
   cgroup_trimmed = cgroup + sizeof("0::/") - 1;      /* Skip the prefix "0::/" */
   cgroup_size = (int)strcspn(cgroup_trimmed, "\n");  /* Find the first \n */
@@ -2340,6 +2342,7 @@ static int uv__get_cgroupv2_constrained_cpu(const char* cgroup,
   /* Read cpu.max in the whole file-hierarchy */
   while(strncmp(path, cgroup_mount, strlen(cgroup_mount)) == 0) {
     snprintf(full_path, sizeof(full_path), "%s/cpu.max", path);
+    fprintf(stderr, "full_path: %s\n", full_path);

     /* Silently ignore and continue if the file does not exist */
     if (uv__slurp(full_path, quota_buf, sizeof(quota_buf)) < 0)
```

This is the output:
```
cgroup: 0::/user.slice/user-1000.slice/user@1000.service/app.slice/tmux-spawn-0a4efa52-dc83-4826-bf02-77770535152b.scope

full_path: /sys/fs/cgroup/user.slice/user-1000.slice/user@1000.service/app.slice/tmux-spawn-0a4efa52-dc83-4826-bf02-77770535152b.scope/cpu.max
full_path: /sys/fs/cgroup/user.slice/user-1000.slice/user@1000.service/app.slice/cpu.max
full_path: /sys/fs/cgroup/user.slice/user-1000.slice/user@1000.service/cpu.max
full_path: /sys/fs/cgroup/user.slice/user-1000.slice/cpu.max
full_path: /sys/fs/cgroup/user.slice/cpu.max
full_path: /sys/fs/cgroup/cpu.max
uv_available_parallelism: 16
```
created_at: 2025-04-08T16:52:32Z
author_association: MEMBER
diff_hunk: @@ -2305,49 +2305,73 @@ uint64_t uv_get_available_memory(void) {
 
 
 static int uv__get_cgroupv2_constrained_cpu(const char* cgroup,
-                                            uv__cpu_constraint* constraint) {
-  char path[256];
-  char buf[1024];
-  unsigned int weight;
-  int cgroup_size;
+                                            long long* quota) {
+  static const char cgroup_mount[] = "/sys/fs/cgroup";
   const char* cgroup_trimmed;
+  char buf[1024];
+  char full_path[256];
+  char path[256];
   char quota_buf[16];
+  char* last_slash;
+  int cgroup_size;
+  long long limit;
+  long long min_quota;
+  long long period;
 
   if (strncmp(cgroup, "0::/", 4) != 0)
     return UV_EINVAL;
 
   /* Trim ending \n by replacing it with a 0 */
   cgroup_trimmed = cgroup + sizeof("0::/") - 1;      /* Skip the prefix "0::/" */
-  cgroup_size = (int)strcspn(cgroup_trimmed, "\n");  /* Find the first slash */
+  cgroup_size = (int)strcspn(cgroup_trimmed, "\n");  /* Find the first \n */
+  min_quota = LLONG_MAX;
 
-  /* Construct the path to the cpu.max file */
-  snprintf(path, sizeof(path), "/sys/fs/cgroup/%.*s/cpu.max", cgroup_size,
-           cgroup_trimmed);
+  /* Construct the path to the cpu.max files */
+  snprintf(path, sizeof(path), "%s/%.*s/cgroup.controllers", cgroup_mount,
+           cgroup_size, cgroup_trimmed);
 
-  /* Read cpu.max */
+  /* Read controllers, if not exists, not really a cgroup */
   if (uv__slurp(path, buf, sizeof(buf)) < 0)
     return UV_EIO;
 
-  if (sscanf(buf, "%15s %llu", quota_buf, &constraint->period_length) != 2)
-    return UV_EINVAL;
+  snprintf(path, sizeof(path), "%s/%.*s", cgroup_mount, cgroup_size,
+           cgroup_trimmed);
 
-  if (strncmp(quota_buf, "max", 3) == 0)
-    constraint->quota_per_period = LLONG_MAX;
-  else if (sscanf(quota_buf, "%lld", &constraint->quota_per_period) != 1)
-    return UV_EINVAL; // conversion failed
+  /* Read cpu.max in the whole file-hierarchy */
+  while(strncmp(path, cgroup_mount, strlen(cgroup_mount)) == 0) {
+    snprintf(full_path, sizeof(full_path), "%s/cpu.max", path);
 
-  /* Construct the path to the cpu.weight file */
-  snprintf(path, sizeof(path), "/sys/fs/cgroup/%.*s/cpu.weight", cgroup_size,
-           cgroup_trimmed);
+    /* Silently ignore and continue if the file does not exist */
+    if (uv__slurp(full_path, quota_buf, sizeof(quota_buf)) < 0)
+      goto next;
 
-  /* Read cpu.weight */
-  if (uv__slurp(path, buf, sizeof(buf)) < 0)
-    return UV_EIO;
+    /* No limit, move on */
+    if (strncmp(quota_buf, "max", 3) == 0)
+      goto next;
 
-  if (sscanf(buf, "%u", &weight) != 1)
-    return UV_EINVAL;
+    /* Read cpu.max */
+    if (sscanf(quota_buf, "%lld %lld", &limit, &period) != 2)
+      goto next;
 
-  constraint->proportions = (double)weight / 100.0;
+    /* Can't divide by 0 */
+    if (period == 0)
+      goto next;
+
+    if ((limit / period) < min_quota)
+      min_quota = limit / period;
+
+next:
+    /* Continue in the file hierarchy */
+    last_slash = strrchr(path, '/');
+    if (last_slash == NULL || strcmp(path, cgroup_mount) == 0)
+      break;
+    *last_slash = '\0';
path: src/unix/linux.c
user: login: juanarbol
body: Yes. As https://github.com/libuv/libuv/issues/4740 came up with porting something from somewhere else, that's exactly what I did. As a reference https://github.com/rust-lang/rust/blob/ae8ab87de4d8caab5d91a027bc19bb5d5e8a3691/library/std/src/sys/pal/unix/thread.rs#L644-L645; does C have anything like Rust's `PathBuf` type? This may be ugly and nasty; if you have another option, let me know and I will write it. Please don't avoid details if so. That will make my life so much easier.
created_at: 2025-04-08T16:55:57Z
author_association: MEMBER
diff_hunk: @@ -2305,49 +2305,73 @@ uint64_t uv_get_available_memory(void) {
 
 
 static int uv__get_cgroupv2_constrained_cpu(const char* cgroup,
-                                            uv__cpu_constraint* constraint) {
-  char path[256];
-  char buf[1024];
-  unsigned int weight;
-  int cgroup_size;
+                                            long long* quota) {
+  static const char cgroup_mount[] = "/sys/fs/cgroup";
   const char* cgroup_trimmed;
+  char buf[1024];
+  char full_path[256];
+  char path[256];
   char quota_buf[16];
+  char* last_slash;
+  int cgroup_size;
+  long long limit;
+  long long min_quota;
+  long long period;
 
   if (strncmp(cgroup, "0::/", 4) != 0)
     return UV_EINVAL;
 
   /* Trim ending \n by replacing it with a 0 */
   cgroup_trimmed = cgroup + sizeof("0::/") - 1;      /* Skip the prefix "0::/" */
-  cgroup_size = (int)strcspn(cgroup_trimmed, "\n");  /* Find the first slash */
+  cgroup_size = (int)strcspn(cgroup_trimmed, "\n");  /* Find the first \n */
+  min_quota = LLONG_MAX;
 
-  /* Construct the path to the cpu.max file */
-  snprintf(path, sizeof(path), "/sys/fs/cgroup/%.*s/cpu.max", cgroup_size,
-           cgroup_trimmed);
+  /* Construct the path to the cpu.max files */
+  snprintf(path, sizeof(path), "%s/%.*s/cgroup.controllers", cgroup_mount,
+           cgroup_size, cgroup_trimmed);
 
-  /* Read cpu.max */
+  /* Read controllers, if not exists, not really a cgroup */
   if (uv__slurp(path, buf, sizeof(buf)) < 0)
     return UV_EIO;
 
-  if (sscanf(buf, "%15s %llu", quota_buf, &constraint->period_length) != 2)
-    return UV_EINVAL;
+  snprintf(path, sizeof(path), "%s/%.*s", cgroup_mount, cgroup_size,
+           cgroup_trimmed);
 
-  if (strncmp(quota_buf, "max", 3) == 0)
-    constraint->quota_per_period = LLONG_MAX;
-  else if (sscanf(quota_buf, "%lld", &constraint->quota_per_period) != 1)
-    return UV_EINVAL; // conversion failed
+  /* Read cpu.max in the whole file-hierarchy */
+  while(strncmp(path, cgroup_mount, strlen(cgroup_mount)) == 0) {
+    snprintf(full_path, sizeof(full_path), "%s/cpu.max", path);
 
-  /* Construct the path to the cpu.weight file */
-  snprintf(path, sizeof(path), "/sys/fs/cgroup/%.*s/cpu.weight", cgroup_size,
-           cgroup_trimmed);
+    /* Silently ignore and continue if the file does not exist */
+    if (uv__slurp(full_path, quota_buf, sizeof(quota_buf)) < 0)
+      goto next;
 
-  /* Read cpu.weight */
-  if (uv__slurp(path, buf, sizeof(buf)) < 0)
-    return UV_EIO;
+    /* No limit, move on */
+    if (strncmp(quota_buf, "max", 3) == 0)
+      goto next;
 
-  if (sscanf(buf, "%u", &weight) != 1)
-    return UV_EINVAL;
+    /* Read cpu.max */
+    if (sscanf(quota_buf, "%lld %lld", &limit, &period) != 2)
+      goto next;
 
-  constraint->proportions = (double)weight / 100.0;
+    /* Can't divide by 0 */
+    if (period == 0)
+      goto next;
+
+    if ((limit / period) < min_quota)
+      min_quota = limit / period;
+
+next:
+    /* Continue in the file hierarchy */
+    last_slash = strrchr(path, '/');
+    if (last_slash == NULL || strcmp(path, cgroup_mount) == 0)
+      break;
+    *last_slash = '\0';
+  }
+
+  if (min_quota == LLONG_MAX)
+    return UV_EIO;
+
+  *quota = min_quota;
path: src/unix/linux.c
user: login: juanarbol
body: Well, idk, let me put that in the inner loop then.
created_at: 2025-04-08T16:56:33Z
author_association: MEMBER
diff_hunk: @@ -2305,49 +2305,73 @@ uint64_t uv_get_available_memory(void) {
 
 
 static int uv__get_cgroupv2_constrained_cpu(const char* cgroup,
-                                            uv__cpu_constraint* constraint) {
-  char path[256];
-  char buf[1024];
-  unsigned int weight;
-  int cgroup_size;
+                                            long long* quota) {
+  static const char cgroup_mount[] = "/sys/fs/cgroup";
   const char* cgroup_trimmed;
+  char buf[1024];
+  char full_path[256];
+  char path[256];
   char quota_buf[16];
+  char* last_slash;
+  int cgroup_size;
+  long long limit;
+  long long min_quota;
+  long long period;
 
   if (strncmp(cgroup, "0::/", 4) != 0)
     return UV_EINVAL;
 
   /* Trim ending \n by replacing it with a 0 */
   cgroup_trimmed = cgroup + sizeof("0::/") - 1;      /* Skip the prefix "0::/" */
-  cgroup_size = (int)strcspn(cgroup_trimmed, "\n");  /* Find the first slash */
+  cgroup_size = (int)strcspn(cgroup_trimmed, "\n");  /* Find the first \n */
+  min_quota = LLONG_MAX;
 
-  /* Construct the path to the cpu.max file */
-  snprintf(path, sizeof(path), "/sys/fs/cgroup/%.*s/cpu.max", cgroup_size,
-           cgroup_trimmed);
+  /* Construct the path to the cpu.max files */
+  snprintf(path, sizeof(path), "%s/%.*s/cgroup.controllers", cgroup_mount,
+           cgroup_size, cgroup_trimmed);
 
-  /* Read cpu.max */
+  /* Read controllers, if not exists, not really a cgroup */
   if (uv__slurp(path, buf, sizeof(buf)) < 0)
     return UV_EIO;
 
-  if (sscanf(buf, "%15s %llu", quota_buf, &constraint->period_length) != 2)
-    return UV_EINVAL;
+  snprintf(path, sizeof(path), "%s/%.*s", cgroup_mount, cgroup_size,
+           cgroup_trimmed);
 
-  if (strncmp(quota_buf, "max", 3) == 0)
-    constraint->quota_per_period = LLONG_MAX;
-  else if (sscanf(quota_buf, "%lld", &constraint->quota_per_period) != 1)
-    return UV_EINVAL; // conversion failed
+  /* Read cpu.max in the whole file-hierarchy */
+  while(strncmp(path, cgroup_mount, strlen(cgroup_mount)) == 0) {
path: src/unix/linux.c
user: login: juanarbol
body: Keep in mind that "full_path" is computed via the actual "path" provided by cgroup argument.

`snprintf(path, sizeof(path), "%s/%.*s", cgroup_mount, cgroup_size,
           cgroup_trimmed);`
           
`snprintf(full_path, sizeof(full_path), "%s/cpu.max", path);`
created_at: 2025-04-08T17:00:02Z
author_association: MEMBER
diff_hunk: @@ -2305,49 +2305,73 @@ uint64_t uv_get_available_memory(void) {
 
 
 static int uv__get_cgroupv2_constrained_cpu(const char* cgroup,
-                                            uv__cpu_constraint* constraint) {
-  char path[256];
-  char buf[1024];
-  unsigned int weight;
-  int cgroup_size;
+                                            long long* quota) {
+  static const char cgroup_mount[] = "/sys/fs/cgroup";
   const char* cgroup_trimmed;
+  char buf[1024];
+  char full_path[256];
+  char path[256];
   char quota_buf[16];
+  char* last_slash;
+  int cgroup_size;
+  long long limit;
+  long long min_quota;
+  long long period;
 
   if (strncmp(cgroup, "0::/", 4) != 0)
     return UV_EINVAL;
 
   /* Trim ending \n by replacing it with a 0 */
   cgroup_trimmed = cgroup + sizeof("0::/") - 1;      /* Skip the prefix "0::/" */
-  cgroup_size = (int)strcspn(cgroup_trimmed, "\n");  /* Find the first slash */
+  cgroup_size = (int)strcspn(cgroup_trimmed, "\n");  /* Find the first \n */
+  min_quota = LLONG_MAX;
 
-  /* Construct the path to the cpu.max file */
-  snprintf(path, sizeof(path), "/sys/fs/cgroup/%.*s/cpu.max", cgroup_size,
-           cgroup_trimmed);
+  /* Construct the path to the cpu.max files */
+  snprintf(path, sizeof(path), "%s/%.*s/cgroup.controllers", cgroup_mount,
+           cgroup_size, cgroup_trimmed);
 
-  /* Read cpu.max */
+  /* Read controllers, if not exists, not really a cgroup */
   if (uv__slurp(path, buf, sizeof(buf)) < 0)
     return UV_EIO;
 
-  if (sscanf(buf, "%15s %llu", quota_buf, &constraint->period_length) != 2)
-    return UV_EINVAL;
+  snprintf(path, sizeof(path), "%s/%.*s", cgroup_mount, cgroup_size,
+           cgroup_trimmed);
 
-  if (strncmp(quota_buf, "max", 3) == 0)
-    constraint->quota_per_period = LLONG_MAX;
-  else if (sscanf(quota_buf, "%lld", &constraint->quota_per_period) != 1)
-    return UV_EINVAL; // conversion failed
+  /* Read cpu.max in the whole file-hierarchy */
+  while(strncmp(path, cgroup_mount, strlen(cgroup_mount)) == 0) {
+    snprintf(full_path, sizeof(full_path), "%s/cpu.max", path);
 
-  /* Construct the path to the cpu.weight file */
-  snprintf(path, sizeof(path), "/sys/fs/cgroup/%.*s/cpu.weight", cgroup_size,
-           cgroup_trimmed);
+    /* Silently ignore and continue if the file does not exist */
+    if (uv__slurp(full_path, quota_buf, sizeof(quota_buf)) < 0)
+      goto next;
 
-  /* Read cpu.weight */
-  if (uv__slurp(path, buf, sizeof(buf)) < 0)
-    return UV_EIO;
+    /* No limit, move on */
+    if (strncmp(quota_buf, "max", 3) == 0)
+      goto next;
 
-  if (sscanf(buf, "%u", &weight) != 1)
-    return UV_EINVAL;
+    /* Read cpu.max */
+    if (sscanf(quota_buf, "%lld %lld", &limit, &period) != 2)
+      goto next;
 
-  constraint->proportions = (double)weight / 100.0;
+    /* Can't divide by 0 */
+    if (period == 0)
+      goto next;
+
+    if ((limit / period) < min_quota)
+      min_quota = limit / period;
+
+next:
+    /* Continue in the file hierarchy */
+    last_slash = strrchr(path, '/');
+    if (last_slash == NULL || strcmp(path, cgroup_mount) == 0)
+      break;
+    *last_slash = '\0';
path: src/unix/linux.c
user: login: bnoordhuis
body: > does C have anything like Rust's PathBuf type?

Definitely not; everything in C predates Rust by 30-40 years :-)

Can you update the comment to explain what the code is doing? It's not super obvious at a casual glance.
created_at: 2025-04-16T12:51:19Z
author_association: MEMBER
diff_hunk: @@ -2023,14 +2023,11 @@ unsigned int uv_available_parallelism(void) {
 
 #ifdef __linux__
   {
-    double rc_with_cgroup;
-    uv__cpu_constraint c = {0, 0, 0.0};
+    long long quota = 0;
 
-    if (uv__get_constrained_cpu(&c) == 0 && c.period_length > 0) {
-      rc_with_cgroup = (double)c.quota_per_period / c.period_length * c.proportions;
-      if (rc_with_cgroup < rc)
-        rc = (long)rc_with_cgroup; /* Casting is safe since rc_with_cgroup < rc < LONG_MAX */
-    }
+    if (uv__get_constrained_cpu(&quota) == 0 && quota > 0)
+      if (quota < rc)
+        rc = quota;
path: src/unix/core.c
user: login: bnoordhuis
body: Can you write this as either:
```suggestion
    if (uv__get_constrained_cpu(&quota) == 0)
      if (quota > 0 && quota < rc)
        rc = quota;
```
Or:
```suggestion
    if (uv__get_constrained_cpu(&quota))
      if (quota > 0)
        if (quota < rc)
          rc = quota;
```
I find the second one a little easier to read but that's personal preference.
created_at: 2025-04-16T12:53:03Z
author_association: MEMBER
diff_hunk: @@ -2023,14 +2023,11 @@ unsigned int uv_available_parallelism(void) {
 
 #ifdef __linux__
   {
-    double rc_with_cgroup;
-    uv__cpu_constraint c = {0, 0, 0.0};
+    long long quota = 0;
 
-    if (uv__get_constrained_cpu(&c) == 0 && c.period_length > 0) {
-      rc_with_cgroup = (double)c.quota_per_period / c.period_length * c.proportions;
-      if (rc_with_cgroup < rc)
-        rc = (long)rc_with_cgroup; /* Casting is safe since rc_with_cgroup < rc < LONG_MAX */
-    }
+    if (uv__get_constrained_cpu(&quota) == 0 && quota > 0)
+      if (quota < rc)
+        rc = quota;
path: src/unix/core.c
user: login: juanarbol
body: Roger that
created_at: 2025-04-16T15:35:14Z
author_association: MEMBER
diff_hunk: @@ -2305,49 +2305,73 @@ uint64_t uv_get_available_memory(void) {
 
 
 static int uv__get_cgroupv2_constrained_cpu(const char* cgroup,
-                                            uv__cpu_constraint* constraint) {
-  char path[256];
-  char buf[1024];
-  unsigned int weight;
-  int cgroup_size;
+                                            long long* quota) {
+  static const char cgroup_mount[] = "/sys/fs/cgroup";
   const char* cgroup_trimmed;
+  char buf[1024];
+  char full_path[256];
+  char path[256];
   char quota_buf[16];
+  char* last_slash;
+  int cgroup_size;
+  long long limit;
+  long long min_quota;
+  long long period;
 
   if (strncmp(cgroup, "0::/", 4) != 0)
     return UV_EINVAL;
 
   /* Trim ending \n by replacing it with a 0 */
   cgroup_trimmed = cgroup + sizeof("0::/") - 1;      /* Skip the prefix "0::/" */
-  cgroup_size = (int)strcspn(cgroup_trimmed, "\n");  /* Find the first slash */
+  cgroup_size = (int)strcspn(cgroup_trimmed, "\n");  /* Find the first \n */
+  min_quota = LLONG_MAX;
 
-  /* Construct the path to the cpu.max file */
-  snprintf(path, sizeof(path), "/sys/fs/cgroup/%.*s/cpu.max", cgroup_size,
-           cgroup_trimmed);
+  /* Construct the path to the cpu.max files */
+  snprintf(path, sizeof(path), "%s/%.*s/cgroup.controllers", cgroup_mount,
+           cgroup_size, cgroup_trimmed);
 
-  /* Read cpu.max */
+  /* Read controllers, if not exists, not really a cgroup */
   if (uv__slurp(path, buf, sizeof(buf)) < 0)
     return UV_EIO;
 
-  if (sscanf(buf, "%15s %llu", quota_buf, &constraint->period_length) != 2)
-    return UV_EINVAL;
+  snprintf(path, sizeof(path), "%s/%.*s", cgroup_mount, cgroup_size,
+           cgroup_trimmed);
 
-  if (strncmp(quota_buf, "max", 3) == 0)
-    constraint->quota_per_period = LLONG_MAX;
-  else if (sscanf(quota_buf, "%lld", &constraint->quota_per_period) != 1)
-    return UV_EINVAL; // conversion failed
+  /* Read cpu.max in the whole file-hierarchy */
+  while(strncmp(path, cgroup_mount, strlen(cgroup_mount)) == 0) {
+    snprintf(full_path, sizeof(full_path), "%s/cpu.max", path);
 
-  /* Construct the path to the cpu.weight file */
-  snprintf(path, sizeof(path), "/sys/fs/cgroup/%.*s/cpu.weight", cgroup_size,
-           cgroup_trimmed);
+    /* Silently ignore and continue if the file does not exist */
+    if (uv__slurp(full_path, quota_buf, sizeof(quota_buf)) < 0)
+      goto next;
 
-  /* Read cpu.weight */
-  if (uv__slurp(path, buf, sizeof(buf)) < 0)
-    return UV_EIO;
+    /* No limit, move on */
+    if (strncmp(quota_buf, "max", 3) == 0)
+      goto next;
 
-  if (sscanf(buf, "%u", &weight) != 1)
-    return UV_EINVAL;
+    /* Read cpu.max */
+    if (sscanf(quota_buf, "%lld %lld", &limit, &period) != 2)
+      goto next;
 
-  constraint->proportions = (double)weight / 100.0;
+    /* Can't divide by 0 */
+    if (period == 0)
+      goto next;
+
+    if ((limit / period) < min_quota)
+      min_quota = limit / period;
+
+next:
+    /* Continue in the file hierarchy */
+    last_slash = strrchr(path, '/');
+    if (last_slash == NULL || strcmp(path, cgroup_mount) == 0)
+      break;
+    *last_slash = '\0';
path: src/unix/linux.c
user: login: juanarbol
body: Fixed!
created_at: 2025-04-16T15:35:34Z
author_association: MEMBER
diff_hunk: @@ -2023,14 +2023,12 @@ unsigned int uv_available_parallelism(void) {
 
 #ifdef __linux__
   {
-    double rc_with_cgroup;
-    uv__cpu_constraint c = {0, 0, 0.0};
+    long long quota = 0;
 
-    if (uv__get_constrained_cpu(&c) == 0 && c.period_length > 0) {
-      rc_with_cgroup = (double)c.quota_per_period / c.period_length * c.proportions;
-      if (rc_with_cgroup < rc)
-        rc = (long)rc_with_cgroup; /* Casting is safe since rc_with_cgroup < rc < LONG_MAX */
-    }
+    if (uv__get_constrained_cpu(&quota) == 0)
+      if (quota > 0)
+        if (quota < rc)
path: src/unix/core.c
user: login: saghul
body: maybe combien there 2? `if (quota > 0 && quota < rc) ...` 
created_at: 2025-04-16T19:02:23Z
author_association: MEMBER
diff_hunk: @@ -2023,14 +2023,12 @@ unsigned int uv_available_parallelism(void) {
 
 #ifdef __linux__
   {
-    double rc_with_cgroup;
-    uv__cpu_constraint c = {0, 0, 0.0};
+    long long quota = 0;
 
-    if (uv__get_constrained_cpu(&c) == 0 && c.period_length > 0) {
-      rc_with_cgroup = (double)c.quota_per_period / c.period_length * c.proportions;
-      if (rc_with_cgroup < rc)
-        rc = (long)rc_with_cgroup; /* Casting is safe since rc_with_cgroup < rc < LONG_MAX */
-    }
+    if (uv__get_constrained_cpu(&quota) == 0)
+      if (quota > 0)
+        if (quota < rc)
path: src/unix/core.c
user: login: juanarbol
body: https://github.com/libuv/libuv/pull/4746#discussion_r2046866212 It was supposed to be a Ben's preference. Let me change it to its first recommendation, then
created_at: 2025-04-16T19:28:45Z
author_association: MEMBER

PR comments:
user: login: juanarbol
created_at: 2025-03-30T05:43:33Z
body: @bnoordhuis here we got some entertainment.

cc// @ggatus-atlassian feel free to pull and test.
author_association: MEMBER
user: login: bnoordhuis
created_at: 2025-03-30T21:42:03Z
body: I'll wait with reviewing until it's been tested.
author_association: MEMBER
user: login: juanarbol
created_at: 2025-04-01T00:36:06Z
body: I've tested the thing. This is my setup:

Image for `libuv-runner`:
```Dockerfile
FROM debian:bookworm

RUN apt-get update && apt-get install -y \
  gdb ca-certificates bash

USER 1000:1000
WORKDIR /home/devuser
CMD ["/bin/bash", "-c", "sleep infinity"]
```

 Kube pod manifest:
```yaml
apiVersion: v1
kind: Pod
metadata:
  name: libuv-platform-test
spec:
  restartPolicy: Never
  securityContext:
    runAsUser: 1000
    runAsGroup: 1000
    fsGroup: 1000
  volumes:
    - name: build-volume
      emptyDir: {}

  initContainers:
    - name: builder
      image: debian:bookworm
      imagePullPolicy: Always
      securityContext:
        runAsUser: 0
      command:
        - /bin/bash
        - -c
        - |
          apt-get update && apt-get install -y \
            git build-essential cmake curl python3 ca-certificates gdb \
            && git clone https://github.com/libuv/libuv.git /home/devuser/libuv \
            && cd /home/devuser/libuv \
            && git fetch origin pull/4746/head:pr-4746 && git checkout pr-4746 \
            && mkdir build && cd build \
            && cmake -DCMAKE_BUILD_TYPE=Debug .. && cmake --build . -j$(nproc)
      volumeMounts:
        - name: build-volume
          mountPath: /home/devuser
      resources:
        limits:
          cpu: "4"
          memory: 2Gi
        requests:
          cpu: "0"
          memory: 2Gi

  containers:
    - name: runner # --- PLEASE NOTE THIS
      image: libuv-runner:latest
      imagePullPolicy: Never
      command: ["/bin/bash"]
      args: ["-c", "sleep infinity"]
      volumeMounts:
        - name: build-volume
          mountPath: /home/devuser
      resources:
        limits:
          cpu: "4"
          memory: 2Gi
        requests:
          cpu: "0" # --- PLEASE NOTE THIS
          memory: 2Gi
```

I used gdb to make sure the implementation was doing what it is meant to do. For now, just part of the test output is enough:
```sh
$ kubectl exec -it libuv-platform -c runner -- /bin/bash
I have no name!@libuv-platform:/home/devuser$ cd libuv/build/
I have no name!@libuv-platform:/home/devuser/libuv/build$ ./uv_run_tests_a platform_output platform_output
uv_get_process_title: ./uv_run_tests_a
uv_cwd: /home/devuser/libuv/build
uv_resident_set_memory: 1867776
uv_uptime: 5519.560000
uv_getrusage:
  user: 0 sec 0 microsec
  system: 0 sec 1776 microsec
  page faults: 0
  maximum resident set size: 1824
uv_available_parallelism: 4
cgroup v2 available parallelism: 4
...
```

The implementation is correct as It iterates over the whole file-hierarchy, instead of giving up at the first read operation.
author_association: MEMBER
user: login: alumni
created_at: 2025-04-24T14:00:07Z
body: Any chance this would make it in node 22 anytime soon?

Our CI is 40-60% slower on node 22 compared to node 20.
author_association: NONE
user: login: bnoordhuis
created_at: 2025-04-24T17:59:37Z
body: Wrong place to ask, that's up to node.
author_association: MEMBER