From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.1 (2015-04-28) on sa.local.altlinux.org X-Spam-Level: X-Spam-Status: No, score=-2.0 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FROM autolearn=ham autolearn_force=no version=3.4.1 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1792682163; x=1793286963; darn=lists.altlinux.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:from:to:cc:subject:date:message-id :reply-to:content-type; bh=HOmvcG6kvde4sIjt5mhT0AzYwSNbJ0qpXyOo7yam2yU=; b=p1trS92Bal9D0y7EvoA3qid+4M7xaR+hCfgLD+5G57V7w4g4RLmDT8gN+W4Zkp2MrO YRcRy8eGfdQzuo8dIBN1ZNTe3j3PRcgHqg/9D3XWvlQfvCz/ANfSADr9PWveIrDmuQxO 3LwnoKFEum5jMPr2bYOHByUkLaQ9nTXQt58Ow1dpvJBSNYtf1im4S1oJwWauW3JNJWa2 5A616DYZsEtPJl5clvdhMIejm9OORjHM8cubOekDxpu1gkMPJVRKlMgizi9jOnta1LU7 WHZVDt7IPXuJ5eH+Ghzcp0eNrEdOOJdjo75gtF90WJWdBaQUGaUQZt7xtilo8TDmPb3W Bp8g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1792682163; x=1793286963; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to:content-type; bh=HOmvcG6kvde4sIjt5mhT0AzYwSNbJ0qpXyOo7yam2yU=; b=iLO0bvUmvYyUQ8Nh0xAKI0FduVDXXWq4fsBzofyjZwxU2/emPcSAv8byyazutLNm8b xIwRdMh5uj7doBXk86ZToigvF9e2FLqZcEkXOew/Ua/HaMdznht/h2hJ3Wy08xru57eK UHDKDtkzA5oV4y7nxDRUablKmETbj9J+xONzvDm+BWf6DeOQxli2Px16edtbZGfAWJbh ikHvSIuga+G0uopolav6MQs//sDJyhniJf6l3saLqiKCrNMcqG9aOOa7+5J4wXai5pSG HuLhGr7dg8FwGpjr3NpfD/zSlJBv286RgQ0ns4fb0DADEljzvNctysJnXKv+fv7hMG0t Bl8Q== X-Gm-Message-State: AFq9FYKj6rPmuerCL9ojas6P5Y4hBmrJfC7ntrxIjKSJl/wW0i1Txsp4 Gqvr2iB4fYWND1nV7BCz0M3TKvfG2pcEpNa45WJaVx13cnh97JGUOWAEPLD70g== X-Gm-Gg: AYBFou3WDX0jd6TRjcsEP4ISxRDePSMev9C/LBm09MVuwVHIYuVCVRN7+elYwwWk3gD q7DJwFszykyzrM4cfwm7YMq8jT3Xa7eLT1AO9yDyufZgUzkJm/Qa2P4qsF47aFJq+exrCVQ1JJY iAFFflCh3eMKqOklKDC3ydCqr/ZrjXIKcFZa85pOzeShs4tglgtZOgahGNZwKDW1lj4MDWLU5Jf Pbkd41D1rQQRVy6uwNUMcKQDcHcsmC31QB0EaipYkd9E+OZF+yxU2TyGCKMlt7K+Zysz0pLf3Uq u4bYnBORceUIBSZy/81GHz9wqOhnvRz/ObLs7YbK5Z6NEF/WjWOfZadskxOKwGBaIgVeXJPPlEQ PA1phqItxfAOzmsrBZ4uVwka4JNW4xw7RYfTFu6iOnsqUwTSgB4g81YMxMcE2QIyGbuHD16KwOQ C7tb0fLuV0+hTCdwlskcax0Z7vnRHoCNRo/aLo46vbvHhUnCLh8EWxmIOD/tLwR6GVfb/4fQDwh 6yL5ZxOb4gMwEDK7JTJFxCx5It89VX24ajy8C6IeNh+OXBCLqzzLvD+QwNS9R2NqByLCHM/RGo5 qhH6ec+LxtL87T1/BMosTY8s499vmFYMSg== X-Received: by 2002:a05:600c:4f50:b0:4a2:903:a220 with SMTP id 5b1f17b1804b1-4a20903a35dmr38967885e9.1.1792682163112; Thu, 22 Oct 2026 08:16:03 -0700 (PDT) From: Alexey Gladkov To: make-initrd@lists.altlinux.org Date: Thu, 22 Oct 2026 17:15:41 +0200 Message-ID: <20261022151541.648908-1-gladkov.alexey@gmail.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261021101032.278166-1-gladkov.alexey@gmail.com> References: <20261021101032.278166-1-gladkov.alexey@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Subject: [make-initrd] [PATCH v2] runtime: Freeze rootdelay countdown while paused X-BeenThere: make-initrd@lists.altlinux.org X-Mailman-Version: 2.1.12 Precedence: list Reply-To: make-initrd@lists.altlinux.org List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 22 Oct 2026 15:16:07 -0000 Archived-At: List-Archive: The rootdelay service tracks device discovery timeouts against an absolute deadline timestamp. When waiting for blocking operations such as filesystem checks, the deadline was left unchanged while boottime continued to advance. When unpaused, the elapsed waiting time was counted against the rootdelay allowance, often causing premature timeout expiration. Furthermore, the existing pause mechanism relied on a single global directory flag, making it incapable of handling concurrent pauses from parallel scripts (where one script unpausing would prematurely unpause the system for other running tasks) and prone to indefinite stalls if a paused process terminated unexpectedly. Redesign rootdelay pausing around a shared inhibitor lock using flock: - Multiple concurrent tasks acquire a shared lock (flock -s) on a pause lockfile, ensuring the system remains paused until the last holder releases its lock. - If a process crashes or is killed, the kernel automatically closes its file descriptor and drops the shared lock, preventing stale pauses. - Introduce run_rootdelay_paused to wrap commands under a shared lock, and update fsck 040-mountdev to use it. - Keep rootdelay_pause and rootdelay_unpause backward-compatible by holding the shared lock on an open descriptor within the process. - Upon release of the final shared lock, adjust the deadline forward by the total elapsed pause duration. - Track timer resets occurring during a pause to prevent adding redundant delay on unpause. - Unify boottime retrieval across rootdelay helpers and rootdelayd via a current_seconds helper. Signed-off-by: Alexey Gladkov --- .../data/lib/uevent/handlers/040-mountdev | 7 +- .../kickstart/data/etc/rc.d/init.d/kickstart | 5 +- features/runtime/data/bin/initrd-sh-functions | 126 +++++++++++++- features/runtime/data/sbin/rootdelayd | 9 +- testing/tests/ts0047-rootdelay-pause/expect | 18 ++ testing/tests/ts0047-rootdelay-pause/run | 156 ++++++++++++++++++ 6 files changed, 302 insertions(+), 19 deletions(-) create mode 100644 testing/tests/ts0047-rootdelay-pause/expect create mode 100755 testing/tests/ts0047-rootdelay-pause/run diff --git a/features/fsck/data/lib/uevent/handlers/040-mountdev b/features/fsck/data/lib/uevent/handlers/040-mountdev index 78bfd165..84fa6503 100755 --- a/features/fsck/data/lib/uevent/handlers/040-mountdev +++ b/features/fsck/data/lib/uevent/handlers/040-mountdev @@ -26,14 +26,11 @@ handler() local rc=0 - rootdelay_pause - - action "Checking filesystem [$MOUNTDEV]:" fsck -t noloop,noswap -T "$MOUNTDEV" -- ${FSCK_ARGS-} || + run_rootdelay_paused \ + action "Checking filesystem [$MOUNTDEV]:" fsck -t noloop,noswap -T "$MOUNTDEV" -- ${FSCK_ARGS-} || rc=$? if [ "$rc" = 0 ] || [ "$rc" = 1 ]; then - rootdelay_unpause - majmin="$(get_majmin "$MOUNTDEV")" && echo 'done' | statefs_write "/devices/block/$majmin/fsck" ||: diff --git a/features/kickstart/data/etc/rc.d/init.d/kickstart b/features/kickstart/data/etc/rc.d/init.d/kickstart index 3ccf2203..0527c20b 100755 --- a/features/kickstart/data/etc/rc.d/init.d/kickstart +++ b/features/kickstart/data/etc/rc.d/init.d/kickstart @@ -25,18 +25,15 @@ if [ -s "$KSFILE" ]; then . uevent-sh-functions . initrd-sh-functions - rootdelay_pause - method= { read -r method < /etc/initrd/method; } >/dev/null 2>&1 ||: echo kickstart > /etc/initrd/method - kickstart -v "$KSFILE" + run_rootdelay_paused kickstart -v "$KSFILE" echo "$method" > /etc/initrd/method rootdelay_reset_timer - rootdelay_unpause rmdir -- "$uevent_confdb/queue/pause/udev" rmdir -- "$uevent_confdb/queue/pause/md-raid-member" diff --git a/features/runtime/data/bin/initrd-sh-functions b/features/runtime/data/bin/initrd-sh-functions index a69eabee..1266ea29 100644 --- a/features/runtime/data/bin/initrd-sh-functions +++ b/features/runtime/data/bin/initrd-sh-functions @@ -126,22 +126,126 @@ write_atomic() fi } -_rootdelay_pause=/.initrd/rootdelay/pause -_rootdelay_timestamp=/.initrd/rootdelay/deadline +current_seconds() +{ + local __v= + __v="$("${TIMESTAMP:-timestamp}" boottime raw 2>/dev/null)" ||: + __v="${__v%.*}" + eval "$1=\"\$__v\"" +} + +rootdelay_pause_lock="${rootdelay_pause_lock:-/.initrd/rootdelay/pause.lock}" +_rootdelay_pause_time="${_rootdelay_pause_time:-/.initrd/rootdelay/pause.time}" +_rootdelay_reset="${_rootdelay_reset:-/.initrd/rootdelay/reset}" +_rootdelay_timestamp="${_rootdelay_timestamp:-/.initrd/rootdelay/deadline}" + +_rootdelay_finalize_pause() +{ + local chk_fd pause_start reset_time now paused_duration deadline + + [ -e "$rootdelay_pause_lock" ] || return 0 + + exec {chk_fd}<"$rootdelay_pause_lock" + if ! flock -n -x "$chk_fd"; then + exec {chk_fd}<&- + return 1 + fi + + if [ -f "$_rootdelay_pause_time" ]; then + readline pause_start "$_rootdelay_pause_time" + + if [ -f "$_rootdelay_reset" ]; then + readline reset_time "$_rootdelay_reset" + rm -f "$_rootdelay_reset" + + if [ -n "$reset_time" ] && [ -n "$pause_start" ] && [ "$reset_time" -gt "$pause_start" ]; then + pause_start="$reset_time" + fi + fi + + if [ -n "$pause_start" ] && [ -f "$_rootdelay_timestamp" ]; then + current_seconds now + paused_duration=$(( $now - $pause_start )) + + if [ "$paused_duration" -gt 0 ]; then + readline deadline "$_rootdelay_timestamp" + + if [ -n "$deadline" ] && [ "$deadline" -gt 0 ]; then + rootdelay_set_deadline "$(( $deadline + $paused_duration ))" + fi + fi + fi + + rm -f "$_rootdelay_pause_time" + fi + + exec {chk_fd}<&- +} + +run_rootdelay_paused() +{ + local fd rc=0 now + + mkdir -p -- "${rootdelay_pause_lock%/*}" + touch "$rootdelay_pause_lock" + + exec {fd}<"$rootdelay_pause_lock" + flock -s "$fd" + + if [ ! -f "$_rootdelay_pause_time" ]; then + current_seconds now + write_atomic "$_rootdelay_pause_time" printf '%s\n' "$now" + fi + + "$@" {fd}<&- || rc=$? + exec {fd}<&- + + _rootdelay_finalize_pause + + return $rc +} rootdelay_pause() { - mkdir -p "$_rootdelay_pause" + local now + + [ -z "${rootdelay_pause_fd-}" ] || return 0 + + mkdir -p -- "${rootdelay_pause_lock%/*}" + touch "$rootdelay_pause_lock" + + exec {rootdelay_pause_fd}<"$rootdelay_pause_lock" + flock -s "$rootdelay_pause_fd" + + if [ ! -f "$_rootdelay_pause_time" ]; then + current_seconds now + write_atomic "$_rootdelay_pause_time" printf '%s\n' "$now" + fi } rootdelay_unpause() { - rm -rf "$_rootdelay_pause" + [ -n "${rootdelay_pause_fd-}" ] || return 0 + + exec {rootdelay_pause_fd}<&- + rootdelay_pause_fd= + + _rootdelay_finalize_pause } rootdelay_paused() { - [ -d "$_rootdelay_pause" ] + local fd rc=0 + + [ -z "${rootdelay_pause_fd-}" ] || return 0 + [ -e "$rootdelay_pause_lock" ] || return 1 + + exec {fd}<"$rootdelay_pause_lock" + + ! flock -n -x "$fd" || rc=1 + exec {fd}<&- + + return $rc } rootdelay_set_deadline() @@ -153,11 +257,17 @@ rootdelay_set_deadline() rootdelay_reset_timer() { local now="${1-}" + if [ -z "$now" ]; then - now="$(timestamp boottime raw)" - now="${now%.*}" + current_seconds now + fi + + rootdelay_set_deadline "$(( $now + ${ROOTDELAY:-0} ))" + + if rootdelay_paused; then + mkdir -p -- "${_rootdelay_reset%/*}" + write_atomic "$_rootdelay_reset" printf '%s\n' "$now" fi - rootdelay_set_deadline "$(( $now + $ROOTDELAY ))" } initrd_features=/.initrd/features diff --git a/features/runtime/data/sbin/rootdelayd b/features/runtime/data/sbin/rootdelayd index 5fa21f07..cdd8fcf8 100755 --- a/features/runtime/data/sbin/rootdelayd +++ b/features/runtime/data/sbin/rootdelayd @@ -47,6 +47,11 @@ while :; do continue fi + if [ -f "$_rootdelay_pause_time" ]; then + _rootdelay_finalize_pause || sleep 0.1 + continue + fi + if [ -z "${__initrd_initenv-}" ] && [ -f /.initrd/initenv ]; then . /.initrd/initenv @@ -71,9 +76,9 @@ while :; do continue fi + current_seconds now + first_iter= - now="$(timestamp boottime raw)" - now="${now%.*}" consmsg="/.initrd/rootdelay/message" if [ ! -f "$_rootdelay_timestamp" ]; then diff --git a/testing/tests/ts0047-rootdelay-pause/expect b/testing/tests/ts0047-rootdelay-pause/expect new file mode 100644 index 00000000..7788a3a8 --- /dev/null +++ b/testing/tests/ts0047-rootdelay-pause/expect @@ -0,0 +1,18 @@ +initial unpause ok +deadline after init: 160 +paused at 120 +unpaused at 150 +deadline after unpause: 190 +remaining delay: 40 +pause before deadline ok +deadline reset: 360 +deadline after reset during pause: 410 +remaining delay: 60 +deadline 0 preserved: 0 +run_rootdelay_paused: inside paused ok +run_rootdelay_paused: outside unpaused ok +B unpause: still paused because A holds lock ok +A unpause: unpaused ok +concurrent deadline: 800 +concurrent remaining delay: 50 +rc=0 diff --git a/testing/tests/ts0047-rootdelay-pause/run b/testing/tests/ts0047-rootdelay-pause/run new file mode 100755 index 00000000..6d32632a --- /dev/null +++ b/testing/tests/ts0047-rootdelay-pause/run @@ -0,0 +1,156 @@ +#!/bin/bash -efu +# SPDX-License-Identifier: GPL-3.0-or-later + +cwd="${0%/*}" +root="$cwd/root" + +rm -rf -- "$root" +mkdir -p -- "$root/bin" "$root/rootdelay" + +mock_time="$root/time" +echo "100" > "$mock_time" + +cat > "$root/bin/timestamp" < deadline=160 +rootdelay_reset_timer +readline deadline "$_rootdelay_timestamp" +echo "deadline after init: $deadline" + +# 3. Pause at t=120 (remaining: 40) +echo "120" > "$mock_time" +rootdelay_pause +if rootdelay_paused; then + echo "paused at 120" +fi + +# 4. Redundant pause at t=130 should not overwrite pause start +echo "130" > "$mock_time" +rootdelay_pause + +# 5. Unpause at t=150 (30 seconds paused). Deadline should be 160 + 30 = 190. +echo "150" > "$mock_time" +rootdelay_unpause +if ! rootdelay_paused; then + echo "unpaused at 150" +fi +readline deadline "$_rootdelay_timestamp" +echo "deadline after unpause: $deadline" +echo "remaining delay: $(( deadline - 150 ))" + +# 6. Pause before deadline exists +rm -f -- "$_rootdelay_timestamp" +echo "200" > "$mock_time" +rootdelay_pause +echo "220" > "$mock_time" +rootdelay_unpause +if [ ! -f "$_rootdelay_timestamp" ]; then + echo "pause before deadline ok" +fi + +# 7. Timer reset during pause +echo "300" > "$mock_time" +rootdelay_reset_timer +readline deadline "$_rootdelay_timestamp" +echo "deadline reset: $deadline" +echo "320" > "$mock_time" +rootdelay_pause +echo "340" > "$mock_time" +rootdelay_reset_timer +echo "350" > "$mock_time" +rootdelay_unpause +readline deadline "$_rootdelay_timestamp" +echo "deadline after reset during pause: $deadline" +echo "remaining delay: $(( deadline - 350 ))" + +# 8. Deadline 0 is preserved +rootdelay_set_deadline 0 +echo "500" > "$mock_time" +rootdelay_pause +echo "520" > "$mock_time" +rootdelay_unpause +readline deadline "$_rootdelay_timestamp" +echo "deadline 0 preserved: $deadline" + +# 9. run_rootdelay_paused wrapper +echo "600" > "$mock_time" +rootdelay_reset_timer +run_rootdelay_paused bash -c ' + . features/runtime/data/bin/initrd-sh-functions + rootdelay_paused && echo "run_rootdelay_paused: inside paused ok" +' +if ! rootdelay_paused; then + echo "run_rootdelay_paused: outside unpaused ok" +fi + +# 10. Concurrent / overlapping pauses in background subshells +echo "700" > "$mock_time" +rootdelay_reset_timer + +fifo1="$root/fifo1" +fifo2="$root/fifo2" +mkfifo "$fifo1" "$fifo2" + +# Subshell 1 (Process A) +bash -c ' + . features/runtime/data/bin/initrd-sh-functions + rootdelay_pause + echo "A_paused" > "'"$fifo1"'" + read -r _ < "'"$fifo2"'" + rootdelay_unpause +' & +pid_a=$! + +# Wait for A to pause at 710 +echo "710" > "$mock_time" +read -r _ < "$fifo1" + +# Subshell 2 (Process B) pauses at 720 +echo "720" > "$mock_time" +bash -c ' + . features/runtime/data/bin/initrd-sh-functions + rootdelay_pause + # B unpauses at 730 + echo "730" > "'"$mock_time"'" + rootdelay_unpause + if rootdelay_paused; then + echo "B unpause: still paused because A holds lock ok" + fi +' + +# Now unblock A at 750 +echo "750" > "$mock_time" +echo "go" > "$fifo2" +wait "$pid_a" + +if ! rootdelay_paused; then + echo "A unpause: unpaused ok" +fi +readline deadline "$_rootdelay_timestamp" +# Deadline was 700 + 60 = 760. Total pause was from 710 to 750 = 40s. New deadline: 760 + 40 = 800. +echo "concurrent deadline: $deadline" +echo "concurrent remaining delay: $(( deadline - 750 ))" + +rm -rf -- "$root" -- 2.56.0