From 3a18ca4a303a9e73182c8e0046fed1c33ac8ee24 Mon Sep 17 00:00:00 2001 From: zhangxiaohui Date: Wed, 21 Oct 2026 19:03:46 +0800 Subject: [PATCH] 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 --- src/service.c | 25 +++++++++--- test/Makefile.am | 2 + test/remain-task-control.sh | 76 +++++++++++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+), 6 deletions(-) create mode 100755 test/remain-task-control.sh diff --git a/src/service.c b/src/service.c index 59c47c30..ecd5f6b1 100644 --- a/src/service.c +++ b/src/service.c @@ -1599,6 +1599,15 @@ int service_restart_now(svc_t *svc) if (!svc) 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)) return service_start_now(svc); @@ -3378,8 +3387,8 @@ int service_step(svc_t *svc) svc_set_state(svc, SVC_CLEANUP_STATE); service_cleanup_script(svc); } else { - /* Unblock remain tasks after post script so they can restart */ - if (svc_is_remain(svc)) + /* Restart remain tasks after runlevel/reload/restart teardown. */ + if (svc_is_remain(svc) && svc_is_restart(svc)) svc_unblock(svc); svc_set_state(svc, SVC_HALTED_STATE); } @@ -3398,13 +3407,17 @@ int service_step(svc_t *svc) break; case SVC_DONE_STATE: - /* Remain tasks: always run post script when stopped, even if config changed */ - if (svc_is_runtask(svc) && svc_is_remain(svc) && svc_is_stopped(svc)) { + /* Remain tasks run post script before stopping or restarting. */ + if (svc_is_runtask(svc) && svc_is_remain(svc) && + (svc_is_stopped(svc) || svc_is_restart(svc))) { if (svc_has_post(svc)) { svc_set_state(svc, SVC_TEARDOWN_STATE); service_post_script(svc); - } else + } else { + if (svc_is_restart(svc)) + svc_unblock(svc); svc_set_state(svc, SVC_HALTED_STATE); + } } else if (svc_is_changed(svc)) svc_set_state(svc, SVC_HALTED_STATE); 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 */ svc->once = 0; - svc_stop(svc); + svc_restarting(svc); service_step(svc); continue; } diff --git a/test/Makefile.am b/test/Makefile.am index b943e1ef..b60971aa 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -60,6 +60,7 @@ EXTRA_DIST += pre-fail.sh EXTRA_DIST += process-depends.sh EXTRA_DIST += rclocal.sh EXTRA_DIST += ready-serv.sh +EXTRA_DIST += remain-task-control.sh EXTRA_DIST += restart-self.sh EXTRA_DIST += runlevel.sh EXTRA_DIST += run-restart-forever.sh @@ -129,6 +130,7 @@ TESTS += pre-fail.sh TESTS += process-depends.sh TESTS += rclocal.sh TESTS += ready-serv.sh +TESTS += remain-task-control.sh TESTS += restart-self.sh TESTS += runlevel.sh TESTS += sysvparts.sh diff --git a/test/remain-task-control.sh b/test/remain-task-control.sh new file mode 100755 index 00000000..a5c26eec --- /dev/null +++ b/test/remain-task-control.sh @@ -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" <