Fix callback method name per Dasynq changes
[oweals/dinit.git] / src / service.h
index ffd28898d70e8440391dd58af4605844e4882c99..25e00daa5a5dc21cb5280793d5e5687529b85464 100644 (file)
@@ -6,14 +6,20 @@
 #include <vector>
 #include <csignal>
 #include <unordered_set>
-#include "ev.h"
+
+#include "dasynq.h"
+
 #include "control.h"
 #include "service-listener.h"
 #include "service-constants.h"
 
 /*
- * Possible service states
+ * This header defines ServiceRecord, a data record maintaining information about a service,
+ * and ServiceSet, a set of interdependent service records. It also defines some associated
+ * types and exceptions.
  *
+ * Service states
+ * --------------
  * Services have both a current state and a desired state. The desired state can be
  * either STARTED or STOPPED. The current state can also be STARTING or STOPPING.
  * A service can be "pinned" in either the STARTED or STOPPED states to prevent it
  * A process service is in the STOPPING state when it has been signalled to stop, and is
  * in the STARTING state when waiting for dependencies to start or for the exec() call in
  * the forked child to complete and return a status.
+ *
+ * Acquisition/release:
+ * ------------------
+ * Each service has a dependent-count ("required_by"). This starts at 0, adds 1 if the
+ * service has explicitly been started (i.e. "start_explicit" is true), and adds 1 for
+ * each dependent service which is not STOPPED (including dependents with a soft dependency).
+ * When required_by transitions to 0, the service is stopped (unless it is pinned). When
+ * require_by transitions from 0, the service is started (unless pinned).
+ *
+ * So, in general, the dependent-count determines the desired state (STARTED if the count
+ * is greater than 0, otherwise STOPPED). However, a service can be issued a stop-and-take
+ * down order (via `stop(true)'); this will first stop dependent services, which may restart
+ * and cancel the stop of the former service. Finally, a service can be force-stopped, which
+ * means that its stop process cannot be cancelled (though it may still be put in a desired
+ * state of STARTED, meaning it will start immediately upon stopping).
+ *
+ * Pinning
+ * -------
+ * A service may be "pinned" in either STARTED or STOPPED states (or even both). Once it
+ * reaches a pinned state, a service will not leave that state, though its desired state
+ * may still be set. (Note that pinning prevents, but never causes, state transition).
+ *
+ * The priority of the different state deciders is:
+ *  - pins
+ *  - force stop flag
+ *  - desired state (which is manipulated by require/release operations)
+ *
+ * So a forced stop cannot occur until the service is not pinned started, for instance.
+ *
+ * Two-phase transition
+ * --------------------
+ * Transition between states occurs in two phases: propagation and execution. In the
+ * propagation phase, acquisition/release messages are processed, and desired state may be
+ * altered accordingly. Desired state of dependencies/dependents should not be examined in
+ * this phase, since it may change during the phase (i.e. its current value at any point
+ * may not reflect the true final value).
+ *
+ * In the execution phase, actions are taken to achieve the desired state. Actual state may
+ * transition according to the current and desired states.
  */
 
 struct OnstartFlags {
     bool rw_ready : 1;
+    bool log_ready : 1;
     
     // Not actually "onstart" commands:
     bool no_sigterm : 1;  // do not send SIGTERM
     bool runs_on_console : 1;  // run "in the foreground"
+    bool pass_cs_fd : 1;  // pass this service a control socket connection via fd
     
-    OnstartFlags() noexcept : rw_ready(false),
-            no_sigterm(false), runs_on_console(false)
+    OnstartFlags() noexcept : rw_ready(false), log_ready(false),
+            no_sigterm(false), runs_on_console(false), pass_cs_fd(false)
     {
     }
 };
@@ -57,8 +104,8 @@ class ServiceLoadExc
     const char *excDescription;
     
     protected:
-    ServiceLoadExc(std::string serviceName) noexcept
-        : serviceName(serviceName)
+    ServiceLoadExc(std::string serviceName, const char *desc) noexcept
+        : serviceName(serviceName), excDescription(desc)
     {
     }
 };
@@ -67,9 +114,8 @@ class ServiceNotFound : public ServiceLoadExc
 {
     public:
     ServiceNotFound(std::string serviceName) noexcept
-        : ServiceLoadExc(serviceName)
+        : ServiceLoadExc(serviceName, "Service description not found.")
     {
-        excDescription = "Service description not found.";
     }
 };
 
