Change semantics of "wake" command.
authorDavin McCall <davmac@davmac.org>
Thu, 8 Aug 2019 11:48:33 +0000 (21:48 +1000)
committerDavin McCall <davmac@davmac.org>
Thu, 8 Aug 2019 11:48:33 +0000 (21:48 +1000)
"Wake" now re-attaches dependencies (from the dependents of the named
service). You can no longer wake a service if it has no active
dependents.

src/control.cc
src/dinitctl.cc
src/tests/cptests/cptests.cc

index f2dfcc77eee9c7cd95c0f50650d8af997e488b47..2cb8b410c10472742109765daa8b7d376a549b09 100644 (file)
@@ -293,16 +293,32 @@ bool control_conn_t::process_start_stop(int pktType)
             break;
         }
         case DINIT_CP_WAKESERVICE:
-            // re-start a stopped service (do not mark as required)
+        {
+            // re-attach a service to its (started) dependents, causing it to start.
             if (services->is_shutting_down()) {
                 ack_buf[0] = DINIT_RP_NAK;
                 break;
             }
+            bool found_dpt = false;
+            for (auto dpt : service->get_dependents()) {
+                auto from = dpt->get_from();
+                auto from_state = from->get_state();
+                if (from_state == service_state_t::STARTED || from_state == service_state_t::STARTING) {
+                    found_dpt = true;
+                    if (! dpt->holding_acq) {
+                        dpt->get_from()->start_dep(*dpt);
+                    }
+                }
+            }
+            if (! found_dpt) {
+                ack_buf[0] = DINIT_RP_NAK;
+            }
+
             if (do_pin) service->pin_start();
-            service->start(false);
             services->process_queues();
             if (service->get_state() == service_state_t::STARTED) ack_buf[0] = DINIT_RP_ALREADYSS;
             break;
+        }
         case DINIT_CP_RELEASESERVICE:
             // remove required mark, stop if not required by dependents
             if (do_pin) service->pin_stop();
index e2c867fe2ca3d001fdd56e4809e7258e0f8e3a6b..ae152c9306322ffcc4b2fdb3e6abc3cf8791eef6 100644 (file)
@@ -582,6 +582,14 @@ static int start_stop_service(int socknum, cpbuffer_t &rbuffer, const char *serv
             cerr << "dinitctl: cannot restart service; service not started.\n";
             return 1;
         }
+        if (reply_pkt_h == DINIT_RP_NAK && command == command_t::START_SERVICE) {
+            cerr << "dinitctl: cannot start service (during shut down).\n";
+            return 1;
+        }
+        if (reply_pkt_h == DINIT_RP_NAK && command == command_t::WAKE_SERVICE) {
+            cerr << "dinitctl: service has no active dependents (or system is shutting down), cannot wake.\n";
+            return 1;
+        }
         if (reply_pkt_h != DINIT_RP_ACK && reply_pkt_h != DINIT_RP_ALREADYSS) {
             cerr << "dinitctl: protocol error." << endl;
             return 1;
index 5b204e8f8bb2469db45b96545ade1bc7aa5cebbf..36f0382ab8e4dad59a3e4a6dcceaa15d3e2a89aa 100644 (file)
@@ -1009,6 +1009,99 @@ void cptest_restart()
     delete cc;
 }
 
