From debc88843f41092aa592ec9d1d3bb3a87b222e81 Mon Sep 17 00:00:00 2001 From: Davin McCall Date: Mon, 7 Sep 2015 10:44:15 +0100 Subject: [PATCH] Implement "soft" dependencies. These are created by using "depends-soft=" in the service file in place of "depends-on=". If a service stops or dies, it will not cause dependents with only a soft dependency to stop. This can be used to control startup order without creating a real dependency. Note: if a soft dependency fails to start, it still causes the dependent to also fail to start. --- load_service.cc | 7 +++- service.cc | 89 +++++++++++++------------------------------------ service.h | 11 ++++-- 3 files changed, 37 insertions(+), 70 deletions(-) diff --git a/load_service.cc b/load_service.cc index 9360403..24aa98c 100644 --- a/load_service.cc +++ b/load_service.cc @@ -118,6 +118,7 @@ ServiceRecord * ServiceSet::loadServiceRecord(const char * name) string command; int service_type = SVC_PROCESS; std::list depends_on; + std::list depends_soft; string logfile; // TODO catch I/O exceptions, wrap & re-throw? @@ -164,6 +165,10 @@ ServiceRecord * ServiceSet::loadServiceRecord(const char * name) string dependency_name = read_setting_value(&i, end); depends_on.push_back(loadServiceRecord(dependency_name.c_str())); } + else if (setting == "depends-soft") { + string dependency_name = read_setting_value(&i, end); + depends_soft.push_back(loadServiceRecord(dependency_name.c_str())); + } else if (setting == "logfile") { logfile = read_setting_value(&i, end); } @@ -199,7 +204,7 @@ ServiceRecord * ServiceSet::loadServiceRecord(const char * name) // We've found the dummy record delete rval; rval = new ServiceRecord(this, string(name), service_type, command, - &depends_on); + & depends_on, & depends_soft); rval->setLogfile(logfile); rval->setAutoRestart(auto_restart); *iter = rval; diff --git a/service.cc b/service.cc index c77f7fd..047a1b6 100644 --- a/service.cc +++ b/service.cc @@ -194,6 +194,11 @@ void ServiceRecord::started() (*i)->start(); } } + for (sr_iter i = soft_dependents.begin(); i != soft_dependents.end(); i++) { + if ((*i)->desired_state == SVC_STARTED) { + (*i)->start(); + } + } } else { stop(); @@ -206,79 +211,26 @@ void ServiceRecord::failed_to_start() desired_state = SVC_STOPPED; service_set->service_inactive(this); // failure to start - // TODO - inform listeners of failure // Cancel start of dependents: for (sr_iter i = dependents.begin(); i != dependents.end(); i++) { if ((*i)->desired_state == SVC_STARTED) { (*i)->failed_dependency(); } } + // What about soft dependents? + // TODO we should probably send them "start" rather than "failed_dependency", + // or add a parameter to failed_dependency which says whether the dependency + // was soft and change start handling as appropriate. + // For now just fail the startup: + for (sr_iter i = soft_dependents.begin(); i != soft_dependents.end(); i++) { + if ((*i)->desired_state == SVC_STARTED) { + (*i)->failed_dependency(); + } + } } bool ServiceRecord::start_ps_process() { - // BIG FAT NOTE: We rely on linux semantics of vfork() here. - // Specifically: - // * Parent process execution is suspended until the forked child - // successfully exec's another program, or it exits - // * Memory is shared between the two processes until exec() - // succeeds. - // Both of the above mean that we can determine in the parent process - // whether or not the exec succeeded. If vfork instead is implemented - // as an alias of fork, it will look like the exec always succeeded. - - /* - volatile int exec_status = 0; - pid_t forkpid = vfork(); - if (forkpid == 0) { - // Child process - // ev_default_destroy(); // won't need that on this side, free up fds. - // Hmm. causes segfault. Of course. Memory is shared due to vfork. - - // Re-set stdin, stdout, stderr - close(0); close(1); close(2); - string logfile = this->logfile; - if (logfile.length() == 0) { - logfile = "/dev/null"; - } - - if (open("/dev/null", O_RDONLY) == 0) { - // stdin = 0. That's what we should have; proceed with opening - // stdout and stderr. - open(logfile.c_str(), O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR); - dup2(1, 2); - } - - const char * pname = program_name.c_str(); - char const * args[2] = { pname, 0 }; - execvp(pname, (char ** const) args); - // If we got here, the exec failed - exec_status = errno; - _exit(0); - } - else { - // Parent process - we only reach here once the exec() above - // has succeeded, or _exit() above was called (because vfork() - // suspends the parent until either of those occurs). - if (exec_status == 0) { - // success - pid = forkpid; - - // Add a process listener so we can detect when the - // service stops - ev_child_init(&child_listener, process_child_callback, pid, 0); - child_listener.data = this; - ev_child_start(ev_default_loop(EVFLAG_AUTO), &child_listener); - - service_state = SVC_STARTED; - return true; - } - else { - return false; - } - } - */ - return start_ps_process(std::vector()); } @@ -387,13 +339,13 @@ void ServiceRecord::forceStop() stop(); for (sr_iter i = dependents.begin(); i != dependents.end(); i++) { (*i)->forceStop(); - } + } + // We don't want to force stop soft dependencies, however. } // A dependency of this service failed to start. void ServiceRecord::failed_dependency() { - // TODO notify listeners desired_state = SVC_STOPPED; // Presumably, we were starting. So now we're not. @@ -404,12 +356,17 @@ void ServiceRecord::failed_dependency() if ((*i)->desired_state == SVC_STARTED) { (*i)->failed_dependency(); } + } + for (sr_iter i = soft_dependents.begin(); i != soft_dependents.end(); i++) { + if ((*i)->desired_state == SVC_STARTED) { + (*i)->failed_dependency(); + } } } void ServiceRecord::dependentStopped() { - if (desired_state == SVC_STOPPED || force_stop) { + if (service_state != SVC_STOPPED && (desired_state == SVC_STOPPED || force_stop)) { bool all_deps_stopped = true; for (sr_iter i = dependents.begin(); i != dependents.end(); ++i) { if ((*i)->service_state != SVC_STOPPED) { diff --git a/service.h b/service.h index f57e1ca..011d24b 100644 --- a/service.h +++ b/service.h @@ -76,13 +76,14 @@ class ServiceRecord // and all its dependencies, MUST be stopped. string program_name; /* executable program or script */ string logfile; /* log file name, empty string specifies /dev/null */ - bool auto_restart; /* whether to restart this (process) if it dies */ + bool auto_restart; /* whether to restart this (process) if it dies unexpectedly */ typedef std::list sr_list; typedef sr_list::iterator sr_iter; sr_list depends_on; // services this one depends on sr_list dependents; // services depending on this one + sr_list soft_dependents; // services depending on this one via a "soft" dependency // unsigned wait_count; /* if we are waiting for dependents/dependencies to // start/stop, this is how many we're waiting for */ @@ -137,7 +138,7 @@ class ServiceRecord } ServiceRecord(ServiceSet *set, string name, int service_type, string command, - std::list * pdepends_on) + sr_list * pdepends_on, sr_list * pdepends_soft) : service_state(SVC_STOPPED), desired_state(SVC_STOPPED), force_stop(false), auto_restart(false) { service_set = set; @@ -147,11 +148,15 @@ class ServiceRecord // TODO splice the contents from the depends_on parameter // rather than duplicating the list. this->depends_on = *pdepends_on; + this->depends_on.insert(this->depends_on.end(), pdepends_soft->begin(), pdepends_soft->end()); // For each dependency, add us as a dependent. - for (sr_iter i = depends_on.begin(); i != depends_on.end(); ++i) { + for (sr_iter i = pdepends_on->begin(); i != pdepends_on->end(); ++i) { (*i)->dependents.push_back(this); } + for (sr_iter i = pdepends_soft->begin(); i != pdepends_soft->end(); ++i) { + (*i)->soft_dependents.push_back(this); + } } // Set logfile, should be done before service is started -- 2.25.1