@@ -77,26 +123,23 @@ class ServiceCyclicDependency : public ServiceLoadExc
 {
     public:
     ServiceCyclicDependency(std::string serviceName) noexcept
-        : ServiceLoadExc(serviceName)
+        : ServiceLoadExc(serviceName, "Has cyclic dependency.")
     {
-        excDescription = "Has cyclic dependency.";
     }
 };
 
 class ServiceDescriptionExc : public ServiceLoadExc
 {
     public:
-    std::string extraInfo;
-    
     ServiceDescriptionExc(std::string serviceName, std::string extraInfo) noexcept
-        : ServiceLoadExc(serviceName), extraInfo(extraInfo)
+        : ServiceLoadExc(serviceName, extraInfo.c_str())
     {
-        excDescription = extraInfo.c_str();
     }    
 };
 
-class ServiceRecord; // forward declaration
-class ServiceSet; // forward declaration
+class ServiceRecord;
+class ServiceSet;
+class base_process_service;
 
 /* Service dependency record */
 class ServiceDep
@@ -107,8 +150,10 @@ class ServiceDep
     public:
     /* Whether the 'from' service is waiting for the 'to' service to start */
     bool waiting_on;
+    /* Whether the 'from' service is holding an acquire on the 'to' service */
+    bool holding_acq;
 
-    ServiceDep(ServiceRecord * from, ServiceRecord * to) noexcept : from(from), to(to), waiting_on(false)
+    ServiceDep(ServiceRecord * from, ServiceRecord * to) noexcept : from(from), to(to), waiting_on(false), holding_acq(false)
     {  }
 
     ServiceRecord * getFrom() noexcept
@@ -146,9 +191,27 @@ static std::vector<const char *> separate_args(std::string &s, std::list<std::pa
     return r;
 }
 
