Implement careful/"gentle" service stop (abort if dependent would stop).
authorDavin McCall <davmac@davmac.org>
Sat, 20 Jul 2019 08:50:41 +0000 (09:50 +0100)
committerDavin McCall <davmac@davmac.org>
Sat, 20 Jul 2019 08:50:41 +0000 (09:50 +0100)
src/control.cc
src/includes/control-cmds.h
src/includes/control.h
src/includes/service.h

index 8b355a8e6aef303c602b924dd53d846a9a4cfa1d..a418a420250b2b3ccddeb3b404e8b7ec332c9fdf 100644 (file)
@@ -175,6 +175,35 @@ bool control_conn_t::process_find_load(int pktType)
     return true;
 }
 
+bool control_conn_t::check_dependents(service_record *service, bool &had_dependents)
+{
+    std::vector<char> reply_pkt;
+
+    for (service_dep *dep : service->get_dependents()) {
+        if (dep->dep_type == dependency_type::REGULAR) {
+            // find or allocate a service handle
+            handle_t dept_handle = allocate_service_handle(dep->get_from());
+            if (reply_pkt.empty()) {
+                // packet type, size
+                reply_pkt.reserve(1 + sizeof(size_t) + sizeof(handle_t));
+                reply_pkt.resize(1 + sizeof(size_t));
+                reply_pkt[0] = DINIT_RP_DEPENDENTS;
+            }
+            auto old_size = reply_pkt.size();
+            reply_pkt.resize(old_size + sizeof(handle_t));
+            memcpy(reply_pkt.data() + old_size, &dept_handle, sizeof(dept_handle));
+        }
+    }
+
+    had_dependents = !reply_pkt.empty();
+    if (had_dependents) {
+        // We didn't build a reply packet, so there are no affected dependents
+        if (! queue_packet(std::move(reply_pkt))) return false;
+    }
+
+    return true;
+}
+
 bool control_conn_t::process_start_stop(int pktType)
 {
     using std::string;
@@ -190,7 +219,7 @@ bool control_conn_t::process_start_stop(int pktType)
     // 1 byte: pin in requested state (0 = no pin, 1 = pin)
     // 4 bytes: service handle
     
-    bool do_pin = (rbuf[1] == 1);
+    bool do_pin = ((rbuf[1] & 1) == 1);
     handle_t handle;
     rbuf.extract((char *) &handle, 2, sizeof(handle));
     
@@ -219,13 +248,27 @@ bool control_conn_t::process_start_stop(int pktType)
             if (service->get_state() == service_state_t::STARTED) ack_buf[0] = DINIT_RP_ALREADYSS;
             break;
         case DINIT_CP_STOPSERVICE:
+        {
             // force service to stop
+            bool gentle = ((rbuf[1] & 2) == 2);
+            if (gentle) {
+                // Check dependents; return appropriate response if any will be affected
+                bool has_dependents;
+                if (check_dependents(service, has_dependents)) {
+                    return false;
+                }
+                if (has_dependents) {
+                    // Reply packet has already been sent
+                    goto clear_out;
+                }
+            }
             if (do_pin) service->pin_stop();
             service->stop(true);
             service->forced_stop();
             services->process_queues();
             if (service->get_state() == service_state_t::STOPPED) ack_buf[0] = DINIT_RP_ALREADYSS;
             break;
+        }
         case DINIT_CP_WAKESERVICE:
             // re-start a stopped service (do not mark as required)
             if (services->is_shutting_down()) {
@@ -249,6 +292,7 @@ bool control_conn_t::process_start_stop(int pktType)
         if (! queue_packet(ack_buf, 1)) return false;
     }
     
+    clear_out:
     // Clear the packet from the buffer
     rbuf.consume(pkt_size);
     chklen = 0;
index ed5d844817ba8b12ebb8668d84c7a210f80c72ac..3c734cb6078846aaa7f330bbc13ed71f1f8b215c 100644 (file)
@@ -78,6 +78,9 @@ constexpr static int DINIT_RP_LISTDONE = 63;
 // Service loader information:
 constexpr static int DINIT_RP_LOADER_MECH = 64;
 
+// Dependent services prevent stopping/restarting. Includes size_t count, handle_t * N handles.
+constexpr static int DINIT_RP_DEPENDENTS = 65;
+
 // Information:
 
 // Service event occurred (4-byte service handle, 1 byte event code)
index 62f54b0a4b74ed248b8c80e388821b9196be36e6..05721ed90577feb7d48a8f4d70273c335ac5a3da 100644 (file)
@@ -119,7 +119,7 @@ class control_conn_t : private service_listener
     //            true (with bad_conn_close == false) if the packet was successfully
     //              queued;
     //            true (with bad_conn_close == true) if the packet was not successfully
-    //              queued (but a suitable error packate has been queued).
+    //              queued (but a suitable error packet has been queued).
     // The in/out watch enabled state will also be set appropriately.
     bool queue_packet(vector<char> &&v) noexcept;
     bool queue_packet(const char *pkt, unsigned size) noexcept;
@@ -163,6 +163,11 @@ class control_conn_t : private service_listener
     
     bool send_data() noexcept;
     
+    // Check if any dependents will be affected by stopping a service, generate a response packet if so.
+    // had_dependents will be set true if the service should not be stopped, false otherwise.
+    // Returns false if the connection must be closed, true otherwise.
+    bool check_dependents(service_record *service, bool &had_dependents);
+
     // Allocate a new handle for a service; may throw std::bad_alloc
     handle_t allocate_service_handle(service_record *record);
     
index 1acec0851811d86539a985299c8f9a2fdb087254..487d976a43d70a01fcc454fe1238407357e8a860 100644 (file)
@@ -606,6 +606,11 @@ class service_record
         return depends_on;
     }
 
+    dpt_list & get_dependents()
+    {
+        return dependents;
+    }
+
     // Add a dependency. Caller must ensure that the services are in an appropriate state and that
     // a circular dependency chain is not created. Propagation queues should be processed after
     // calling this. May throw std::bad_alloc.