From 81229c59e62fff7dad5c84631ff92fd5758e0c81 Mon Sep 17 00:00:00 2001 From: Davin McCall Date: Sat, 9 Jan 2016 18:20:48 +0000 Subject: [PATCH] Introduce "explicitly started" state flag for services, and automatically stop services which were not explicitly started when they have no dependents running. This means that if you start a service, and then stop that service, the system should return to its original state with no additional services running. --- src/service.cc | 119 ++++++++++++++++++++++++++++++++----------------- src/service.h | 15 ++++++- 2 files changed, 91 insertions(+), 43 deletions(-) diff --git a/src/service.cc b/src/service.cc index c4667bb..105c08b 100644 --- a/src/service.cc +++ b/src/service.cc @@ -45,7 +45,7 @@ void ServiceSet::startService(const char *name) using namespace std; ServiceRecord *record = loadServiceRecord(name); - record->start(); + record->start(false); } void ServiceSet::stopService(const std::string & name) noexcept @@ -68,26 +68,32 @@ void ServiceRecord::stopped() noexcept service_state = ServiceState::STOPPED; force_stop = false; - // Stop any dependencies whose desired state is STOPPED: - for (sr_iter i = depends_on.begin(); i != depends_on.end(); i++) { - (*i)->dependentStopped(); - } - - service_set->service_inactive(this); notifyListeners(ServiceEvent::STOPPED); if (desired_state == ServiceState::STARTED) { // Desired state is "started". - start(); + do_start(); } - else if (socket_fd != -1) { - close(socket_fd); - socket_fd = -1; + else { + if (socket_fd != -1) { + close(socket_fd); + socket_fd = -1; + } + + service_set->service_inactive(this); + + // Stop any dependencies whose desired state is STOPPED: + for (auto i = depends_on.begin(); i != depends_on.end(); i++) { + (*i)->dependentStopped(); + } + for (auto i = soft_deps.begin(); i != soft_deps.end(); i++) { + i->getTo()->dependentStopped(); + } } } void ServiceRecord::process_child_callback(struct ev_loop *loop, ev_child *w, int revents) noexcept -{ +{ ServiceRecord *sr = (ServiceRecord *) w->data; sr->pid = -1; @@ -117,24 +123,21 @@ void ServiceRecord::handle_exit_status() noexcept if (doing_recovery) { // (BGPROCESS only) doing_recovery = false; - bool do_stop = false; + bool need_stop = false; if (exit_status != 0) { - do_stop = true; + need_stop = true; } else { // We need to re-read the PID, since it has now changed. if (service_type == ServiceType::BGPROCESS && pid_file.length() != 0) { if (! read_pid_file()) { - do_stop = true; + need_stop = true; } } } - if (do_stop) { - stop(); - if (auto_restart && service_set->get_auto_restart()) { - start(); - } + if (need_stop) { + do_stop(); } return; @@ -166,10 +169,6 @@ void ServiceRecord::handle_exit_status() noexcept else { forceStop(); } - - if (auto_restart && service_set->get_auto_restart()) { - start(); - } } else { // SCRIPTED if (service_state == ServiceState::STOPPING) { @@ -235,7 +234,7 @@ void ServiceRecord::process_child_status(struct ev_loop *loop, ev_io * stat_io, } } -void ServiceRecord::start() noexcept +void ServiceRecord::start(bool transitive) noexcept { if ((service_state == ServiceState::STARTING || service_state == ServiceState::STARTED) && desired_state == ServiceState::STOPPED) { @@ -244,10 +243,22 @@ void ServiceRecord::start() noexcept notifyListeners(ServiceEvent::STOPCANCELLED); } + if (transitive) { + started_deps++; + } + else { + start_explicit = true; + } + if (desired_state == ServiceState::STARTED && service_state != ServiceState::STOPPED) return; desired_state = ServiceState::STARTED; - + service_set->service_active(this); + do_start(); +} + +void ServiceRecord::do_start() noexcept +{ if (pinned_stopped) return; if (service_state != ServiceState::STOPPED) { @@ -259,10 +270,10 @@ void ServiceRecord::start() noexcept // We're STOPPING, and that can be interrupted. Our dependencies might be STOPPING, // but if so they are waiting (for us), so they too can be instantly returned to // STARTING state. + notifyListeners(ServiceEvent::STOPCANCELLED); } service_state = ServiceState::STARTING; - service_set->service_active(this); waiting_for_deps = true; @@ -294,7 +305,7 @@ bool ServiceRecord::startCheckDependencies(bool start_deps) noexcept if ((*i)->service_state != ServiceState::STARTED) { if (start_deps) { all_deps_started = false; - (*i)->start(); + (*i)->start(true); } else { return false; @@ -306,7 +317,7 @@ bool ServiceRecord::startCheckDependencies(bool start_deps) noexcept ServiceRecord * to = i->getTo(); if (start_deps) { if (to->service_state != ServiceState::STARTED) { - to->start(); + to->start(true); i->waiting_on = true; all_deps_started = false; } @@ -489,11 +500,7 @@ void ServiceRecord::started() noexcept if (force_stop || desired_state == ServiceState::STOPPED) { // We must now stop. - bool do_restart = (desired_state != ServiceState::STOPPED); - stop(); - if (do_restart) { - start(); - } + do_stop(); return; } @@ -519,6 +526,14 @@ void ServiceRecord::failed_to_start(bool depfailed) noexcept service_set->service_inactive(this); notifyListeners(ServiceEvent::FAILEDSTART); + // Stop any dependencies whose desired state is STOPPED: + for (auto i = depends_on.begin(); i != depends_on.end(); i++) { + (*i)->dependentStopped(); + } + for (auto i = soft_deps.begin(); i != soft_deps.end(); i++) { + i->getTo()->dependentStopped(); + } + // Cancel start of dependents: for (sr_iter i = dependents.begin(); i != dependents.end(); i++) { if ((*i)->service_state == ServiceState::STARTING) { @@ -659,7 +674,10 @@ void ServiceRecord::forceStop() noexcept for (sr_iter i = dependents.begin(); i != dependents.end(); i++) { (*i)->forceStop(); } - stop(); + + if (service_state == ServiceState::STARTED) { + do_stop(); + } // We don't want to force stop soft dependencies, however. } @@ -667,16 +685,29 @@ void ServiceRecord::forceStop() noexcept void ServiceRecord::dependentStopped() noexcept { + started_deps--; if (service_state == ServiceState::STOPPING) { // Check the other dependents before we stop. if (stopCheckDependents()) { allDepsStopped(); } } + else if (started_deps == 0 && !start_explicit) { + desired_state = ServiceState::STOPPED; + service_state = ServiceState::STOPPING; + allDepsStopped(); + } } void ServiceRecord::stop() noexcept { + if (desired_state == ServiceState::STOPPED && service_state != ServiceState::STARTED) return; + + start_explicit = false; + if (started_deps != 0) { + return; + } + if ((service_state == ServiceState::STOPPING || service_state == ServiceState::STOPPED) && desired_state == ServiceState::STARTED) { // The service *was* stopped/stopping, but it was going to restart. @@ -684,10 +715,13 @@ void ServiceRecord::stop() noexcept notifyListeners(ServiceEvent::STARTCANCELLED); } - if (desired_state == ServiceState::STOPPED && service_state != ServiceState::STARTED) return; - desired_state = ServiceState::STOPPED; + do_stop(); +} + +void ServiceRecord::do_stop() noexcept +{ if (pinned_started) return; if (service_state != ServiceState::STARTED) { @@ -700,11 +734,14 @@ void ServiceRecord::stop() noexcept stopDependents(); return; } + + // We must have had desired_state == STARTED. + notifyListeners(ServiceEvent::STARTCANCELLED); // Reaching this point, we have can_interrupt_start() == true. So, // we can stop. Dependents might be starting, but they must be // waiting on us, so they should also be immediately stoppable. - // Fall through to below. + // Fall through to below,. } else { // If we're starting we need to wait for that to complete. @@ -717,7 +754,7 @@ void ServiceRecord::stop() noexcept waiting_for_deps = true; // If we get here, we are in STARTED state; stop all dependents. - if (stopDependents()) { + if (stopCheckDependents()) { allDepsStopped(); } } @@ -785,7 +822,7 @@ void ServiceRecord::allDepsStopped() void ServiceRecord::pinStart() noexcept { - start(); + start(false); pinned_started = true; } @@ -806,7 +843,7 @@ void ServiceRecord::unpin() noexcept if (pinned_stopped) { pinned_stopped = false; if (desired_state == ServiceState::STARTED) { - start(); + do_start(); } } } diff --git a/src/service.h b/src/service.h index 3d7bc45..3e5211b 100644 --- a/src/service.h +++ b/src/service.h @@ -167,6 +167,7 @@ class ServiceRecord OnstartFlags onstart_flags; string logfile; // log file name, empty string specifies /dev/null + bool auto_restart : 1; // whether to restart this (process) if it dies unexpectedly bool smooth_recovery : 1; // whether the service process can restart without bringing down service @@ -176,6 +177,8 @@ class ServiceRecord bool waiting_for_execstat : 1; // if we are waiting for exec status after fork() bool doing_recovery : 1; // if we are currently recovering a BGPROCESS (restarting process, while // holding STARTED service state) + bool start_explicit : 1; // whether we are are explictly required to be started + int started_deps = 0; // number of dependents that require this service to be started typedef std::list sr_list; typedef sr_list::iterator sr_iter; @@ -306,12 +309,18 @@ class ServiceRecord // Release console (console must be currently held by this service) void releaseConsole() noexcept; + bool get_start_flag(bool transitive) + { + return transitive ? started_deps != 0 : start_explicit; + } + public: ServiceRecord(ServiceSet *set, string name) : service_state(ServiceState::STOPPED), desired_state(ServiceState::STOPPED), auto_restart(false), pinned_stopped(false), pinned_started(false), waiting_for_deps(false), - waiting_for_execstat(false), doing_recovery(false), force_stop(false) + waiting_for_execstat(false), doing_recovery(false), + start_explicit(false), force_stop(false) { service_set = set; service_name = name; @@ -409,8 +418,10 @@ class ServiceRecord const char *getServiceName() const noexcept { return service_name.c_str(); } ServiceState getState() const noexcept { return service_state; } - void start() noexcept; // start the service + void start(bool transitive = false) noexcept; // start the service void stop() noexcept; // stop the service + void do_start() noexcept; + void do_stop() noexcept; void pinStart() noexcept; // start the service and pin it void pinStop() noexcept; // stop the service and pin it -- 2.25.1