#!/bin/sh # Ensure that "nproc" honors cgroup quotas # Copyright (C) 2025 Free Software Foundation, Inc. # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . . "${srcdir=.}/tests/init.sh"; path_prepend_ ./src print_ver_ nproc require_root_ require_gcc_shared_ # Replace sched_getscheduler() cat > k.c <<'EOF' || framework_failure_ #include #include #define __USE_GNU /* For SCHED_DEADLINE. */ #include int sched_getscheduler (pid_t pid) { fclose (fopen ("preloaded","w")); /* marker for preloaded interception */ FILE* policyf = fopen ("/proc/self/sched", "r"); int policy; #define fscanfmt fscanf /* Avoid syntax check. */ if (pid == 0 && fscanfmt (policyf, "policy : %d", &policy) == 1) { switch (policy) { case 0: return SCHED_OTHER; case 1: return SCHED_FIFO; case 2: return SCHED_RR; case 6: return SCHED_DEADLINE; case -1: errno = EINVAL; return -1; default: return SCHED_OTHER; } } else { errno = ENOSYS; return -1; } } EOF # compile/link the interception shared library: gcc_shared_ k.c k.so \ || skip_ 'failed to build sched_getscheduler shared library' (export LD_PRELOAD=$LD_PRELOAD:./k.so nproc) || fail=1 # Ensure our wrapper is in place and is called # I.e., this test is restricted to new enough Linux systems # as otherwise cpu_quota() will not call sched_getscheduler() test -e preloaded || skip_ 'LD_PRELOAD interception failed' # We could look for modifiable cgroup quotas on the current system, # but that would be dangerous to modify and restore robustly. # Instead simulate the queried cgroup files in a chroot. # For recipes for testing with real cgroups see # the comment in: gnulib/tests/test-nproc.c ROOT=cgroup { mkdir -p $ROOT/proc/self && mkdir -p $ROOT/sys/fs/cgroup/foo && touch $ROOT/sys/fs/cgroup/cgroup.controllers && echo '0::/foo' > $ROOT/proc/self/cgroup && echo 'policy : 0' > $ROOT/proc/self/sched && echo 'max 100000' > $ROOT/sys/fs/cgroup/foo/cpu.max # ignored } || framework_failure_ nproc=$abs_top_builddir/src/nproc$EXEEXT cp --parents $(ldd $nproc | grep -o '/[^ ]*') $ROOT || skip_ 'Failed to copy nproc libs to chroot' cp $nproc $ROOT || framework_failure_ cp k.so $ROOT || framework_failure_ NPROC() { LD_PRELOAD=$LD_PRELOAD:./k.so chroot $ROOT /nproc "$@"; } NPROC --version || skip_ 'Failed to execute nproc in chroot' unset OMP_NUM_THREADS unset OMP_THREAD_LIMIT ncpus=$(nproc) || fail=1 # Avoid the following block if the system may # already have CPU quotas set, as our simulation # would then not match $ncpus if test "$ncpus" = "$(nproc --all)"; then echo 'max 100000' > $ROOT/sys/fs/cgroup/cpu.max && test $(NPROC) -eq $ncpus || fail=1 # Note Linux won't allow values > 1,000,000 (i.e., micro seconds) # so no point testing above that threshold ( for e.g $((1<<53)) ) echo "1000000 1" > $ROOT/sys/fs/cgroup/cpu.max && test $(NPROC) -eq $ncpus || fail=1 # Can't happen in real cgroup, but ensure we avoid divide by zero echo '100000 0' > $ROOT/sys/fs/cgroup/cpu.max && test $(NPROC) -eq $ncpus || fail=1 echo '100000 100000' > $ROOT/sys/fs/cgroup/cpu.max && test $(OMP_NUM_THREADS=$ncpus NPROC) -eq $ncpus || fail=1 echo '100000 100000' > $ROOT/sys/fs/cgroup/cpu.max && echo 'policy : 1' > $ROOT/proc/self/sched && # No quota for SCHED_FIFO test $(NPROC) -eq $ncpus || fail=1 echo 'policy : 2' > $ROOT/proc/self/sched && # No quota for SCHED_RR test $(NPROC) -eq $ncpus || fail=1 echo 'policy : 6' > $ROOT/proc/self/sched && # No quota for SCHED_DEADLINE test $(NPROC) -eq $ncpus || fail=1 echo 'policy : -1' > $ROOT/proc/self/sched && # Unsupported test $(NPROC) -eq $ncpus || fail=1 echo 'policy : 0' > $ROOT/proc/self/sched # reset to SCHED_OTHER fi echo '40000 100000' > $ROOT/sys/fs/cgroup/cpu.max && test $(NPROC) -eq 1 || fail=1 echo '50000 100000' > $ROOT/sys/fs/cgroup/cpu.max && test $(NPROC) -eq 1 || fail=1 echo '100000 100000' > $ROOT/sys/fs/cgroup/cpu.max && test $(NPROC) -eq 1 || fail=1 echo '140000 100000' > $ROOT/sys/fs/cgroup/cpu.max && test $(NPROC) -eq 1 || fail=1 echo '1 1000000' > $ROOT/sys/fs/cgroup/cpu.max && test $(NPROC) -eq 1 || fail=1 if test $ncpus -gt 1; then echo '150000 100000' > $ROOT/sys/fs/cgroup/cpu.max && test $(NPROC) -eq 2 || fail=1 echo '150000 100000' > $ROOT/sys/fs/cgroup/cpu.max && test $(OMP_THREAD_LIMIT=10 NPROC) -eq 2 || fail=1 fi Exit $fail