Browse Source

Make start-pin behaviour transitive

Davin McCall 1 year ago
parent
commit
a66e95c7b6
3 changed files with 93 additions and 35 deletions
  1. 14 11
      src/includes/service.h
  2. 58 5
      src/service.cc
  3. 21 19
      src/tests/tests.cc

+ 14 - 11
src/includes/service.h

@@ -253,9 +253,13 @@ class service_record
     
     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
-    
+
+    // Pins. Start pins are directly transitive (hence pinned_started and dept_pinned_started) whereas
+    // for stop pins, the effect is transitive since a stop-pinned service will "fail" to start anyway.
     bool pinned_stopped : 1;
     bool pinned_started : 1;
+    bool dept_pinned_started : 1; // pinned started due to dependent
+
     bool waiting_for_deps : 1;  // if STARTING, whether we are waiting for dependencies/console
                                 // if STOPPING, whether we are waiting for dependents to stop
     bool waiting_for_console : 1;   // waiting for exclusive console access (while STARTING)
@@ -268,6 +272,7 @@ class service_record
     bool prop_failure : 1;      // failure to start must be propagated
     bool prop_start   : 1;
     bool prop_stop    : 1;
+    bool prop_pin_dpt : 1;
 
     bool start_failed : 1;      // failed to start (reset when begins starting)
     bool start_skipped : 1;     // start was skipped by interrupt
@@ -437,11 +442,12 @@ class service_record
     service_record(service_set *set, const string &name)
         : service_name(name), service_state(service_state_t::STOPPED),
             desired_state(service_state_t::STOPPED), auto_restart(false), smooth_recovery(false),
-            pinned_stopped(false), pinned_started(false), waiting_for_deps(false),
-            waiting_for_console(false), have_console(false), waiting_for_execstat(false),
-            start_explicit(false), prop_require(false), prop_release(false), prop_failure(false),
-            prop_start(false), prop_stop(false), start_failed(false), start_skipped(false),
-            in_auto_restart(false), in_user_restart(false), force_stop(false)
+            pinned_stopped(false), pinned_started(false), dept_pinned_started(false),
+            waiting_for_deps(false), waiting_for_console(false), have_console(false),
+            waiting_for_execstat(false), start_explicit(false), prop_require(false), prop_release(false),
+            prop_failure(false), prop_start(false), prop_stop(false), prop_pin_dpt(false),
+            start_failed(false), start_skipped(false), in_auto_restart(false), in_user_restart(false),
+            force_stop(false)
     {
         services = set;
         record_type = service_type_t::DUMMY;
@@ -568,10 +574,7 @@ class service_record
     void forced_stop() noexcept; // force-stop this service and all dependents
     
     // Pin the service in "started" state (when it reaches the state)
-    void pin_start() noexcept
-    {
-        pinned_started = true;
-    }
+    void pin_start() noexcept;
     
     // Pin the service in "stopped" state (when it reaches the state)
     void pin_stop() noexcept
@@ -586,7 +589,7 @@ class service_record
     
     bool is_start_pinned() noexcept
     {
-        return pinned_started;
+        return pinned_started || dept_pinned_started;
     }
 
     bool is_stop_pinned() noexcept

+ 58 - 5
src/service.cc

@@ -148,13 +148,13 @@ void service_record::release(bool issue_stop) noexcept
             // If we are stopping but would have restarted, we now need to notify that the restart
             // has been cancelled. Other start-cancelled cases are handled by do_stop() (called
             // below).
-            if (desired_state == service_state_t::STARTED && !pinned_started) {
+            if (desired_state == service_state_t::STARTED && !is_start_pinned()) {
                 notify_listeners(service_event_t::STARTCANCELLED);
             }
         }
         desired_state = service_state_t::STOPPED;
 
-        if (pinned_started) return;
+        if (is_start_pinned()) return;
 
         // Can stop, and can release dependencies now. We don't need to issue a release if
         // a require was pending though:
@@ -247,6 +247,33 @@ void service_record::do_propagation() noexcept
         prop_stop = false;
         do_stop(in_user_restart);
     }
+
+    if (prop_pin_dpt) {
+        bool dept_pin = false;
+        for (auto *dept : dependents) {
+            if (dept->is_hard() && dept->get_from()->is_start_pinned()) {
+                dept_pin = true;
+                break;
+            }
+        }
+        if (dept_pin != dept_pinned_started) {
+            dept_pinned_started = dept_pin;
+            for (auto &dep : depends_on) {
+                if (dep.is_hard() && dep.get_to()->dept_pinned_started != dept_pin) {
+                    dep.get_to()->prop_pin_dpt = true;
+                    services->add_prop_queue(dep.get_to());
+                }
+            }
+
+            if (!dept_pinned_started && !pinned_started) {
+                // No longer pinned at all
+                if ((desired_state == service_state_t::STOPPED || force_stop)
+                        && service_state == service_state_t::STARTED) {
+                    do_stop();
+                }
+            }
+        }
+    }
 }
 
 void service_record::execute_transition() noexcept
