force_stop = false;
// If we are a soft dependency of another target, break the acquisition from that target now:
- bool will_restart = (desired_state == ServiceState::STARTED)
- && service_set->get_auto_restart();
-
- if (! will_restart) {
- for (auto dependency : soft_dpts) {
- if (dependency->holding_acq) {
- dependency->holding_acq = false;
- release();
- }
+ for (auto dependent : soft_dpts) {
+ if (dependent->holding_acq) {
+ dependent->holding_acq = false;
+ release();
}
}
+ bool will_restart = (desired_state == ServiceState::STARTED)
+ && service_set->get_auto_restart();
+
for (auto dependency : depends_on) {
- // we signal dependencies in case they are waiting for us to stop - but only if we won't
- // restart or if they are stopping uninterruptibly.
- if (! will_restart || ! dependency->can_interrupt_stop()) {
- dependency->dependentStopped();
- }
+ // we signal dependencies in case they are waiting for us to stop:
+ dependency->dependentStopped();
}
service_state = ServiceState::STOPPED;
if (will_restart) {
// Desired state is "started".
restarting = true;
- service_set->addToStartQueue(this);
+ start(false);
}
else {
if (socket_fd != -1) {
void ServiceRecord::require() noexcept
{
if (required_by++ == 0) {
-
- if (! prop_require) {
- prop_require = true;
- prop_release = false;
- service_set->addToPropQueue(this);
- }
-
- if (service_state == ServiceState::STOPPED) {
- // (In any other state, the service is already considered active.)
- service_set->service_active(this);
- }
+ prop_require = !prop_release;
+ prop_release = false;
+ service_set->addToPropQueue(this);
}
}
{
if (--required_by == 0) {
desired_state = ServiceState::STOPPED;
- // Can stop, and can release dependencies now:
- prop_release = true;
+
+ // Can stop, and can release dependencies now. We don't need to issue a release if
+ // the require was pending though:
+ prop_release = !prop_require;
prop_require = false;
service_set->addToPropQueue(this);
- if (service_state != ServiceState::STOPPED) {
- service_set->addToStopQueue(this);
+
+ if (service_state == ServiceState::STOPPED) {
+ service_set->service_inactive(this);
}
else {
- service_set->service_inactive(this);
+ do_stop();
}
}
}
}
if (desired_state == ServiceState::STARTED && service_state != ServiceState::STOPPED) return;
-
+
+ bool was_active = service_state != ServiceState::STOPPED || desired_state != ServiceState::STOPPED;
desired_state = ServiceState::STARTED;
- service_set->addToStartQueue(this);
+
+ if (service_state != ServiceState::STOPPED) {
+ // We're already starting/started, or we are stopping and need to wait for
+ // that the complete.
+ if (service_state != ServiceState::STOPPING || ! can_interrupt_stop()) {
+ return;
+ }
+ // We're STOPPING, and that can be interrupted. Our dependencies might be STOPPING,
+ // but if so they are waiting (for us), so they too can be instantly returned to
+ // STARTING state.
+ notifyListeners(ServiceEvent::STOPCANCELLED);
+ }
+ else if (! was_active) {
+ service_set->service_active(this);
+ }
+
+ service_state = ServiceState::STARTING;
+ waiting_for_deps = true;
+
+ if (startCheckDependencies(true)) {
+ service_set->addToStartQueue(this);
+ }
}
void ServiceRecord::do_propagation() noexcept
failed_to_start(true);
}
- if (waiting_for_deps) {
- if (service_state == ServiceState::STARTING) {
- if (startCheckDependencies(false)) {
- allDepsStarted();
- }
- }
- else if (service_state == ServiceState::STOPPING) {
- if (stopCheckDependents()) {
- all_deps_stopped();
- }
- }
+ if (prop_start) {
+ prop_start = false;
+ start(false);
+ }
+
+ if (prop_stop) {
+ prop_stop = false;
+ do_stop();
}
}
void ServiceRecord::execute_transition() noexcept
{
- bool is_started = (service_state == ServiceState::STARTED)
- || (service_state == ServiceState::STARTING && can_interrupt_start());
- bool is_stopped = (service_state == ServiceState::STOPPED)
- || (service_state == ServiceState::STOPPING && can_interrupt_stop());
-
- if (is_started && (desired_state == ServiceState::STOPPED || force_stop)) {
- if (! pinned_started) {
- do_stop();
+ if (service_state == ServiceState::STARTING) {
+ if (startCheckDependencies(false)) {
+ allDepsStarted(false);
}
}
- else if (is_stopped && desired_state == ServiceState::STARTED) {
- if (! pinned_stopped) {
- do_start();
+ else if (service_state == ServiceState::STOPPING) {
+ if (stopCheckDependents()) {
+ all_deps_stopped();
}
}
}
{
if (pinned_stopped) return;
- if (service_state != ServiceState::STOPPED) {
- // We're already starting/started, or we are stopping and need to wait for
- // that the complete.
- if (service_state != ServiceState::STOPPING || ! can_interrupt_stop()) {
- return;
- }
- // We're STOPPING, and that can be interrupted. Our dependencies might be STOPPING,
- // but if so they are waiting (for us), so they too can be instantly returned to
- // STARTING state.
- notifyListeners(ServiceEvent::STOPCANCELLED);
+ if (service_state != ServiceState::STARTING) {
+ return;
}
service_state = ServiceState::STARTING;
waiting_for_deps = true;
// Ask dependencies to start, mark them as being waited on.
- if (! startCheckDependencies(true)) {
- return;
+ if (startCheckDependencies(false)) {
+ // Once all dependencies are started, we start properly:
+ allDepsStarted();
}
-
- // Actually start this service.
- allDepsStarted();
}
void ServiceRecord::dependencyStarted() noexcept
{
if (service_state == ServiceState::STARTING && waiting_for_deps) {
- service_set->addToPropQueue(this);
+ service_set->addToStartQueue(this);
}
}
if ((*i)->service_state != ServiceState::STARTED) {
if (start_deps) {
all_deps_started = false;
- (*i)->start(false);
+ (*i)->prop_start = true;
+ service_set->addToPropQueue(*i);
}
else {
return false;
ServiceRecord * to = i->getTo();
if (start_deps) {
if (to->service_state != ServiceState::STARTED) {
- to->start(false);
+ to->prop_start = true;
+ service_set->addToPropQueue(to);
i->waiting_on = true;
all_deps_started = false;
}
if (force_stop || desired_state == ServiceState::STOPPED) {
// We must now stop.
- service_set->addToStopQueue(this);
+ do_stop();
return;
}
void ServiceRecord::dependentStopped() noexcept
{
if (service_state == ServiceState::STOPPING && waiting_for_deps) {
- service_set->addToPropQueue(this);
+ service_set->addToStopQueue(this);
}
}
start_explicit = false;
release();
}
-
- if (bring_down && desired_state != ServiceState::STOPPED) {
- desired_state = ServiceState::STOPPED;
- service_set->addToStopQueue(this);
+
+ if (bring_down) {
+ do_stop();
}
}
// We must have had desired_state == STARTED.
notifyListeners(ServiceEvent::STARTCANCELLED);
- // Reaching this point, we have can_interrupt_start() == true. So,
- // we can stop. Dependents might be starting, but they must be
- // waiting on us, so they should also be immediately stoppable.
- // Fall through to below,.
+ // Reaching this point, we are starting interruptibly - so we
+ // stop now (by falling through to below).
}
else {
// If we're starting we need to wait for that to complete.
service_state = ServiceState::STOPPING;
waiting_for_deps = true;
-
- // If we get here, we are in STARTED state; stop all dependents.
if (stopDependents()) {
- all_deps_stopped();
+ service_set->addToStopQueue(this);
}
}
(*i)->forceStop();
}
- (*i)->do_stop();
+ (*i)->prop_stop = true;
+ service_set->addToPropQueue(*i);
}
return all_deps_stopped;
*
* 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).
+ * Transition between states occurs in two phases: propagation and execution. In both phases
+ * a linked-list queue is used to keep track of which services need processing; this avoids
+ * recursion (which would be of unknown depth and therefore liable to stack overflow).
+ *
+ * In the propagation phase, acquisition/release messages are processed, and desired state may be
+ * altered accordingly. Start and stop requests are also propagated in this phase. The state may
+ * be set to STARTING or STOPPING to reflect the desired state, but will never be set to STARTED
+ * or STOPPED (that happens in the execution phase).
+ *
+ * Propagation variables:
+ * prop_acquire: the service has transitioned to an acquired state and must issue an acquire
+ * on its dependencies
+ * prop_release: the service has transitioned to a released state and must issue a release on
+ * its dependencies.
+ *
+ * prop_start: the service should start
+ * prop_stop: the service should stop
+ *
+ * Note that "prop_acquire"/"prop_release" form a pair which cannot both be set at the same time
+ * which is enforced via explicit checks. For "prop_start"/"prop_stop" this occurs implicitly.
*
* In the execution phase, actions are taken to achieve the desired state. Actual state may
- * transition according to the current and desired states.
+ * transition according to the current and desired states. Processes can be sent signals, etc
+ * in order to stop them. A process can restart if it stops, but it does so by raising prop_start
+ * which needs to be processed in a second transition phase. Seeing as starting never causes
+ * another process to stop, the transition-execute-transition cycle always ends at the 2nd
+ * transition stage, at the latest.
*/
struct OnstartFlags {
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
+ bool prop_start : 1;
+ bool prop_stop : 1;
bool restarting : 1; // re-starting after unexpected termination
int required_by = 0; // number of dependents wanting this service to be started
pinned_stopped(false), pinned_started(false), waiting_for_deps(false),
waiting_for_execstat(false), start_explicit(false),
prop_require(false), prop_release(false), prop_failure(false),
- restarting(false), force_stop(false)
+ prop_start(false), prop_stop(false), restarting(false), force_stop(false)
{
service_set = set;
service_name = name;