Implement "soft" dependencies. These are created by using
authorDavin McCall <davmac@davmac.org>
Mon, 7 Sep 2015 09:44:15 +0000 (10:44 +0100)
committerDavin McCall <davmac@davmac.org>
Mon, 7 Sep 2015 09:44:15 +0000 (10:44 +0100)
"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
service.cc
service.h

index 936040382b328e785cd7c950afe8abafe9c2641c..24aa98c63c34bdb5b10921dcd9e58b5a3a73ed1e 100644 (file)
@@ -118,6 +118,7 @@ ServiceRecord * ServiceSet::loadServiceRecord(const char * name)
     string command;
     int service_type = SVC_PROCESS;
     std::list<ServiceRecord *> depends_on;
+    std::list<ServiceRecord *> 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;
index c77f7fdbecbf832ecd653cccc3ae05f925408f01..047a1b64bcd8f2e99e77480e11a8d64877e245ba 100644 (file)
@@ -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<std::string>());
 }
 
@@ -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) {
index f57e1caac7ec99bad6b81a3bf202859ed5850028..011d24b70c3cea35cccbee8030598cc62abb0f6a 100644 (file)
--- 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<ServiceRecord *> 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<ServiceRecord *> * 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