}
}
-void ServiceRecord::start(bool unpinned) noexcept
+void ServiceRecord::start() noexcept
{
if ((service_state == ServiceState::STARTING || service_state == ServiceState::STARTED)
&& desired_state == ServiceState::STOPPED) {
notifyListeners(ServiceEvent::STOPCANCELLED);
}
- if (desired_state == ServiceState::STARTED && !unpinned) return;
+ if (desired_state == ServiceState::STARTED && service_state != ServiceState::STOPPED) return;
desired_state = ServiceState::STARTED;
+ 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.
- return;
+ 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.
}
- if (pinned_stopped) return;
-
service_state = ServiceState::STARTING;
service_set->service_active(this);
+ waiting_for_deps = true;
+
// Ask dependencies to start, mark them as being waited on.
if (! startCheckDependencies(true)) {
return;
void ServiceRecord::dependencyStarted() noexcept
{
- if (service_state != ServiceState::STARTING) {
+ if (service_state != ServiceState::STARTING || ! waiting_for_deps) {
return;
}
void ServiceRecord::allDepsStarted(bool hasConsole) noexcept
{
if (onstart_flags.runs_on_console && ! hasConsole) {
+ waiting_for_deps = true;
queueForConsole();
return;
}
+
+ waiting_for_deps = false;
if (service_type == ServiceType::PROCESS) {
bool start_success = start_ps_process();
// Notify any dependents whose desired state is STARTED:
for (auto i = dependents.begin(); i != dependents.end(); i++) {
- if ((*i)->desired_state == ServiceState::STARTED) {
- (*i)->dependencyStarted();
- }
+ (*i)->dependencyStarted();
}
for (auto i = soft_dpts.begin(); i != soft_dpts.end(); i++) {
- if ((*i)->getFrom()->desired_state == ServiceState::STARTED) {
- (*i)->getFrom()->dependencyStarted();
- }
+ (*i)->getFrom()->dependencyStarted();
}
}
}
}
for (auto i = soft_dpts.begin(); i != soft_dpts.end(); i++) {
- if ((*i)->getFrom()->desired_state == ServiceState::STARTED) {
- // We can send 'start', because this is only a soft dependency.
- // Our startup failure means that they don't have to wait for us.
- (*i)->getFrom()->dependencyStarted();
- }
+ // We can send 'start', because this is only a soft dependency.
+ // Our startup failure means that they don't have to wait for us.
+ (*i)->getFrom()->dependencyStarted();
}
}
}
// A dependency of this service failed to start.
+// Only called when state == STARTING.
void ServiceRecord::failed_dependency()
{
desired_state = ServiceState::STOPPED;
// Presumably, we were starting. So now we're not.
service_state = ServiceState::STOPPED;
service_set->service_inactive(this);
+ logServiceFailed(service_name);
// Notify dependents of this service also
for (auto i = dependents.begin(); i != dependents.end(); i++) {
}
}
for (auto i = soft_dpts.begin(); i != soft_dpts.end(); i++) {
- if ((*i)->getFrom()->service_state == ServiceState::STARTING) {
- // It's a soft dependency, so send them 'started' rather than
- // 'failed dep'.
- (*i)->getFrom()->dependencyStarted();
- }
+ // It's a soft dependency, so send them 'started' rather than
+ // 'failed dep'.
+ (*i)->getFrom()->dependencyStarted();
}
}
}
}
-void ServiceRecord::stop(bool unpinned) noexcept
+void ServiceRecord::stop() noexcept
{
if ((service_state == ServiceState::STOPPING || service_state == ServiceState::STOPPED)
&& desired_state == ServiceState::STARTED) {
if (service_state != ServiceState::STARTED) {
if (service_state == ServiceState::STARTING) {
- // Well this is awkward: we're going to have to continue
- // starting, but we don't want any dependents to think that
- // they are still waiting to start.
- // Make sure they remain stopped:
- if (stopDependents()) {
- if (service_type == ServiceType::INTERNAL) {
- // Internal services can go straight from STARTING to STOPPED.
- allDepsStopped();
- // (Other types have to finish starting first).
- }
+ if (! can_interrupt_start()) {
+ // Well this is awkward: we're going to have to continue
+ // starting, but we don't want any dependents to think that
+ // they are still waiting to start.
+ // Make sure they remain stopped:
+ stopDependents();
+ return;
}
+
+ // 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.
+ }
+ else {
+ // If we're starting we need to wait for that to complete.
+ // If we're already stopping/stopped there's nothing to do.
+ return;
}
-
- // If we're starting we need to wait for that to complete.
- // If we're already stopping/stopped there's nothing to do.
- return;
}
service_state = ServiceState::STOPPING;
+ waiting_for_deps = true;
+
// If we get here, we are in STARTED state; stop all dependents.
if (stopDependents()) {
allDepsStopped();
// Dependency stopped or is stopping; we must stop too.
void ServiceRecord::allDepsStopped()
{
+ waiting_for_deps = false;
if (service_type == ServiceType::PROCESS) {
if (pid != -1) {
// The process is still kicking on - must actually kill it.
if (pinned_started) {
pinned_started = false;
if (desired_state == ServiceState::STOPPED) {
- stop(true);
+ stop();
}
}
if (pinned_stopped) {
pinned_stopped = false;
if (desired_state == ServiceState::STARTED) {
- start(true);
+ start();
}
}
}
bool auto_restart : 1; /* whether to restart this (process) if it dies unexpectedly */
bool pinned_stopped : 1;
bool pinned_started : 1;
+
+ bool waiting_for_deps : 1; /* if STARTING, whether we are waiting for dependencies (inc console) to start */
typedef std::list<ServiceRecord *> sr_list;
typedef sr_list::iterator sr_iter;
// 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
+ {
+ return waiting_for_deps;
+ }
+
+ // Whether a STOPPING service can immediately transition to STARTED.
+ bool can_interrupt_stop() noexcept
+ {
+ return waiting_for_deps && ! force_stop;
+ }
// A dependent has reached STOPPED state
void dependentStopped() noexcept;
const char *getServiceName() const noexcept { return service_name.c_str(); }
ServiceState getState() const noexcept { return service_state; }
- void start(bool unpinned = false) noexcept; // start the service
- void stop(bool unpinned = false) noexcept; // stop the service
+ void start() noexcept; // start the service
+ void stop() noexcept; // stop the service
void pinStart() noexcept; // start the service and pin it
void pinStop() noexcept; // stop the service and pin it