+void cptest_wake()
+{
+    service_set sset;
+
+    const char * const service_name1 = "test-service-1";
+    const char * const service_name2 = "test-service-2";
+
+    service_record *s1 = new service_record(&sset, service_name1, service_type_t::INTERNAL, {});
+    sset.add_service(s1);
+    service_record *s2 = new service_record(&sset, service_name2, service_type_t::INTERNAL,
+            {{ s1, dependency_type::WAITS_FOR }});
+    sset.add_service(s2);
+
+    s2->start();
+    sset.process_queues();
+
+    s1->stop(true);
+    sset.process_queues();
+
+    assert(s1->get_state() == service_state_t::STOPPED);
+    assert(s2->get_state() == service_state_t::STARTED);
+
+    int fd = bp_sys::allocfd();
+    auto *cc = new control_conn_t(event_loop, &sset, fd);
+
+    // Get a service handle:
+    std::vector<char> cmd = { DINIT_CP_FINDSERVICE };
+    uint16_t name_len = strlen(service_name1);
+    char *name_len_cptr = reinterpret_cast<char *>(&name_len);
+    cmd.insert(cmd.end(), name_len_cptr, name_len_cptr + sizeof(name_len));
+    cmd.insert(cmd.end(), service_name1, service_name1 + name_len);
+
+    bp_sys::supply_read_data(fd, std::move(cmd));
+
+    event_loop.regd_bidi_watchers[fd]->read_ready(event_loop, fd);
+
+    std::vector<char> wdata;
+    bp_sys::extract_written_data(fd, wdata);
+
+    assert(wdata.size() == 3 + sizeof(control_conn_t::handle_t));
+    assert(wdata[0] == DINIT_RP_SERVICERECORD);
+    service_state_t s = static_cast<service_state_t>(wdata[1]);
+    assert(s == service_state_t::STOPPED);
+    service_state_t ts = static_cast<service_state_t>(wdata[6]);
+    assert(ts == service_state_t::STOPPED);
+
+    control_conn_t::handle_t h1;
+    std::copy(wdata.data() + 2, wdata.data() + 2 + sizeof(h1), reinterpret_cast<char *>(&h1));
+
+    // Wake s1:
+    cmd = { DINIT_CP_WAKESERVICE, 0 /* don't pin */ };
+    char * h_cp = reinterpret_cast<char *>(&h1);
+    cmd.insert(cmd.end(), h_cp, h_cp + sizeof(h1));
+    bp_sys::supply_read_data(fd, std::move(cmd));
+
+    event_loop.regd_bidi_watchers[fd]->read_ready(event_loop, fd);
+    bp_sys::extract_written_data(fd, wdata);
+
+    assert(wdata.size() == 1 + 7 /* ACK reply + info packet */);
+    assert(wdata[0] == DINIT_IP_SERVICEEVENT);
+    // packetsize, key (handle), event
+    assert(wdata[1] == 7);
+    control_conn_t::handle_t ip_h;
+    std::copy(wdata.data() + 2, wdata.data() + 2 + sizeof(ip_h), reinterpret_cast<char *>(&ip_h));
+    assert(ip_h == h1);
+    assert(wdata[6] == static_cast<int>(service_event_t::STARTED));
+
+    // and then the ack (already started):
+    assert(wdata[7] == DINIT_RP_ALREADYSS);
+
+    // now stop s2 (and therefore s1):
+    s2->stop(true);
+    sset.process_queues();
+    assert(s1->get_state() == service_state_t::STOPPED);
+    assert(s2->get_state() == service_state_t::STOPPED);
+
+    // Clear any info packets:
+    bp_sys::extract_written_data(fd, wdata);
+
+    // Trying to wake s1 should now fail:
+    cmd = { DINIT_CP_WAKESERVICE, 0 /* don't pin */ };
+    cmd.insert(cmd.end(), h_cp, h_cp + sizeof(h1));
+    bp_sys::supply_read_data(fd, std::move(cmd));
+
+    event_loop.regd_bidi_watchers[fd]->read_ready(event_loop, fd);
+    bp_sys::extract_written_data(fd, wdata);
+
+    assert(wdata.size() == 1);
+    assert(wdata[0] == DINIT_RP_NAK);
+
+    delete cc;
+}
+
 
 #define RUN_TEST(name, spacing) \
     std::cout << #name "..." spacing << std::flush; \
@@ -1030,5 +1123,6 @@ int main(int argc, char **argv)
     RUN_TEST(cptest_addrmdeps, "          ");
     RUN_TEST(cptest_enableservice, "      ");
     RUN_TEST(cptest_restart, "            ");
+    RUN_TEST(cptest_wake, "               ");
     return 0;
 }