systemd/test/units/TEST-55-OOMD.sh

191 lines
6.7 KiB
Bash
Raw Normal View History

2021-03-05 09:36:04 +00:00
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
set -eux
set -o pipefail
# shellcheck source=test/units/util.sh
. "$(dirname "$0")"/util.sh
. /etc/os-release
# OpenSUSE does not have the stress tool packaged. It does have stress-ng but the stress-ng does not support
# --vm-stride which this test uses.
if [[ "$ID" =~ "opensuse" ]]; then
echo "Skipping due to missing stress package in OpenSUSE" >>/skipped
exit 77
fi
2021-03-05 09:36:04 +00:00
systemd-analyze log-level debug
# Ensure that the init.scope.d drop-in is applied on boot
test "$(cat /sys/fs/cgroup/init.scope/memory.high)" != "max"
2021-03-05 09:36:04 +00:00
# Loose checks to ensure the environment has the necessary features for systemd-oomd
[[ -e /proc/pressure ]] || echo "no PSI" >>/skipped
2024-02-09 17:53:19 +00:00
[[ "$(get_cgroup_hierarchy)" == "unified" ]] || echo "no cgroupsv2" >>/skipped
[[ -x /usr/lib/systemd/systemd-oomd ]] || echo "no oomd" >>/skipped
if [[ -s /skipped ]]; then
exit 77
fi
rm -rf /run/systemd/system/TEST-55-OOMD-testbloat.service.d
# Activate swap file if we are in a VM
if systemd-detect-virt --vm --quiet; then
swapoff --all
if [[ "$(findmnt -n -o FSTYPE /)" == btrfs ]]; then
btrfs filesystem mkswapfile -s 64M /swapfile
else
dd if=/dev/zero of=/swapfile bs=1M count=64
chmod 0600 /swapfile
mkswap /swapfile
fi
swapon /swapfile
swapon --show
fi
# Configure oomd explicitly to avoid conflicts with distro dropins
mkdir -p /run/systemd/oomd.conf.d/
cat >/run/systemd/oomd.conf.d/99-oomd-test.conf <<EOF
[OOM]
DefaultMemoryPressureDurationSec=2s
EOF
mkdir -p /run/systemd/system/-.slice.d/
cat >/run/systemd/system/-.slice.d/99-oomd-test.conf <<EOF
[Slice]
ManagedOOMSwap=auto
EOF
mkdir -p /run/systemd/system/user@.service.d/
cat >/run/systemd/system/user@.service.d/99-oomd-test.conf <<EOF
[Service]
ManagedOOMMemoryPressure=auto
ManagedOOMMemoryPressureLimit=0%
EOF
mkdir -p /run/systemd/system/systemd-oomd.service.d/
cat >/run/systemd/system/systemd-oomd.service.d/debug.conf <<EOF
[Service]
Environment=SYSTEMD_LOG_LEVEL=debug
EOF
systemctl daemon-reload
# enable the service to ensure dbus-org.freedesktop.oom1.service exists
# and D-Bus activation works
systemctl enable systemd-oomd.service
# if oomd is already running for some reasons, then restart it to make sure the above settings to be applied
if systemctl is-active systemd-oomd.service; then
systemctl restart systemd-oomd.service
fi
test: make the MemoryHigh= limit a bit more generous with sanitizers When we're running with sanitizers, sd-executor might pull in a significant chunk of shared libraries on startup, that can cause a lot of memory pressure and put us in the front when sd-oomd decides to go on a killing spree. This is exacerbated further on Arch Linux when built with gcc, as Arch ships unstripped gcc-libs so sd-executor pulls in over 30M of additional shared libs on startup: ~# lddtree build-san/systemd-executor build-san/systemd-executor (interpreter => /lib64/ld-linux-x86-64.so.2) libasan.so.8 => /usr/lib/libasan.so.8 libstdc++.so.6 => /usr/lib/libstdc++.so.6 libm.so.6 => /usr/lib/libm.so.6 libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 libsystemd-core-255.so => /root/systemd/build-san/src/core/libsystemd-core-255.so libaudit.so.1 => /usr/lib/libaudit.so.1 libcap-ng.so.0 => /usr/lib/libcap-ng.so.0 ... libseccomp.so.2 => /usr/lib/libseccomp.so.2 libubsan.so.1 => /usr/lib/libubsan.so.1 libc.so.6 => /usr/lib/libc.so.6 ~# ls -Llh /usr/lib/libasan.so.8 /usr/lib/libstdc++.so.6 /usr/lib/libubsan.so.1 -rwxr-xr-x 1 root root 9.7M Feb 2 10:36 /usr/lib/libasan.so.8 -rwxr-xr-x 1 root root 21M Feb 2 10:36 /usr/lib/libstdc++.so.6 -rwxr-xr-x 1 root root 3.2M Feb 2 10:36 /usr/lib/libubsan.so.1 Sanitized libsystemd-core.so is also quite big: ~# ls -Llh /root/systemd/build-san/src/core/libsystemd-core-255.so /usr/lib/systemd/libsystemd-core-255.so -rwxr-xr-x 1 root root 26M Feb 8 19:04 /root/systemd/build-san/src/core/libsystemd-core-255.so -rwxr-xr-x 1 root root 5.9M Feb 7 12:03 /usr/lib/systemd/libsystemd-core-255.so
2024-02-09 17:44:58 +00:00
if [[ -v ASAN_OPTIONS || -v UBSAN_OPTIONS ]]; then
# If we're running with sanitizers, sd-executor might pull in quite a significant chunk of shared
# libraries, which in turn causes a lot of pressure that can put us in the front when sd-oomd decides to
# go on a killing spree. This fact is exacerbated further on Arch Linux which ships unstripped gcc-libs,
# so sd-executor pulls in over 30M of libs on startup. Let's make the MemoryHigh= limit a bit more
# generous when running with sanitizers to make the test happy.
systemctl edit --runtime --stdin --drop-in=99-MemoryHigh.conf TEST-55-OOMD-testchill.service <<EOF
test: make the MemoryHigh= limit a bit more generous with sanitizers When we're running with sanitizers, sd-executor might pull in a significant chunk of shared libraries on startup, that can cause a lot of memory pressure and put us in the front when sd-oomd decides to go on a killing spree. This is exacerbated further on Arch Linux when built with gcc, as Arch ships unstripped gcc-libs so sd-executor pulls in over 30M of additional shared libs on startup: ~# lddtree build-san/systemd-executor build-san/systemd-executor (interpreter => /lib64/ld-linux-x86-64.so.2) libasan.so.8 => /usr/lib/libasan.so.8 libstdc++.so.6 => /usr/lib/libstdc++.so.6 libm.so.6 => /usr/lib/libm.so.6 libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 libsystemd-core-255.so => /root/systemd/build-san/src/core/libsystemd-core-255.so libaudit.so.1 => /usr/lib/libaudit.so.1 libcap-ng.so.0 => /usr/lib/libcap-ng.so.0 ... libseccomp.so.2 => /usr/lib/libseccomp.so.2 libubsan.so.1 => /usr/lib/libubsan.so.1 libc.so.6 => /usr/lib/libc.so.6 ~# ls -Llh /usr/lib/libasan.so.8 /usr/lib/libstdc++.so.6 /usr/lib/libubsan.so.1 -rwxr-xr-x 1 root root 9.7M Feb 2 10:36 /usr/lib/libasan.so.8 -rwxr-xr-x 1 root root 21M Feb 2 10:36 /usr/lib/libstdc++.so.6 -rwxr-xr-x 1 root root 3.2M Feb 2 10:36 /usr/lib/libubsan.so.1 Sanitized libsystemd-core.so is also quite big: ~# ls -Llh /root/systemd/build-san/src/core/libsystemd-core-255.so /usr/lib/systemd/libsystemd-core-255.so -rwxr-xr-x 1 root root 26M Feb 8 19:04 /root/systemd/build-san/src/core/libsystemd-core-255.so -rwxr-xr-x 1 root root 5.9M Feb 7 12:03 /usr/lib/systemd/libsystemd-core-255.so
2024-02-09 17:44:58 +00:00
[Service]
MemoryHigh=60M
EOF
# Do the same for the user instance as well
mkdir -p /run/systemd/user/
cp -rfv /run/systemd/system/TEST-55-OOMD-testchill.service.d/ /run/systemd/user/
test: make the MemoryHigh= limit a bit more generous with sanitizers When we're running with sanitizers, sd-executor might pull in a significant chunk of shared libraries on startup, that can cause a lot of memory pressure and put us in the front when sd-oomd decides to go on a killing spree. This is exacerbated further on Arch Linux when built with gcc, as Arch ships unstripped gcc-libs so sd-executor pulls in over 30M of additional shared libs on startup: ~# lddtree build-san/systemd-executor build-san/systemd-executor (interpreter => /lib64/ld-linux-x86-64.so.2) libasan.so.8 => /usr/lib/libasan.so.8 libstdc++.so.6 => /usr/lib/libstdc++.so.6 libm.so.6 => /usr/lib/libm.so.6 libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 libsystemd-core-255.so => /root/systemd/build-san/src/core/libsystemd-core-255.so libaudit.so.1 => /usr/lib/libaudit.so.1 libcap-ng.so.0 => /usr/lib/libcap-ng.so.0 ... libseccomp.so.2 => /usr/lib/libseccomp.so.2 libubsan.so.1 => /usr/lib/libubsan.so.1 libc.so.6 => /usr/lib/libc.so.6 ~# ls -Llh /usr/lib/libasan.so.8 /usr/lib/libstdc++.so.6 /usr/lib/libubsan.so.1 -rwxr-xr-x 1 root root 9.7M Feb 2 10:36 /usr/lib/libasan.so.8 -rwxr-xr-x 1 root root 21M Feb 2 10:36 /usr/lib/libstdc++.so.6 -rwxr-xr-x 1 root root 3.2M Feb 2 10:36 /usr/lib/libubsan.so.1 Sanitized libsystemd-core.so is also quite big: ~# ls -Llh /root/systemd/build-san/src/core/libsystemd-core-255.so /usr/lib/systemd/libsystemd-core-255.so -rwxr-xr-x 1 root root 26M Feb 8 19:04 /root/systemd/build-san/src/core/libsystemd-core-255.so -rwxr-xr-x 1 root root 5.9M Feb 7 12:03 /usr/lib/systemd/libsystemd-core-255.so
2024-02-09 17:44:58 +00:00
else
# Ensure that we can start services even with a very low hard memory cap without oom-kills, but skip
# under sanitizers as they balloon memory usage.
core: add systemd-executor binary Currently we spawn services by forking a child process, doing a bunch of work, and then exec'ing the service executable. There are some advantages to this approach: - quick: we immediately have access to all the enourmous amount of state simply by virtue of sharing the memory with the parent - easy to refactor and add features - part of the same binary, will never be out of sync There are however significant drawbacks: - doing work after fork and before exec is against glibc's supported case for several APIs we call - copy-on-write trap: anytime any memory is touched in either parent or child, a copy of that page will be triggered - memory footprint of the child process will be memory footprint of PID1, but using the cgroup memory limits of the unit The last issue is especially problematic on resource constrained systems where hard memory caps are enforced and swap is not allowed. As soon as PID1 is under load, with no page out due to no swap, and a service with a low MemoryMax= tries to start, hilarity ensues. Add a new systemd-executor binary, that is able to receive all the required state via memfd, deserialize it, prepare the appropriate data structures and call exec_child. Use posix_spawn which uses CLONE_VM + CLONE_VFORK, to ensure there is no copy-on-write (same address space will be used, and parent process will be frozen, until exec). The sd-executor binary is pinned by FD on startup, so that we can guarantee there will be no incompatibilities during upgrades.
2023-06-01 18:51:42 +00:00
systemd-run -t -p MemoryMax=10M -p MemorySwapMax=0 -p MemoryZSwapMax=0 /bin/true
fi
systemctl start TEST-55-OOMD-testchill.service
systemctl start TEST-55-OOMD-testbloat.service
2021-03-05 09:36:04 +00:00
# Verify systemd-oomd is monitoring the expected units
timeout 1m bash -xec 'until oomctl | grep "/TEST-55-OOMD-workload.slice"; do sleep 1; done'
oomctl | grep "/TEST-55-OOMD-workload.slice"
2024-02-09 17:53:19 +00:00
oomctl | grep "20.00%"
oomctl | grep "Default Memory Pressure Duration: 2s"
systemctl status TEST-55-OOMD-testchill.service
# systemd-oomd watches for elevated pressure for 2 seconds before acting.
2021-03-05 09:36:04 +00:00
# It can take time to build up pressure so either wait 2 minutes or for the service to fail.
2024-02-09 17:53:19 +00:00
for _ in {0..59}; do
if ! systemctl status TEST-55-OOMD-testbloat.service; then
break
2021-03-05 09:36:04 +00:00
fi
oomctl
sleep 2
done
2021-03-05 09:36:04 +00:00
# testbloat should be killed and testchill should be fine
if systemctl status TEST-55-OOMD-testbloat.service; then exit 42; fi
if ! systemctl status TEST-55-OOMD-testchill.service; then exit 24; fi
2021-03-05 09:36:04 +00:00
# Make sure we also work correctly on user units.
test: temporarily enable session lingering for the test user #2 Similarly to bbac11c993 we need to enable session lingering for the test user, so the long-running test units are not killed prematurely: [ 18.822261] testsuite-55.sh[403]: + systemctl start --machine testuser@.host --user testsuite-55-testchill.service [ 18.852775] systemd[1]: Started run-u17.service. [ 19.256431] (o-bridge)[526]: pam_unix(login:session): session opened for user testuser(uid=4711) by testuser(uid=0) [ 19.288346] systemd[1]: Started session-2.scope. [ 20.165874] systemd[392]: Created slice session.slice. [ 20.166459] systemd[392]: Starting dbus-broker.service... [ 20.220189] dbus-broker-launch[529]: Policy to allow eavesdropping in /usr/share/dbus-1/session.conf +31: Eavesdropping is deprecated and ignored [ 20.220189] dbus-broker-launch[529]: Policy to allow eavesdropping in /usr/share/dbus-1/session.conf +33: Eavesdropping is deprecated and ignored [ 20.220494] systemd[392]: Started dbus-broker.service. [ 20.224276] dbus-broker-launch[529]: Ready [ 20.231702] systemd[392]: Created slice testsuite.slice. [ 20.231976] systemd[392]: Created slice testsuite-55.slice. [ 20.232259] systemd[392]: Created slice testsuite-55-workload.slice. [ 31.065294] testsuite-55.sh[403]: + systemctl start --machine testuser@.host --user testsuite-55-testbloat.service [ 31.065641] (sd-pam)[528]: pam_unix(login:session): session closed for user testuser [ 31.066103] (sd-pam)[528]: pam_systemd(login:session): Failed to release session: Access denied [ 31.066152] systemd[392]: Started testsuite-55-testchill.service. [ 31.068062] systemd[1]: run-u17.service: Deactivated successfully. [ 31.068217] dbus-broker[389]: A security policy denied :1.20 to send method call /org/freedesktop/login1:org.freedesktop.login1.Manager.ReleaseSession to org.freedesktop.login1. [ 31.075901] (o-bridge)[537]: pam_unix(login:session): session opened for user testuser(uid=4711) by testuser(uid=0) [ 31.091098] systemd[1]: Stopping session-2.scope... [ 31.092158] systemd[1]: Started run-u21.service. [ 31.092993] systemd[1]: session-2.scope: Deactivated successfully. [ 31.093287] systemd[1]: Stopped session-2.scope. [ 31.095798] systemd[1]: Stopping user@4711.service... [ 31.103541] systemd[392]: Activating special unit exit.target... [ 31.108359] systemd[392]: Stopped target default.target. [ 31.109798] systemd[392]: Stopped target timers.target. [ 31.110790] systemd[392]: Stopping testsuite-55-testchill.service... [ 31.112154] systemd[392]: Stopped testsuite-55-testchill.service. [ 31.114033] systemd[392]: Removed slice testsuite-55-workload.slice. [ 31.114971] systemd[392]: Removed slice testsuite-55.slice. [ 31.115858] systemd[392]: Removed slice testsuite.slice. ... [ 31.475949] testsuite-55.sh[403]: + systemctl --machine testuser@.host --user status testsuite-55-testchill.service [ 31.490464] systemd[1]: session-3.scope: Deactivated successfully. [ 31.565929] systemd[1]: Started run-u33.service. [ 31.592437] (o-bridge)[583]: pam_unix(login:session): session opened for user testuser(uid=4711) by testuser(uid=0) [ 31.610210] systemd[1]: Started session-5.scope. [ 31.616960] testsuite-55.sh[578]: ○ testsuite-55-testchill.service - No memory pressure [ 31.616960] testsuite-55.sh[578]: Loaded: loaded (/usr/lib/systemd/tests/testdata/units/testsuite-55-testchill.service; static) [ 31.616960] testsuite-55.sh[578]: Active: inactive (dead) [ 31.617438] (sd-pam)[586]: pam_unix(login:session): session closed for user testuser Addresses https://github.com/systemd/systemd/pull/31426#issuecomment-1956436844.
2024-02-21 14:42:35 +00:00
loginctl enable-linger testuser
systemctl start --machine "testuser@.host" --user TEST-55-OOMD-testchill.service
systemctl start --machine "testuser@.host" --user TEST-55-OOMD-testbloat.service
# Verify systemd-oomd is monitoring the expected units
# Try to avoid racing the oomctl output check by checking in a loop with a timeout
timeout 1m bash -xec 'until oomctl | grep "/TEST-55-OOMD-workload.slice"; do sleep 1; done'
oomctl | grep -E "/user.slice.*/TEST-55-OOMD-workload.slice"
2024-02-09 17:53:19 +00:00
oomctl | grep "20.00%"
oomctl | grep "Default Memory Pressure Duration: 2s"
systemctl --machine "testuser@.host" --user status TEST-55-OOMD-testchill.service
# systemd-oomd watches for elevated pressure for 2 seconds before acting.
# It can take time to build up pressure so either wait 2 minutes or for the service to fail.
2024-02-09 17:53:19 +00:00
for _ in {0..59}; do
if ! systemctl --machine "testuser@.host" --user status TEST-55-OOMD-testbloat.service; then
break
fi
oomctl
sleep 2
done
# testbloat should be killed and testchill should be fine
if systemctl --machine "testuser@.host" --user status TEST-55-OOMD-testbloat.service; then exit 42; fi
if ! systemctl --machine "testuser@.host" --user status TEST-55-OOMD-testchill.service; then exit 24; fi
test: temporarily enable session lingering for the test user #2 Similarly to bbac11c993 we need to enable session lingering for the test user, so the long-running test units are not killed prematurely: [ 18.822261] testsuite-55.sh[403]: + systemctl start --machine testuser@.host --user testsuite-55-testchill.service [ 18.852775] systemd[1]: Started run-u17.service. [ 19.256431] (o-bridge)[526]: pam_unix(login:session): session opened for user testuser(uid=4711) by testuser(uid=0) [ 19.288346] systemd[1]: Started session-2.scope. [ 20.165874] systemd[392]: Created slice session.slice. [ 20.166459] systemd[392]: Starting dbus-broker.service... [ 20.220189] dbus-broker-launch[529]: Policy to allow eavesdropping in /usr/share/dbus-1/session.conf +31: Eavesdropping is deprecated and ignored [ 20.220189] dbus-broker-launch[529]: Policy to allow eavesdropping in /usr/share/dbus-1/session.conf +33: Eavesdropping is deprecated and ignored [ 20.220494] systemd[392]: Started dbus-broker.service. [ 20.224276] dbus-broker-launch[529]: Ready [ 20.231702] systemd[392]: Created slice testsuite.slice. [ 20.231976] systemd[392]: Created slice testsuite-55.slice. [ 20.232259] systemd[392]: Created slice testsuite-55-workload.slice. [ 31.065294] testsuite-55.sh[403]: + systemctl start --machine testuser@.host --user testsuite-55-testbloat.service [ 31.065641] (sd-pam)[528]: pam_unix(login:session): session closed for user testuser [ 31.066103] (sd-pam)[528]: pam_systemd(login:session): Failed to release session: Access denied [ 31.066152] systemd[392]: Started testsuite-55-testchill.service. [ 31.068062] systemd[1]: run-u17.service: Deactivated successfully. [ 31.068217] dbus-broker[389]: A security policy denied :1.20 to send method call /org/freedesktop/login1:org.freedesktop.login1.Manager.ReleaseSession to org.freedesktop.login1. [ 31.075901] (o-bridge)[537]: pam_unix(login:session): session opened for user testuser(uid=4711) by testuser(uid=0) [ 31.091098] systemd[1]: Stopping session-2.scope... [ 31.092158] systemd[1]: Started run-u21.service. [ 31.092993] systemd[1]: session-2.scope: Deactivated successfully. [ 31.093287] systemd[1]: Stopped session-2.scope. [ 31.095798] systemd[1]: Stopping user@4711.service... [ 31.103541] systemd[392]: Activating special unit exit.target... [ 31.108359] systemd[392]: Stopped target default.target. [ 31.109798] systemd[392]: Stopped target timers.target. [ 31.110790] systemd[392]: Stopping testsuite-55-testchill.service... [ 31.112154] systemd[392]: Stopped testsuite-55-testchill.service. [ 31.114033] systemd[392]: Removed slice testsuite-55-workload.slice. [ 31.114971] systemd[392]: Removed slice testsuite-55.slice. [ 31.115858] systemd[392]: Removed slice testsuite.slice. ... [ 31.475949] testsuite-55.sh[403]: + systemctl --machine testuser@.host --user status testsuite-55-testchill.service [ 31.490464] systemd[1]: session-3.scope: Deactivated successfully. [ 31.565929] systemd[1]: Started run-u33.service. [ 31.592437] (o-bridge)[583]: pam_unix(login:session): session opened for user testuser(uid=4711) by testuser(uid=0) [ 31.610210] systemd[1]: Started session-5.scope. [ 31.616960] testsuite-55.sh[578]: ○ testsuite-55-testchill.service - No memory pressure [ 31.616960] testsuite-55.sh[578]: Loaded: loaded (/usr/lib/systemd/tests/testdata/units/testsuite-55-testchill.service; static) [ 31.616960] testsuite-55.sh[578]: Active: inactive (dead) [ 31.617438] (sd-pam)[586]: pam_unix(login:session): session closed for user testuser Addresses https://github.com/systemd/systemd/pull/31426#issuecomment-1956436844.
2024-02-21 14:42:35 +00:00
loginctl disable-linger testuser
2021-03-05 09:36:04 +00:00
# only run this portion of the test if we can set xattrs
if cgroupfs_supports_user_xattrs; then
2021-03-05 09:36:04 +00:00
sleep 120 # wait for systemd-oomd kill cool down and elevated memory pressure to come down
mkdir -p /run/systemd/system/TEST-55-OOMD-testbloat.service.d/
cat >/run/systemd/system/TEST-55-OOMD-testbloat.service.d/override.conf <<EOF
[Service]
ManagedOOMPreference=avoid
EOF
2021-03-05 09:36:04 +00:00
systemctl daemon-reload
systemctl start TEST-55-OOMD-testchill.service
systemctl start TEST-55-OOMD-testmunch.service
systemctl start TEST-55-OOMD-testbloat.service
2021-03-05 09:36:04 +00:00
2024-02-09 17:53:19 +00:00
for _ in {0..59}; do
if ! systemctl status TEST-55-OOMD-testmunch.service; then
2021-03-05 09:36:04 +00:00
break
fi
oomctl
sleep 2
2021-03-05 09:36:04 +00:00
done
# testmunch should be killed since testbloat had the avoid xattr on it
if ! systemctl status TEST-55-OOMD-testbloat.service; then exit 25; fi
if systemctl status TEST-55-OOMD-testmunch.service; then exit 43; fi
if ! systemctl status TEST-55-OOMD-testchill.service; then exit 24; fi
2021-03-05 09:36:04 +00:00
fi
systemd-analyze log-level info
touch /testok