From c91adb8b6cb91e9b15c65f07724e30b6677e222b Mon Sep 17 00:00:00 2001 From: Davin McCall Date: Thu, 8 Aug 2019 21:48:33 +1000 Subject: [PATCH] Change semantics of "wake" command. "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 | 20 +++++++- src/dinitctl.cc | 8 +++ src/tests/cptests/cptests.cc | 94 ++++++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 2 deletions(-) diff --git a/src/control.cc b/src/control.cc index f2dfcc7..2cb8b41 100644 --- a/src/control.cc +++ b/src/control.cc @@ -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(); diff --git a/src/dinitctl.cc b/src/dinitctl.cc index e2c867f..ae152c9 100644 --- a/src/dinitctl.cc +++ b/src/dinitctl.cc @@ -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; diff --git a/src/tests/cptests/cptests.cc b/src/tests/cptests/cptests.cc index 5b204e8..36f0382 100644 --- a/src/tests/cptests/cptests.cc +++ b/src/tests/cptests/cptests.cc @@ -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 cmd = { DINIT_CP_FINDSERVICE }; + uint16_t name_len = strlen(service_name1); + char *name_len_cptr = reinterpret_cast(&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 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(wdata[1]); + assert(s == service_state_t::STOPPED); + service_state_t ts = static_cast(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(&h1)); + + // Wake s1: + cmd = { DINIT_CP_WAKESERVICE, 0 /* don't pin */ }; + char * h_cp = reinterpret_cast(&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(&ip_h)); + assert(ip_h == h1); + assert(wdata[6] == static_cast(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; } -- 2.25.1