service: preserve explicit stop for remain tasks

A completed remain task unconditionally cleared its user stop block
after running its post script.  Since it was still valid in the current
runlevel, it immediately started again, making initctl stop ineffective.

Keep explicit stops blocked, distinguish automatic and requested
restarts, and route initctl restart through teardown before rerunning the
task.  Cover stop, start, restart, reload, and runlevel transitions with
a regression test.

Signed-off-by: zhangxiaohui <xiaohui.zhang@wbstar.com>
This commit is contained in:
zhangxiaohui committed 2026-10-21 19:03:46 +08:00
1 parent 530d6703f1
commit 3a18ca4a30
3 files changed
+97 -6

No files matched your search

+19 -6
View File
@@ -1599,6 +1599,15 @@ int service_restart_now(svc_t *svc)
if (!svc) if (!svc)
return 1; return 1;
if (svc_is_runtask(svc) && svc_is_remain(svc) &&
svc->state == SVC_DONE_STATE) {
service_timeout_cancel(svc);
svc_restarting(svc);
service_step(svc);
return 0;
}
if (!svc_is_running(svc)) if (!svc_is_running(svc))
return service_start_now(svc); return service_start_now(svc);
@@ -3378,8 +3387,8 @@ restart:
svc_set_state(svc, SVC_CLEANUP_STATE); svc_set_state(svc, SVC_CLEANUP_STATE);
service_cleanup_script(svc); service_cleanup_script(svc);
} else { } else {
/* Unblock remain tasks after post script so they can restart */ /* Restart remain tasks after runlevel/reload/restart teardown. */
if (svc_is_remain(svc)) if (svc_is_remain(svc) && svc_is_restart(svc))
svc_unblock(svc); svc_unblock(svc);
svc_set_state(svc, SVC_HALTED_STATE); svc_set_state(svc, SVC_HALTED_STATE);
} }
@@ -3398,13 +3407,17 @@ restart:
break; break;
case SVC_DONE_STATE: case SVC_DONE_STATE:
/* Remain tasks: always run post script when stopped, even if config changed */ /* Remain tasks run post script before stopping or restarting. */
if (svc_is_runtask(svc) && svc_is_remain(svc) && svc_is_stopped(svc)) { if (svc_is_runtask(svc) && svc_is_remain(svc) &&
(svc_is_stopped(svc) || svc_is_restart(svc))) {
if (svc_has_post(svc)) { if (svc_has_post(svc)) {
svc_set_state(svc, SVC_TEARDOWN_STATE); svc_set_state(svc, SVC_TEARDOWN_STATE);
service_post_script(svc); service_post_script(svc);
} else } else {
if (svc_is_restart(svc))
svc_unblock(svc);
svc_set_state(svc, SVC_HALTED_STATE); svc_set_state(svc, SVC_HALTED_STATE);
}
} else if (svc_is_changed(svc)) } else if (svc_is_changed(svc))
svc_set_state(svc, SVC_HALTED_STATE); svc_set_state(svc, SVC_HALTED_STATE);
else if (svc_is_runtask(svc) && svc_is_manual(svc) && enabled) else if (svc_is_runtask(svc) && svc_is_manual(svc) && enabled)
@@ -3727,7 +3740,7 @@ void service_runtask_clean(void)
/* Config changed or leaving runlevel: stop, run post, restart */ /* Config changed or leaving runlevel: stop, run post, restart */
svc->once = 0; svc->once = 0;
svc_stop(svc); svc_restarting(svc);
service_step(svc); service_step(svc);
continue; continue;
} }
+2
View File
@@ -60,6 +60,7 @@ EXTRA_DIST += pre-fail.sh
EXTRA_DIST += process-depends.sh EXTRA_DIST += process-depends.sh
EXTRA_DIST += rclocal.sh EXTRA_DIST += rclocal.sh
EXTRA_DIST += ready-serv.sh EXTRA_DIST += ready-serv.sh
EXTRA_DIST += remain-task-control.sh
EXTRA_DIST += restart-self.sh EXTRA_DIST += restart-self.sh
EXTRA_DIST += runlevel.sh EXTRA_DIST += runlevel.sh
EXTRA_DIST += run-restart-forever.sh EXTRA_DIST += run-restart-forever.sh
@@ -129,6 +130,7 @@ TESTS += pre-fail.sh
TESTS += process-depends.sh TESTS += process-depends.sh
TESTS += rclocal.sh TESTS += rclocal.sh
TESTS += ready-serv.sh TESTS += ready-serv.sh
TESTS += remain-task-control.sh
TESTS += restart-self.sh TESTS += restart-self.sh
TESTS += runlevel.sh TESTS += runlevel.sh
TESTS += sysvparts.sh TESTS += sysvparts.sh
+76
View File
@@ -0,0 +1,76 @@
#!/bin/sh
set -eu
TEST_DIR=$(dirname "$0")
TEST_CONF=/etc/finit.d/remain-task.conf
STATE=/run/remain-task.state
test_teardown()
{
run "rm -f $TEST_CONF $STATE"
}
assert_runs()
{
assert "Remain task has run $1 time(s)" \
"$(texec initctl -j status remain-task | jq -M .restarts)" -eq "$1"
}
# shellcheck source=/dev/null
. "$TEST_DIR/lib/setup.sh"
say 'Add remain task'
run "cat > $TEST_CONF" <<EOF
task remain-task {
description = "Remain task control"
runlevel = "2345"
remain-after-exit = true
command = "/bin/touch $STATE"
exec-stop-post = "/bin/rm -f $STATE"
}
EOF
run "initctl reload"
retry 'assert_status remain-task done'
assert_file_exists "$STATE"
assert_runs 1
say 'Stop remain task'
run "initctl stop remain-task"
retry 'assert_status remain-task stopped'
assert "Post script removed state" \
"$(texec sh -c "[ ! -e '$STATE' ] && echo yes")" = yes
assert_runs 1
say 'Start remain task'
run "initctl start remain-task"
retry 'assert_status remain-task done'
assert_file_exists "$STATE"
assert_runs 2
say 'Restart remain task'
run "initctl restart remain-task"
retry 'assert_runs 3'
retry 'assert_status remain-task done'
assert_file_exists "$STATE"
say 'Reload changed remain task'
run "sed -i 's/Remain task control/Updated remain task control/' $TEST_CONF"
run "initctl reload"
retry 'assert_runs 4'
retry 'assert_status remain-task done'
assert_file_exists "$STATE"
say 'Leave remain task runlevel'
run "initctl runlevel 1"
retry 'assert_status remain-task halted'
assert "Post script removed state" \
"$(texec sh -c "[ ! -e '$STATE' ] && echo yes")" = yes
assert_runs 4
say 'Re-enter remain task runlevel'
run "initctl runlevel 2"
retry 'assert_runs 5'
retry 'assert_status remain-task done'
assert_file_exists "$STATE"