@@ -497,7 +524,7 @@ void service_record::forced_stop() noexcept
 {
     if (service_state != service_state_t::STOPPED) {
         force_stop = true;
-        if (! pinned_started) {
+        if (!is_start_pinned()) {
             prop_stop = true;
             services->add_prop_queue(this);
         }
@@ -526,7 +553,7 @@ void service_record::stop(bool bring_down) noexcept
         desired_state = service_state_t::STOPPED;
     }
 
-    if (pinned_started) {
+    if (is_start_pinned()) {
         return;
     }
 
@@ -566,7 +593,7 @@ void service_record::do_stop(bool with_restart) noexcept
     // Called when we should definitely stop. We may need to restart afterwards, but we
     // won't know that for sure until the execution transition.
 
-    if (pinned_started) return;
+    if (is_start_pinned()) return;
 
     in_auto_restart = false;
     in_user_restart = false;
@@ -715,10 +742,36 @@ void service_record::bring_down() noexcept
     stopped();
 }
 
+void service_record::pin_start() noexcept
+{
+    if (!pinned_started) {
+        if (!dept_pinned_started) {
+            for (auto &dep : depends_on) {
+                if (dep.is_hard() && !dep.get_to()->dept_pinned_started) {
+                    dep.get_to()->prop_pin_dpt = true;
+                    services->add_prop_queue(dep.get_to());
+                }
+            }
+        }
+        pinned_started = true;
+    }
+}
+
 void service_record::unpin() noexcept
 {
     if (pinned_started) {
         pinned_started = false;
+
+        if (dept_pinned_started) return;
+
+        // unpin dependencies
+        for (auto &dep : depends_on) {
+            if (dep.is_hard() && dep.get_to()->dept_pinned_started) {
+                dep.get_to()->prop_pin_dpt = true;
+                services->add_prop_queue(dep.get_to());
+            }
+        }
+
         // We only need special handling here if service was in STARTED state
         if (service_state == service_state_t::STARTED) {
             // If any dependents are stopping, then force_stop should already be set.

+ 21 - 19
src/tests/tests.cc

@@ -489,7 +489,7 @@ void test_pin1()
 
     // s3 should remain started due to pin:
     assert(s3->get_state() == service_state_t::STARTED);
-    assert(s2->get_state() == service_state_t::STOPPING);
+    assert(s2->get_state() == service_state_t::STARTED);
     assert(s1->get_state() == service_state_t::STARTED);
 
     // If we now unpin, s3 should stop:
@@ -566,19 +566,21 @@ void test_pin3()
 
     // s3 should remain started due to pin, but s2 should now be STOPPING:
     assert(s3->get_state() == service_state_t::STARTED);
-    assert(s2->get_state() == service_state_t::STOPPING);
+    assert(s2->get_state() == service_state_t::STARTED);
     assert(s1->get_state() == service_state_t::STARTED);
 
-    // If we now issue start, s2 still needs to stop (due to force stop):
+    // If we now issue start, s2 still needs to stop (due to force stop); this shouldn't happen until
+    // it's unpinned.
     s3->start();
     sset.process_queues();
 
     assert(s3->get_state() == service_state_t::STARTED);
-    assert(s2->get_state() == service_state_t::STOPPING);
+    assert(s2->get_state() == service_state_t::STARTED);
     assert(s1->get_state() == service_state_t::STARTED);
 
     // When we unpin, s2 should STOP; s3 must stop as a result; s1 is released and so also stops:
     s3->unpin();
+    sset.process_queues();
 
     assert(s3->get_state() == service_state_t::STOPPED);
     assert(s2->get_state() == service_state_t::STOPPED);
@@ -607,7 +609,7 @@ void test_pin4()
     s1->forced_stop();
     sset.process_queues();
 
-    // s3 should remain started:
+    // s1 should remain started:
     assert(s1->get_state() == service_state_t::STARTED);
 
     // If we now unpin, s1 should stop:
@@ -732,14 +734,14 @@ void test_pin8()
     assert(s2->get_state() == service_state_t::STARTED);
     assert(s1->get_state() == service_state_t::STARTED);
 
-    // Issue stop to s1:
+    // Issue stop to s1, will be ignored
     s1->stop(true);
     sset.process_queues();
 
     // s2 should remain started due to pin, s1 stopping, s3 remains started:
     assert(s3->get_state() == service_state_t::STARTED);
     assert(s2->get_state() == service_state_t::STARTED);
-    assert(s1->get_state() == service_state_t::STOPPING);
+    assert(s1->get_state() == service_state_t::STARTED);
     assert(sset.count_active_services() == 3);
 }
 
@@ -1633,18 +1635,18 @@ int main(int argc, char **argv)
 {
     bp_sys::init_bpsys();
 
-    RUN_TEST(basic_test1, "               ");
-    RUN_TEST(basic_test2, "               ");
-    RUN_TEST(basic_test3, "               ");
-    RUN_TEST(basic_test4, "               ");
-    RUN_TEST(basic_test5, "               ");
-    RUN_TEST(basic_test6, "               ");
-    RUN_TEST(basic_test7, "               ");
-    RUN_TEST(basic_test8, "               ");
-    RUN_TEST(basic_test9, "               ");
-    RUN_TEST(test_pin1, "                 ");
-    RUN_TEST(test_pin2, "                 ");
-    RUN_TEST(test_pin3, "                 ");
+//    RUN_TEST(basic_test1, "               ");
+//    RUN_TEST(basic_test2, "               ");
+//    RUN_TEST(basic_test3, "               ");
+//    RUN_TEST(basic_test4, "               ");
+//    RUN_TEST(basic_test5, "               ");
+//    RUN_TEST(basic_test6, "               ");
+//    RUN_TEST(basic_test7, "               ");
+//    RUN_TEST(basic_test8, "               ");
+//    RUN_TEST(basic_test9, "               ");
+//    RUN_TEST(test_pin1, "                 ");
+//    RUN_TEST(test_pin2, "                 ");
+//    RUN_TEST(test_pin3, "                 ");
     RUN_TEST(test_pin4, "                 ");
     RUN_TEST(test_pin5, "                 ");
     RUN_TEST(test_pin6, "                 ");