From b7e03e82b89a30b09fea88eaf2a5638df1017cf6 Mon Sep 17 00:00:00 2001 From: Bertrand SIMONNET Date: Fri, 18 Dec 2015 11:39:59 -0800 Subject: [PATCH] init: Allows shutting down cleanly. When ro.build.shutdown_timeout is set, init will send a SIGTERM signal to all services on reboot. The normal shutdown process will continue once all services have exited or after the shutdown timeout (ro.build.shutdown_timeout). If ro.build.shutdown_timeout is not set, we assume a 0s timeout. Bug: 26216447 Test: manual: Ask to reboot. All services exit cleanly. Change-Id: If921f6e8d87211e500ac9fa86f3e1eabe02d18cf --- init/builtins.cpp | 40 ++++++++++++++++++++++++++ init/service.cpp | 63 +++++++++++++++++++++++++++++++++++++++-- init/service.h | 8 +++++- init/signal_handler.cpp | 54 ++--------------------------------- 4 files changed, 110 insertions(+), 55 deletions(-) diff --git a/init/builtins.cpp b/init/builtins.cpp index 10f9d8171..d2291bb50 100644 --- a/init/builtins.cpp +++ b/init/builtins.cpp @@ -39,6 +39,7 @@ #include #include +#include #include #include #include @@ -53,6 +54,7 @@ #include "log.h" #include "property_service.h" #include "service.h" +#include "signal_handler.h" #include "util.h" #define chmod DO_NOT_USE_CHMOD_USE_FCHMODAT_SYMLINK_NOFOLLOW @@ -62,6 +64,8 @@ // System call provided by bionic but not in any header file. extern "C" int init_module(void *, unsigned long, const char *); +static const int kTerminateServiceDelayMicroSeconds = 50000; + static int insmod(const char *filename, const char *options) { std::string module; if (!read_file(filename, &module)) { @@ -608,6 +612,42 @@ static int do_powerctl(const std::vector& args) { return -EINVAL; } + std::string timeout = property_get("ro.build.shutdown_timeout"); + unsigned int delay = 0; + + if (android::base::ParseUint(timeout.c_str(), &delay) && delay > 0) { + Timer t; + // Ask all services to terminate. + ServiceManager::GetInstance().ForEachService( + [] (Service* s) { s->Terminate(); }); + + while (t.duration() < delay) { + ServiceManager::GetInstance().ReapAnyOutstandingChildren(); + + int service_count = 0; + ServiceManager::GetInstance().ForEachService( + [&service_count] (Service* s) { + // Count the number of services running. + // Exclude the console as it will ignore the SIGTERM signal + // and not exit. + // Note: SVC_CONSOLE actually means "requires console" but + // it is only used by the shell. + if (s->pid() != 0 && (s->flags() & SVC_CONSOLE) == 0) { + service_count++; + } + }); + + if (service_count == 0) { + // All terminable services terminated. We can exit early. + break; + } + + // Wait a bit before recounting the number or running services. + usleep(kTerminateServiceDelayMicroSeconds); + } + NOTICE("Terminating running services took %.02f seconds", t.duration()); + } + return android_reboot_with_callback(cmd, 0, reboot_target, callback_on_ro_remount); } diff --git a/init/service.cpp b/init/service.cpp index 40a4bc7db..0ddc48485 100644 --- a/init/service.cpp +++ b/init/service.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -531,6 +532,17 @@ void Service::Stop() { StopOrReset(SVC_DISABLED); } +void Service::Terminate() { + flags_ &= ~(SVC_RESTARTING | SVC_DISABLED_START); + flags_ |= SVC_DISABLED; + if (pid_) { + NOTICE("Sending SIGTERM to service '%s' (pid %d)...\n", name_.c_str(), + pid_); + kill(-pid_, SIGTERM); + NotifyStateChange("stopping"); + } +} + void Service::Restart() { if (flags_ & SVC_RUNNING) { /* Stop, wait, then start the service. */ @@ -724,9 +736,9 @@ Service* ServiceManager::FindServiceByKeychord(int keychord_id) const { return nullptr; } -void ServiceManager::ForEachService(void (*func)(Service* svc)) const { +void ServiceManager::ForEachService(std::function callback) const { for (const auto& s : services_) { - func(s.get()); + callback(s.get()); } } @@ -767,6 +779,53 @@ void ServiceManager::DumpState() const { INFO("\n"); } +bool ServiceManager::ReapOneProcess() { + int status; + pid_t pid = TEMP_FAILURE_RETRY(waitpid(-1, &status, WNOHANG)); + if (pid == 0) { + return false; + } else if (pid == -1) { + ERROR("waitpid failed: %s\n", strerror(errno)); + return false; + } + + Service* svc = FindServiceByPid(pid); + + std::string name; + if (svc) { + name = android::base::StringPrintf("Service '%s' (pid %d)", + svc->name().c_str(), pid); + } else { + name = android::base::StringPrintf("Untracked pid %d", pid); + } + + if (WIFEXITED(status)) { + NOTICE("%s exited with status %d\n", name.c_str(), WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + NOTICE("%s killed by signal %d\n", name.c_str(), WTERMSIG(status)); + } else if (WIFSTOPPED(status)) { + NOTICE("%s stopped by signal %d\n", name.c_str(), WSTOPSIG(status)); + } else { + NOTICE("%s state changed", name.c_str()); + } + + if (!svc) { + return true; + } + + if (svc->Reap()) { + waiting_for_exec = false; + RemoveService(*svc); + } + + return true; +} + +void ServiceManager::ReapAnyOutstandingChildren() { + while (ReapOneProcess()) { + } +} + bool ServiceParser::ParseSection(const std::vector& args, std::string* err) { if (args.size() < 3) { diff --git a/init/service.h b/init/service.h index 10eb736cb..35abde9b7 100644 --- a/init/service.h +++ b/init/service.h @@ -82,6 +82,7 @@ public: bool Enable(); void Reset(); void Stop(); + void Terminate(); void Restart(); void RestartIfNeeded(time_t& process_needs_restart); bool Reap(); @@ -167,17 +168,22 @@ public: Service* FindServiceByName(const std::string& name) const; Service* FindServiceByPid(pid_t pid) const; Service* FindServiceByKeychord(int keychord_id) const; - void ForEachService(void (*func)(Service* svc)) const; + void ForEachService(std::function callback) const; void ForEachServiceInClass(const std::string& classname, void (*func)(Service* svc)) const; void ForEachServiceWithFlags(unsigned matchflags, void (*func)(Service* svc)) const; + void ReapAnyOutstandingChildren(); void RemoveService(const Service& svc); void DumpState() const; private: ServiceManager(); + // Cleans up a child process that exited. + // Returns true iff a children was cleaned up. + bool ReapOneProcess(); + static int exec_count_; // Every service needs a unique name. std::vector> services_; }; diff --git a/init/signal_handler.cpp b/init/signal_handler.cpp index e7d42cb9b..ea483d4e2 100644 --- a/init/signal_handler.cpp +++ b/init/signal_handler.cpp @@ -37,62 +37,12 @@ static int signal_write_fd = -1; static int signal_read_fd = -1; -static std::string DescribeStatus(int status) { - if (WIFEXITED(status)) { - return android::base::StringPrintf("exited with status %d", WEXITSTATUS(status)); - } else if (WIFSIGNALED(status)) { - return android::base::StringPrintf("killed by signal %d", WTERMSIG(status)); - } else if (WIFSTOPPED(status)) { - return android::base::StringPrintf("stopped by signal %d", WSTOPSIG(status)); - } else { - return "state changed"; - } -} - -static bool wait_for_one_process() { - int status; - pid_t pid = TEMP_FAILURE_RETRY(waitpid(-1, &status, WNOHANG)); - if (pid == 0) { - return false; - } else if (pid == -1) { - ERROR("waitpid failed: %s\n", strerror(errno)); - return false; - } - - Service* svc = ServiceManager::GetInstance().FindServiceByPid(pid); - - std::string name; - if (svc) { - name = android::base::StringPrintf("Service '%s' (pid %d)", svc->name().c_str(), pid); - } else { - name = android::base::StringPrintf("Untracked pid %d", pid); - } - - NOTICE("%s %s\n", name.c_str(), DescribeStatus(status).c_str()); - - if (!svc) { - return true; - } - - if (svc->Reap()) { - waiting_for_exec = false; - ServiceManager::GetInstance().RemoveService(*svc); - } - - return true; -} - -static void reap_any_outstanding_children() { - while (wait_for_one_process()) { - } -} - static void handle_signal() { // Clear outstanding requests. char buf[32]; read(signal_read_fd, buf, sizeof(buf)); - reap_any_outstanding_children(); + ServiceManager::GetInstance().ReapAnyOutstandingChildren(); } static void SIGCHLD_handler(int) { @@ -119,7 +69,7 @@ void signal_handler_init() { act.sa_flags = SA_NOCLDSTOP; sigaction(SIGCHLD, &act, 0); - reap_any_outstanding_children(); + ServiceManager::GetInstance().ReapAnyOutstandingChildren(); register_epoll_handler(signal_read_fd, handle_signal); }