X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=src%2Fservice.h;h=eed59e621cb3094e34b33df119fa7c607bc1493f;hb=dff52e4261b762fbf7dd69b79b071693ebcf36c9;hp=f8bbdebf42f6bd10ad2d72a53bf4733b93aacd1f;hpb=ed798e97850840af7019cb7500c9483e8fe51888;p=oweals%2Fdinit.git diff --git a/src/service.h b/src/service.h index f8bbdeb..eed59e6 100644 --- a/src/service.h +++ b/src/service.h @@ -6,14 +6,20 @@ #include #include #include -#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 @@ -34,16 +40,45 @@ * 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. + * + * Aquisition/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 depdendents 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. */ 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" - OnstartFlags() noexcept : rw_ready(false), + OnstartFlags() noexcept : rw_ready(false), log_ready(false), no_sigterm(false), runs_on_console(false) { } @@ -146,9 +181,38 @@ static std::vector separate_args(std::string &s, std::listfd = fd; + EventLoop_t::FdWatcher::addWatch(loop, fd, flags); + } +}; class ServiceRecord { + friend class ServiceChildWatcher; + friend class ServiceIoWatcher; + typedef std::string string; string service_name; @@ -199,9 +263,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 listeners; // Process services: @@ -224,9 +285,22 @@ 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; + + ServiceChildWatcher child_listener; + ServiceIoWatcher 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; + + // Start/stop queues + ServiceRecord *next_in_start_queue = nullptr; + ServiceRecord *next_in_stop_queue = nullptr; + + + private: // All dependents have stopped. void allDepsStopped(); @@ -247,17 +321,14 @@ class ServiceRecord bool start_ps_process(const std::vector &args, bool on_console) noexcept; // Callback from libev when a child process dies - static void process_child_callback(struct ev_loop *loop, struct ev_child *w, + static void process_child_callback(EventLoop_t *loop, ServiceChildWatcher *w, int revents) noexcept; - static void process_child_status(struct ev_loop *loop, ev_io * stat_io, + static void process_child_status(EventLoop_t *loop, ServiceIoWatcher * stat_io, int revents) noexcept; void handle_exit_status() noexcept; - - void do_start() noexcept; - void do_stop() noexcept; - + // A dependency has reached STARTED state void dependencyStarted() noexcept; @@ -319,19 +390,18 @@ 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 do_auto_restart() 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) + start_explicit(false), force_stop(false), child_listener(this), child_status_listener(this) { service_set = set; service_name = name; @@ -364,6 +434,15 @@ class ServiceRecord } // TODO write a destructor + + // 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> &stop_command_offsets) @@ -430,13 +509,26 @@ class ServiceRecord ServiceState getState() const noexcept { return service_state; } void start(bool activate = true) noexcept; // start the service - void stop() noexcept; // stop the service + void stop(bool bring_down = true) noexcept; // stop the service void forceStop() noexcept; // force-stop this service and all dependents - void pinStart() noexcept; // start the service and pin it - void pinStop() noexcept; // stop the service and pin it - void unpin() noexcept; // unpin the service + // 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; + } + + // 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 { @@ -456,7 +548,25 @@ class ServiceRecord } }; - +/* + * 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 stop puts itself on the stop queue; a service that wishes to start + * puts itself on the start queue. Any operation that potentially manipulates the queues must + * be folloed by a "process queues" order (processQueues method, which can be instructed to + * process either the start queue or the stop queue first). + * + * Note that which queue it does process first, processQueues always repeatedly processes both + * queues until they are empty. The process is finite because starting a service can never + * cause services to be added to the stop queue, unless they fail to start, which should cause + * them to stop semi-permanently. + */ class ServiceSet { int active_services; @@ -466,7 +576,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 + + // start/stop "queue" - list of services waiting to stop/start + ServiceRecord * first_start_queue = nullptr; + ServiceRecord * first_stop_queue = nullptr; // Private methods @@ -515,14 +630,84 @@ class ServiceSet // transition to the 'stopped' state. void stopService(const std::string &name) noexcept; + // Add a service record to the start queue + void addToStartQueue(ServiceRecord *service) noexcept + { + if (service->next_in_start_queue == nullptr && first_start_queue != service) { + service->next_in_start_queue = first_start_queue; + first_start_queue = service; + } + } + + // Add a service to the stop queue + 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; + } + } + + void processQueues(bool do_start_first) noexcept + { + if (! do_start_first) { + 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->do_stop(); + } + } + + while (first_stop_queue != nullptr || first_start_queue != nullptr) { + while (first_start_queue != nullptr) { + auto next = first_start_queue; + first_start_queue = next->next_in_start_queue; + next->next_in_start_queue = nullptr; + next->do_start(); + } + 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->do_stop(); + } + } + } + // Set the console queue tail (returns previous tail) ServiceRecord * consoleQueueTail(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; @@ -543,9 +728,10 @@ class ServiceSet restart_enabled = false; shutdown_type = type; for (std::list::iterator i = records.begin(); i != records.end(); ++i) { - (*i)->stop(); + (*i)->stop(false); (*i)->unpin(); } + processQueues(false); } void set_auto_restart(bool restart) noexcept