+class ServiceChildWatcher : public EventLoop_t::child_proc_watcher_impl<ServiceChildWatcher>
+{
+    public:
+    base_process_service * service;
+    rearm status_change(EventLoop_t &eloop, pid_t child, int status) noexcept;
+    
+    ServiceChildWatcher(base_process_service * sr) noexcept : service(sr) { }
+};
+
+class ServiceIoWatcher : public EventLoop_t::fd_watcher_impl<ServiceIoWatcher>
+{
+    public:
+    base_process_service * service;
+    rearm fd_event(EventLoop_t &eloop, int fd, int flags) noexcept;
+    
+    ServiceIoWatcher(base_process_service * sr) noexcept : service(sr) { }
+};
 
 class ServiceRecord
 {
+    protected:
     typedef std::string string;
     
     string service_name;
@@ -156,11 +219,11 @@ class ServiceRecord
     ServiceState service_state = ServiceState::STOPPED; /* ServiceState::STOPPED, STARTING, STARTED, STOPPING */
     ServiceState desired_state = ServiceState::STOPPED; /* ServiceState::STOPPED / STARTED */
 
-    string program_name;          /* storage for program/script and arguments */
-    std::vector<const char *> exec_arg_parts; /* pointer to each argument/part of the program_name */
+    string program_name;          // storage for program/script and arguments
+    std::vector<const char *> exec_arg_parts; // pointer to each argument/part of the program_name, and nullptr
     
-    string stop_command;          /* storage for stop program/script and arguments */
-    std::vector<const char *> stop_arg_parts; /* pointer to each argument/part of the stop_command */
+    string stop_command;          // storage for stop program/script and arguments
+    std::vector<const char *> stop_arg_parts; // pointer to each argument/part of the stop_command, and nullptr
     
     string pid_file;
     
@@ -175,10 +238,13 @@ class ServiceRecord
     bool pinned_started : 1;
     bool waiting_for_deps : 1;  // if STARTING, whether we are waiting for dependencies (inc console) to start
     bool waiting_for_execstat : 1;  // if we are waiting for exec status after fork()
-    bool doing_recovery : 1;    // if we are currently recovering a BGPROCESS (restarting process, while
-                                //   holding STARTED service state)
     bool start_explicit : 1;    // whether we are are explictly required to be started
-    int started_deps = 0;       // number of dependents that require this service to be started
+
+    bool prop_require : 1;      // require must be propagated
+    bool prop_release : 1;      // release must be propagated
+    bool prop_failure : 1;      // failure to start must be propagated
+    
+    int required_by = 0;        // number of dependents wanting this service to be started
 
     typedef std::list<ServiceRecord *> sr_list;
     typedef sr_list::iterator sr_iter;
@@ -199,9 +265,6 @@ class ServiceRecord
     
     ServiceSet *service_set; // the set this service belongs to
     
-    // Next service (after this one) in the queue for the console:
-    ServiceRecord *next_for_console;
-
     std::unordered_set<ServiceListener *> listeners;
     
     // Process services:
@@ -214,7 +277,7 @@ class ServiceRecord
     string socket_path; // path to the socket for socket-activation service
     int socket_perms;   // socket permissions ("mode")
     uid_t socket_uid = -1;  // socket user id or -1
-    gid_t socket_gid = -1;  // sockget group id or -1
+    gid_t socket_gid = -1;  // socket group id or -1
 
     // Implementation details
     
@@ -224,12 +287,23 @@ class ServiceRecord
     int exit_status; // Exit status, if the process has exited (pid == -1).
     int socket_fd = -1;  // For socket-activation services, this is the file
                          // descriptor for the socket.
-
-    ev_child child_listener;
-    ev_io child_status_listener;
+    
+    
+    // Data for use by ServiceSet
+    public:
+    
+    // Next service (after this one) in the queue for the console. Intended to only be used by ServiceSet class.
+    ServiceRecord *next_for_console;
+    
+    // Propagation and start/stop queues
+    ServiceRecord *next_in_prop_queue = nullptr;
+    ServiceRecord *next_in_stop_queue = nullptr;
+    
+    
+    protected:
     
     // All dependents have stopped.
-    void allDepsStopped();
+    virtual void all_deps_stopped() noexcept;
     
     // Service has actually stopped (includes having all dependents
     // reaching STOPPED state).
@@ -242,33 +316,29 @@ class ServiceRecord
     //   dep_failed: whether failure is recorded due to a dependency failing
     void failed_to_start(bool dep_failed = false) noexcept;
 
-    // For process services, start the process, return true on success
-    bool start_ps_process() noexcept;
-    bool start_ps_process(const std::vector<const char *> &args, bool on_console) noexcept;
+    void run_child_proc(const char * const *args, const char *logfile, bool on_console, int wpipefd,
+            int csfd) noexcept;
     
     // Callback from libev when a child process dies
-    static void process_child_callback(struct ev_loop *loop, struct ev_child *w,
-            int revents) noexcept;
-    
-    static void process_child_status(struct ev_loop *loop, ev_io * stat_io,
+    static void process_child_callback(EventLoop_t *loop, ServiceChildWatcher *w,
             int revents) noexcept;
     
-    void handle_exit_status() noexcept;
-    
+    //virtual void handle_exit_status(int exit_status) noexcept;
+
     // A dependency has reached STARTED state
     void dependencyStarted() noexcept;
     
     void allDepsStarted(bool haveConsole = false) noexcept;
-    
-    // Read the pid-file, return false on failure
-    bool read_pid_file() noexcept;
-    
+
+    // Do any post-dependency startup; return false on failure
+    virtual bool start_ps_process() noexcept;
+
     // Open the activation socket, return false on failure
     bool open_socket() noexcept;
-    
+
     // Check whether dependencies have started, and optionally ask them to start
     bool startCheckDependencies(bool do_start) noexcept;
-    
+
     // Whether a STARTING service can immediately transition to STOPPED (as opposed to
     // having to wait for it reach STARTED and then go through STOPPING).
     bool can_interrupt_start() noexcept
@@ -282,10 +352,6 @@ class ServiceRecord
         return waiting_for_deps && ! force_stop;
     }
 
-    // Notify dependencies that we no longer need them,
-    // (if this is actually the case).
-    void notify_dependencies_stopped() noexcept;
-
     // A dependent has reached STOPPED state
     void dependentStopped() noexcept;
 
@@ -295,7 +361,16 @@ class ServiceRecord
     // issue a stop to all dependents, return true if they are all already stopped
     bool stopDependents() noexcept;
     
-    void forceStop() noexcept; // force-stop this service and all dependents
+    void require() noexcept;
+    void release() noexcept;
+    void release_dependencies() noexcept;
+    
+    // Check if service is, fundamentally, stopped.
+    bool is_stopped() noexcept
+    {
+        return service_state == ServiceState::STOPPED
+            || (service_state == ServiceState::STARTING && waiting_for_deps);
+    }
     
     void notifyListeners(ServiceEvent event) noexcept
     {
@@ -307,24 +382,22 @@ class ServiceRecord
     // Queue to run on the console. 'acquiredConsole()' will be called when the console is available.
     void queueForConsole() noexcept;
     
-    // Console is available.
-    void acquiredConsole() noexcept;
-    
     // Release console (console must be currently held by this service)
     void releaseConsole() noexcept;
     
-    bool get_start_flag(bool transitive)
-    {
-        return transitive ? started_deps != 0 : start_explicit;
-    }
-    
+    bool do_auto_restart() noexcept;
+
+    // Started state reached
+    bool process_started() noexcept;
+
     public:
 
     ServiceRecord(ServiceSet *set, string name)
         : service_state(ServiceState::STOPPED), desired_state(ServiceState::STOPPED), auto_restart(false),
             pinned_stopped(false), pinned_started(false), waiting_for_deps(false),
-            waiting_for_execstat(false), doing_recovery(false),
-            start_explicit(false), force_stop(false)
+            waiting_for_execstat(false), start_explicit(false),
+            prop_require(false), prop_release(false), prop_failure(false),
+            force_stop(false)
     {
         service_set = set;
         service_name = name;
@@ -340,7 +413,7 @@ class ServiceRecord
         this->service_type = service_type;
         this->depends_on = std::move(*pdepends_on);
 
-        program_name = command;
+        program_name = std::move(command);
         exec_arg_parts = separate_args(program_name, command_offsets);
 
         for (sr_iter i = depends_on.begin(); i != depends_on.end(); ++i) {
@@ -356,7 +429,23 @@ class ServiceRecord
         }
     }
     
-    // TODO write a destructor
+    virtual ~ServiceRecord() noexcept
+    {
+    }
+    
+    // begin transition from stopped to started state or vice versa depending on current and desired state
+    void execute_transition() noexcept;
+    
+    void do_propagation() noexcept;
+    
+    // Called on transition of desired state from stopped to started (or unpinned stop)
+    void do_start() noexcept;
+
+    // Called on transition of desired state from started to stopped (or unpinned start)
+    void do_stop() noexcept;
+    
+    // Console is available.
+    void acquiredConsole() noexcept;
     
     // Set the stop command and arguments (may throw std::bad_alloc)
     void setStopCommand(std::string command, std::list<std::pair<unsigned,unsigned>> &stop_command_offsets)
@@ -408,28 +497,41 @@ class ServiceRecord
     
     void set_pid_file(string &&pid_file) noexcept
     {
-        this->pid_file = pid_file;
+        this->pid_file = std::move(pid_file);
     }
     
     void set_socket_details(string &&socket_path, int socket_perms, uid_t socket_uid, uid_t socket_gid) noexcept
     {
-        this->socket_path = socket_path;
+        this->socket_path = std::move(socket_path);
         this->socket_perms = socket_perms;
         this->socket_uid = socket_uid;
         this->socket_gid = socket_gid;
     }
 
-    const char *getServiceName() const noexcept { return service_name.c_str(); }
+    const std::string &getServiceName() const noexcept { return service_name; }
     ServiceState getState() const noexcept { return service_state; }
     
-    void start(bool transitive = false) noexcept;  // start the service
-    void stop() noexcept;   // stop the service
-    void do_start() noexcept;
-    void do_stop() noexcept;
+    void start(bool activate = true) noexcept;  // start the service
+    void stop(bool bring_down = true) noexcept;   // stop the service
+    
+    void forceStop() noexcept; // force-stop this service and all dependents
+    
+    // Pin the service in "started" state (when it reaches the state)
+    void pinStart() noexcept
+    {
+        pinned_started = true;
+    }
+    
+    // Pin the service in "stopped" state (when it reaches the state)
+    void pinStop() noexcept
+    {
+        pinned_stopped = true;
+    }
     
-    void pinStart() noexcept;  // start the service and pin it
-    void pinStop() noexcept;   // stop the service and pin it
-    void unpin() noexcept;     // unpin the service
+    // Remove both "started" and "stopped" pins. If the service is currently pinned
+    // in either state but would naturally be in the opposite state, it will immediately
+    // commence starting/stopping.
+    void unpin() noexcept;
     
     bool isDummy() noexcept
     {
@@ -449,7 +551,119 @@ class ServiceRecord
     }
 };
 
+class base_process_service : public ServiceRecord
+{
+    friend class ServiceChildWatcher;
+    friend class ServiceIoWatcher;
+
+    protected:
+    ServiceChildWatcher child_listener;
+    ServiceIoWatcher child_status_listener;
+
+    // start the process, return true on success
+    virtual bool start_ps_process() noexcept;
+    bool start_ps_process(const std::vector<const char *> &args, bool on_console) noexcept;
+
+    virtual void all_deps_stopped() noexcept;
+    virtual void handle_exit_status(int exit_status) noexcept = 0;
+
+    public:
+    base_process_service(ServiceSet *sset, string name, ServiceType service_type, string &&command,
+            std::list<std::pair<unsigned,unsigned>> &command_offsets,
+            sr_list * pdepends_on, sr_list * pdepends_soft)
+         : ServiceRecord(sset, name, service_type, std::move(command), command_offsets,
+             pdepends_on, pdepends_soft), child_listener(this), child_status_listener(this)
+    {
+    }
+
+    ~base_process_service() noexcept
+    {
+    }
+};
+
+class process_service : public base_process_service
+{
+    // called when the process exits. The exit_status is the status value yielded by
+    // the "wait" system call.
+    virtual void handle_exit_status(int exit_status) noexcept override;
+
+    public:
+    process_service(ServiceSet *sset, string name, string &&command,
+            std::list<std::pair<unsigned,unsigned>> &command_offsets,
+            sr_list * pdepends_on, sr_list * pdepends_soft)
+         : base_process_service(sset, name, ServiceType::PROCESS, std::move(command), command_offsets,
+             pdepends_on, pdepends_soft)
+    {
+    }
+
+    ~process_service() noexcept
+    {
+    }
+};
+
+class bgproc_service : public base_process_service
+{
+    virtual void handle_exit_status(int exit_status) noexcept override;
+
+    bool doing_recovery : 1;    // if we are currently recovering a BGPROCESS (restarting process, while
+                                //   holding STARTED service state)
+
+    // Read the pid-file, return false on failure
+    bool read_pid_file() noexcept;
+
+    public:
+    bgproc_service(ServiceSet *sset, string name, string &&command,
+            std::list<std::pair<unsigned,unsigned>> &command_offsets,
+            sr_list * pdepends_on, sr_list * pdepends_soft)
+         : base_process_service(sset, name, ServiceType::BGPROCESS, std::move(command), command_offsets,
+             pdepends_on, pdepends_soft)
+    {
+        doing_recovery = false;
+    }
+
+    ~bgproc_service() noexcept
+    {
+    }
+};
+
+class scripted_service : public base_process_service
+{
+    virtual void all_deps_stopped() noexcept override;
+    virtual void handle_exit_status(int exit_status) noexcept override;
 
+    public:
+    scripted_service(ServiceSet *sset, string name, string &&command,
+            std::list<std::pair<unsigned,unsigned>> &command_offsets,
+            sr_list * pdepends_on, sr_list * pdepends_soft)
+         : base_process_service(sset, name, ServiceType::SCRIPTED, std::move(command), command_offsets,
+             pdepends_on, pdepends_soft)
+    {
+    }
+
+    ~scripted_service() noexcept
+    {
+    }
+};
+
+
+/*
+ * A ServiceSet, as the name suggests, manages a set of services.
+ *
+ * Other than the ability to find services by name, the service set manages various queues.
+ * One is the queue for processes wishing to acquire the console. There is also a set of
+ * processes that want to start, and another set of those that want to stop. These latter
+ * two "queues" (not really queues since their order is not important) are used to prevent too
+ * much recursion and to prevent service states from "bouncing" too rapidly.
+ * 
+ * A service that wishes to start or stop puts itself on the start/stop queue; a service that
+ * needs to propagate changes to dependent services or dependencies puts itself on the
+ * propagation queue. Any operation that potentially manipulates the queues must be followed
+ * by a "process queues" order (processQueues() method).
+ *
+ * Note that processQueues always repeatedly processes both queues until they are empty. The
+ * process is finite because starting a service can never cause services to stop, unless they
+ * fail to start, which should cause them to stop semi-permanently.
+ */
 class ServiceSet
 {
     int active_services;
@@ -459,7 +673,12 @@ class ServiceSet
     
     ShutdownType shutdown_type = ShutdownType::CONTINUE;  // Shutdown type, if stopping
     
+    ServiceRecord * console_queue_head = nullptr; // first record in console queue
     ServiceRecord * console_queue_tail = nullptr; // last record in console queue
+
+    // Propagation and start/stop "queues" - list of services waiting for processing
+    ServiceRecord * first_prop_queue = nullptr;
+    ServiceRecord * first_stop_queue = nullptr;
     
     // Private methods
         
@@ -489,7 +708,7 @@ class ServiceSet
     void startService(const char *name);
     
     // Locate an existing service record.
-    ServiceRecord *findService(const std::string &name) noexcept;
+    ServiceRecord *find_service(const std::string &name) noexcept;
     
     // Find a loaded service record, or load it if it is not loaded.
     // Throws:
@@ -497,25 +716,101 @@ class ServiceSet
     //   std::bad_alloc on out-of-memory condition 
     ServiceRecord *loadService(const std::string &name)
     {
-        ServiceRecord *record = findService(name);
+        ServiceRecord *record = find_service(name);
         if (record == nullptr) {
             record = loadServiceRecord(name.c_str());
         }
         return record;
     }
     
+    // Get the list of all loaded services.
+    const std::list<ServiceRecord *> &listServices()
+    {
+        return records;
+    }
+    
     // Stop the service with the given name. The named service will begin
     // transition to the 'stopped' state.
     void stopService(const std::string &name) noexcept;
     
+    // Add a service record to the state propogation queue
+    void addToPropQueue(ServiceRecord *service) noexcept
+    {
+        if (service->next_in_prop_queue == nullptr && first_prop_queue != service) {
+            service->next_in_prop_queue = first_prop_queue;
+            first_prop_queue = service;
+        }
+    }
+    
+    // Add a service record to the start queue; called by service record
+    void addToStartQueue(ServiceRecord *service) noexcept
+    {
+        // The start/stop queue is actually one queue:
+        addToStopQueue(service);
+    }
+    
+    // Add a service to the stop queue; called by service record
+    void addToStopQueue(ServiceRecord *service) noexcept
+    {
+        if (service->next_in_stop_queue == nullptr && first_stop_queue != service) {
+            service->next_in_stop_queue = first_stop_queue;
+            first_stop_queue = service;
+        }
+    }
+    
+    // Process state propagation and start/stop queues, until they are empty.
+    // TODO remove the pointless parameter
+    void processQueues(bool ignoredparam = false) noexcept
+    {
+        while (first_stop_queue != nullptr || first_prop_queue != nullptr) {
+            while (first_prop_queue != nullptr) {
+                auto next = first_prop_queue;
+                first_prop_queue = next->next_in_prop_queue;
+                next->next_in_prop_queue = nullptr;
+                next->do_propagation();
+            }
+            while (first_stop_queue != nullptr) {
+                auto next = first_stop_queue;
+                first_stop_queue = next->next_in_stop_queue;
+                next->next_in_stop_queue = nullptr;
+                next->execute_transition();
+            }
+        }
+    }
+    
     // Set the console queue tail (returns previous tail)
-    ServiceRecord * consoleQueueTail(ServiceRecord * newTail) noexcept
+    ServiceRecord * append_console_queue(ServiceRecord * newTail) noexcept
     {
         auto prev_tail = console_queue_tail;
         console_queue_tail = newTail;
+        newTail->next_for_console = nullptr;
+        if (! prev_tail) {
+            console_queue_head = newTail;
+            enable_console_log(false);
+        }
+        else {
+            prev_tail->next_for_console = newTail;
+        }
         return prev_tail;
     }
     
+    // Retrieve the current console queue head and remove it from the queue
+    ServiceRecord * pullConsoleQueue() noexcept
+    {
+        auto prev_head = console_queue_head;
+        if (prev_head) {
+            prev_head->acquiredConsole();
+            console_queue_head = prev_head->next_for_console;
+            if (! console_queue_head) {
+                console_queue_tail = nullptr;
+            }
+        }
+        else {
+            enable_console_log(true);
+        }
+        return prev_head;
+    }
+    
     // Notification from service that it is active (state != STOPPED)
     // Only to be called on the transition from inactive to active.
     void service_active(ServiceRecord *) noexcept;
@@ -536,9 +831,10 @@ class ServiceSet
         restart_enabled = false;
         shutdown_type = type;
         for (std::list<ServiceRecord *>::iterator i = records.begin(); i != records.end(); ++i) {
-            (*i)->stop();
+            (*i)->stop(false);
             (*i)->unpin();
         }
+        processQueues(false);
     }
     
     void set_auto_restart(bool restart) noexcept