// start service, mark as required
if (do_pin) service->pinStart();
service->start();
+ service_set->processQueues(true);
already_there = service->getState() == ServiceState::STARTED;
break;
case DINIT_CP_STOPSERVICE:
if (do_pin) service->pinStop();
service->stop(true);
service->forceStop();
+ service_set->processQueues(false);
already_there = service->getState() == ServiceState::STOPPED;
break;
case DINIT_CP_WAKESERVICE:
// re-start a stopped service (do not mark as required)
if (do_pin) service->pinStart();
service->start(false);
+ service_set->processQueues(true);
already_there = service->getState() == ServiceState::STARTED;
break;
case DINIT_CP_RELEASESERVICE:
// remove required mark, stop if not required by dependents
if (do_pin) service->pinStop();
service->stop();
+ service_set->processQueues(false);
already_there = service->getState() == ServiceState::STOPPED;
break;
default:
}
else {
service->unpin();
+ service_set->processQueues(true);
char ack_buf[] = { (char) DINIT_RP_ACK };
if (! queuePacket(ack_buf, 1)) return false;
}
ServiceRecord *record = findService(name);
if (record != nullptr) {
record->stop();
+ processQueues(false);
}
}
// Failed startup: no auto-restart.
desired_state = ServiceState::STOPPED;
forceStop();
+ service_set->processQueues(false);
}
return;
if (! do_auto_restart()) desired_state = ServiceState::STOPPED;
forceStop();
}
+ service_set->processQueues(false);
}
else { // SCRIPTED
if (service_state == ServiceState::STOPPING) {
// can be stopped:
stopped();
}
+ service_set->processQueues(false);
}
else { // STARTING
if (exit_status == 0) {
Rearm ServiceIoWatcher::gotEvent(EventLoop_t *loop, int fd, int flags) noexcept
{
ServiceRecord::process_child_status(loop, this, flags);
- return Rearm::NOOP;
+ return Rearm::REMOVED;
}
// TODO remove unused revents param
release_dependencies();
}
else {
- do_stop();
+ service_set->addToStopQueue(this);
}
}
}
pidbuf[r] = 0; // store nul terminator
pid = std::atoi(pidbuf);
if (kill(pid, 0) == 0) {
- //ev_child_init(&child_listener, process_child_callback, pid, 0);
- //child_listener.data = this;
- //ev_child_start(ev_default_loop(EVFLAG_AUTO), &child_listener);
child_listener.registerWith(&eventLoop, pid);
}
else {
if (force_stop || desired_state == ServiceState::STOPPED) {
// We must now stop.
- do_stop();
+ service_set->addToStopQueue(this);
return;
}
logServiceFailed(service_name);
service_state = ServiceState::STOPPED;
- stop(); // release dependencies if appropriate
+ if (start_explicit) {
+ start_explicit = false;
+ release();
+ }
notifyListeners(ServiceEvent::FAILEDSTART);
// Cancel start of dependents:
{
if (service_state != ServiceState::STOPPED) {
force_stop = true;
- do_stop();
+ service_set->addToStopQueue(this);
}
}
if (bring_down && desired_state != ServiceState::STOPPED) {
desired_state = ServiceState::STOPPED;
- do_stop();
+ service_set->addToStopQueue(this);
}
}
(*i)->forceStop();
}
else {
- (*i)->do_stop();
+ service_set->addToStopQueue(*i);
}
}
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.
-
+
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();
void handle_exit_status() 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;
-
// A dependency has reached STARTED state
void dependencyStarted() noexcept;
// TODO write a destructor
- // Next service (after this one) in the queue for the console. Intended to only be used by ServiceSet class.
- ServiceRecord *next_for_console;
+ // 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;
}
};
-
+/*
+ * 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;
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
// 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
{