releaseConsole();
}
- logServiceStopped(service_name);
service_state = ServiceState::STOPPED;
force_stop = false;
+ logServiceStopped(service_name);
notifyListeners(ServiceEvent::STOPPED);
+ bool will_restart = (desired_state == ServiceState::STARTED) && service_set->get_auto_restart();
for (auto dependency : depends_on) {
- dependency->dependentStopped();
+ if (! will_restart || ! dependency->can_interrupt_stop()) {
+ dependency->dependentStopped();
+ }
}
- if (desired_state == ServiceState::STARTED && service_set->get_auto_restart()) {
+ if (will_restart) {
// Desired state is "started".
do_start();
}
if (need_stop) {
// Failed startup: no auto-restart.
desired_state = ServiceState::STOPPED;
- do_stop();
+ forceStop();
}
return;
void ServiceRecord::start(bool activate) noexcept
{
- if (activate) {
- if (!start_explicit) require();
+ if (activate && ! start_explicit) {
+ require();
start_explicit = true;
}
if (desired_state == ServiceState::STARTED && service_state != ServiceState::STOPPED) return;
if (required_by == 0) {
- service_set->service_active(this);
+ // It really doesn't make any sense to start if there is no dependent or explicit activation.
+ return;
}
desired_state = ServiceState::STARTED;
release();
}
- if (bring_down) {
+ if (bring_down && desired_state != ServiceState::STOPPED) {
desired_state = ServiceState::STOPPED;
do_stop();
}
return all_deps_stopped;
}
-// All dependents have stopped; we can stop now, too.
+// All dependents have stopped; we can stop now, too. Only called when STOPPING.
void ServiceRecord::allDepsStopped()
{
waiting_for_deps = false;
int status;
pid_t r = waitpid(pid, &status, WNOHANG);
if (r == -1 && errno == ECHILD) {
- // We can't track this child
+ // We can't track this child (or it's terminated already)
stopped();
}
else if (r == pid) {
if (pinned_started) {
pinned_started = false;
if (desired_state == ServiceState::STOPPED) {
- stop();
+ do_stop();
}
}
if (pinned_stopped) {
* 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 {
int revents) noexcept;
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