]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
test: avoid using external commands in trap handlers
authorFrantisek Sumsal <frantisek@sumsal.cz>
Tue, 21 Apr 2026 10:07:02 +0000 (12:07 +0200)
committerLuca Boccassi <luca.boccassi@gmail.com>
Tue, 21 Apr 2026 15:19:47 +0000 (16:19 +0100)
In #39675 the reported fail was as follows:

5580s [  247.559994] TEST-13-NSPAWN.sh[1858]: Exported 93%.
5580s [  247.659002] TEST-13-NSPAWN.sh[1858]: Exported 95%.
5580s [  247.785893] TEST-13-NSPAWN.sh[1858]: Operation completed successfully.
5580s [  247.923727] TEST-13-NSPAWN.sh[1858]: Exiting.
5580s [  258.300406] TEST-13-NSPAWN.sh[1074]: + machinectl import-raw /var/tmp/container-export.raw container-raw-reimport
5580s [  258.323328] TEST-13-NSPAWN.sh[1884]: The 'machinectl import-raw' command has been replaced by 'importctl -m import-raw'. Redirecting invocation.
5580s [  258.659982] TEST-13-NSPAWN.sh[1884]: Failed to transfer image: Remote peer disconnected
5580s [  258.734218] TEST-13-NSPAWN.sh[1074]: + at_exit

Turns out that the real reason behind this fail is that the machine was
under heavy load due to a busy-loop from the stub init. The cause of
this is a bug in bash, where running commands that fork (i.e. not
built-ins) can cause a permanent busy-loop due to a desync in trap
handling if you send the signals to the bash process _just right_:

[   90.855318] TEST-13-NSPAWN.sh[1074]: + machinectl poweroff long-running long-running long-running
[   90.855318] TEST-13-NSPAWN.sh[1074]: + machinectl reboot long-running long-running long-running
[   90.928980] systemd-nspawn[1679]: ++ touch /poweroff
[   90.928980] systemd-nspawn[1679]: +++ touch /reboot
[   90.928980] systemd-nspawn[1679]: + :
[   90.928980] systemd-nspawn[1679]: + :
[   90.928980] systemd-nspawn[1679]: + wait
[   90.928980] systemd-nspawn[1679]: + :
[   90.928980] systemd-nspawn[1679]: + :
[   90.928980] systemd-nspawn[1679]: + wait
[   90.928980] systemd-nspawn[1679]: + :
[   90.928980] systemd-nspawn[1679]: + :
[   90.928980] systemd-nspawn[1679]: + wait
...

$ journalctl --file TEST-13-NSPAWN-1.journal -o short-monotonic --no-hostname --grep "^\+ wait$" | wc -l
349734

So the stub-init was hammering the machine in a tight endless loop,
which then caused systemd-importd to timeout when talking to D-Bus:

[  258.300096] TEST-13-NSPAWN.sh[1074]: + machinectl import-raw /var/tmp/container-export.raw container-raw-reimport
...
[  258.415319] systemd-importd[1859]: Unable to request name, failing connection: Method call timed out
[  258.483662] systemd-importd[1859]: Bus n/a: changing state RUNNING → CLOSING
[  258.605442] systemd-importd[1859]: Bus n/a: changing state CLOSING → CLOSED
[  258.659958] TEST-13-NSPAWN.sh[1884]: Failed to transfer image: Remote peer disconnected

Given this is not our issue, let's work around it by using just
built-ins from the trap handlers, which are not susceptible to this bug.

Resolves: #39675

test/units/TEST-13-NSPAWN.machined.sh

index 34307f3c8b16264809fa56ead4247cc469be009d..de51daa24c73de049105f923146a7a2936beb0da 100755 (executable)
@@ -44,21 +44,27 @@ set -x
 
 PID=0
 
-trap 'touch /terminate; kill 0' RTMIN+3
-trap 'touch /poweroff' RTMIN+4
-trap 'touch /reboot' INT
-trap 'touch /trap' TRAP
+# Use only builtins in trap handlers to avoid forking. External commands
+# (like touch) cause bash to enter wait_for() for the child, and a nested
+# signal arriving during that wait triggers a bash bug where
+# run_interrupt_trap() clears catch_flag while other traps are still
+# pending, creating an orphaned pending_traps[] entry that makes 'wait'
+# busy-loop indefinitely.
+trap ': >/terminate; kill 0' RTMIN+3
+trap ': >/poweroff' RTMIN+4
+trap ': >/reboot' INT
+trap ': >/trap' TRAP
 trap 'exit 0' TERM
 trap 'kill $PID' EXIT
 
 # We need to wait for the sleep process asynchronously in order to allow
 # bash to process signals
 sleep infinity &
+PID=$!
 
 # notify that the process is ready
-touch /ready
+: >/ready
 
-PID=$!
 while :; do
     wait || :
 done
@@ -332,11 +338,11 @@ trap 'kill $PID' EXIT
 # We need to wait for the sleep process asynchronously in order to allow
 # bash to process signals
 sleep infinity &
+PID=$!
 
 # notify that the process is ready
-touch /ready
+: >/ready
 
-PID=$!
 while :; do
     wait || :
 done