All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.yamcs.YamcsInstanceService Maven / Gradle / Ivy

There is a newer version: 5.10.9
Show newest version
package org.yamcs;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;

import org.yamcs.protobuf.YamcsInstance.InstanceState;

import com.google.common.util.concurrent.Monitor;
import com.google.common.util.concurrent.Monitor.Guard;

/**
 * Inspired from Guava services, this class offers the following states:
 * 
    *
  • OFFLINE
  • *
  • INITIALIZING
  • *
  • INITIALIZED
  • *
  • STARTING
  • *
  • RUNNING
  • *
  • STOPPING
  • *
  • FAILED
  • *
* * transitions are allowed back to OFFLINE from all steady states * * @author nm * */ public abstract class YamcsInstanceService { private final Monitor monitor = new Monitor(); private final Guard isInitializable = new IsInitializableGuard(); private final Guard isStartable = new IsStartableGuard(); private final Guard isStoppable = new IsStoppableGuard(); private final Guard hasReachedRunning = new HasReachedRunningGuard(); private final Guard hasReachedOffline = new HasReachedOfflineGuard(); private final Guard hasReachedInitialized = new HasReachedInitializedGuard(); private volatile StateSnapshot snapshot = new StateSnapshot(InstanceState.OFFLINE); private Set stateListeners = new CopyOnWriteArraySet<>(); protected abstract void doInit(); protected abstract void doStart(); protected abstract void doStop(); public final InstanceState state() { return snapshot.externalState(); } public final YamcsInstanceService initAsync() { if (monitor.enterIf(isInitializable)) { try { snapshot = new StateSnapshot(InstanceState.INITIALIZING); initializing(); doInit(); } catch (Throwable startupFailure) { notifyFailed(startupFailure); } finally { monitor.leave(); // executeListeners(); } } else { throw new IllegalStateException("Service " + this + " has already been started"); } return this; } public final YamcsInstanceService startAsync() { if (monitor.enterIf(isStartable)) { try { InstanceState previous = state(); if (previous == InstanceState.INITIALIZING) { snapshot = new StateSnapshot(InstanceState.STARTING, true, false, null); } else { snapshot = new StateSnapshot(InstanceState.STARTING); } starting(); doStart(); } catch (Throwable startupFailure) { notifyFailed(startupFailure); } finally { monitor.leave(); // executeListeners(); } } else { throw new IllegalStateException("Service " + this + " has already been started"); } return this; } public final YamcsInstanceService stopAsync() { if (monitor.enterIf(isStoppable)) { try { InstanceState previous = state(); switch (previous) { case INITIALIZED: snapshot = new StateSnapshot(InstanceState.OFFLINE); offline(InstanceState.INITIALIZED); break; case STARTING: case INITIALIZING: snapshot = new StateSnapshot(previous, false, true, null); stopping(previous); break; case RUNNING: snapshot = new StateSnapshot(InstanceState.STOPPING); stopping(InstanceState.RUNNING); doStop(); break; case OFFLINE: break; case FAILED: snapshot = new StateSnapshot(InstanceState.OFFLINE); break; case STOPPING: // These cases are impossible due to the if statement above. throw new AssertionError("isStoppable is incorrectly implemented, saw: " + previous); default: throw new AssertionError("Unexpected state: " + previous); } } catch (Throwable shutdownFailure) { notifyFailed(shutdownFailure); } finally { monitor.leave(); // executeListeners(); } } return this; } public final void awaitInitialized() { monitor.enterWhenUninterruptibly(hasReachedInitialized); try { checkCurrentState(InstanceState.INITIALIZED); } finally { monitor.leave(); } } public final void awaitRunning() { monitor.enterWhenUninterruptibly(hasReachedRunning); try { checkCurrentState(InstanceState.RUNNING); } finally { monitor.leave(); } } public final void awaitOffline() { monitor.enterWhenUninterruptibly(hasReachedOffline); try { checkCurrentState(InstanceState.OFFLINE); } finally { monitor.leave(); } } public void addStateListener(InstanceStateListener listener) { stateListeners.add(listener); } public void removeStateListener(InstanceStateListener listener) { stateListeners.remove(listener); } /** * Implementing classes should invoke this method once their service has been initialized. * * @throws IllegalStateException * if the service is not {@link InstanceState#STARTING}. */ protected final void notifyInitialized() { monitor.enter(); try { // We have to examine the internal state of the snapshot here to properly handle the stop // while starting case. if (snapshot.state != InstanceState.INITIALIZING) { IllegalStateException failure = new IllegalStateException( "Cannot notifyInitialized() when the service is " + snapshot.state); notifyFailed(failure); throw failure; } if (snapshot.shutdownWhenStartupOrInitFinishes) { snapshot = new StateSnapshot(InstanceState.STOPPING); // We don't call listeners here because we already did that when we set the // shutdownWhenStartupFinishes flag. doStop(); } else if (snapshot.runWhenInitFinishes) { snapshot = new StateSnapshot(InstanceState.STARTING); doStart(); } else { snapshot = new StateSnapshot(InstanceState.INITIALIZED); running(); } } finally { monitor.leave(); // executeListeners(); } } /** * Implementing classes should invoke this method once their service has started. It will cause the service to * transition from {@link InstanceState#STARTING} to {@link InstanceState#RUNNING}. * * @throws IllegalStateException * if the service is not {@link InstanceState#STARTING}. */ protected final void notifyStarted() { monitor.enter(); try { // We have to examine the internal state of the snapshot here to properly handle the stop // while starting case. if (snapshot.state != InstanceState.STARTING) { IllegalStateException failure = new IllegalStateException( "Cannot notifyStarted() when the service is " + snapshot.state); notifyFailed(failure); throw failure; } if (snapshot.shutdownWhenStartupOrInitFinishes) { snapshot = new StateSnapshot(InstanceState.STOPPING); // We don't call listeners here because we already did that when we set the // shutdownWhenStartupFinishes flag. doStop(); } else { snapshot = new StateSnapshot(InstanceState.RUNNING); running(); } } finally { monitor.leave(); // executeListeners(); } } private void running() { stateListeners.forEach(InstanceStateListener::running); } /** * Implementing classes should invoke this method once their service has stopped. It will cause the service to * transition from {@link InstanceState#STOPPING} to {@link InstanceState#OFFLINE}. * * @throws IllegalStateException * if the service is neither {@link InstanceState#STOPPING} nor {@link InstanceState#RUNNING}. */ protected final void notifyStopped() { monitor.enter(); try { // We check the internal state of the snapshot instead of state() directly so we don't allow // notifyStopped() to be called while STARTING, even if stop() has already been called. InstanceState previous = snapshot.state; if (previous != InstanceState.STOPPING && previous != InstanceState.RUNNING) { IllegalStateException failure = new IllegalStateException( "Cannot notifyStopped() when the service is " + previous); notifyFailed(failure); throw failure; } snapshot = new StateSnapshot(InstanceState.OFFLINE); offline(previous); } finally { monitor.leave(); // executeListeners(); } } private void checkCurrentState(InstanceState expected) { InstanceState actual = state(); if (actual != expected) { if (actual == InstanceState.FAILED) { // Handle this specially so that we can include the failureCause, if there is one. throw new IllegalStateException("Expected the service to be " + expected + ", but the service has FAILED", failureCause()); } throw new IllegalStateException("Expected the service to be " + expected + ", but was " + actual); } } public final Throwable failureCause() { return snapshot.failureCause(); } /** * Invoke this method to transition the service to the {@link InstanceState#FAILED}. The service will not be * stopped if it is running. Invoke this method when a service has failed critically or otherwise cannot be * started nor stopped. */ protected final void notifyFailed(Throwable cause) { checkNotNull(cause); monitor.enter(); try { InstanceState previous = state(); switch (previous) { case INITIALIZED: case OFFLINE: throw new IllegalStateException("Failed while in state:" + previous, cause); case RUNNING: case STARTING: case STOPPING: case INITIALIZING: snapshot = new StateSnapshot(InstanceState.FAILED, false, false, cause); failed(previous, cause); break; case FAILED: // Do nothing break; default: throw new AssertionError("Unexpected state: " + previous); } } finally { monitor.leave(); // executeListeners(); } } private void failed(InstanceState previous, Throwable cause) { stateListeners.forEach(l -> l.failed(cause)); } private void initializing() { stateListeners.forEach(InstanceStateListener::initializing); } private void starting() { stateListeners.forEach(InstanceStateListener::starting); } public void offline(InstanceState from) { stateListeners.forEach(InstanceStateListener::offline); } public void stopping(InstanceState from) { stateListeners.forEach(l -> l.stopping()); } /** * An immutable snapshot of the current state of the service. This class represents a consistent snapshot of the * state and therefore it can be used to answer simple queries without needing to grab a lock. */ private static final class StateSnapshot { /** * The internal state, which equals external state unless shutdownWhenStartupFinishes is true. */ final InstanceState state; /** * If true, the user requested a start while the service was still initializing up. */ final boolean runWhenInitFinishes; /** * If true, the user requested a shutdown while the service was still starting or initializing up. */ final boolean shutdownWhenStartupOrInitFinishes; /** * The exception that caused this service to fail. This will be {@code null} unless the service has failed. */ final Throwable failure; StateSnapshot(InstanceState internalState) { this(internalState, false, false, null); } StateSnapshot( InstanceState internalState, boolean runWhenInitFinishes, boolean shutdownWhenStartupFinishes, Throwable failure) { checkArgument(!shutdownWhenStartupFinishes || internalState == InstanceState.STARTING, "shudownWhenStartupFinishes can only be set if state is STARTING. Got %s instead.", internalState); checkArgument(!(failure != null ^ internalState == InstanceState.FAILED), "A failure cause should be set if and only if the state is failed. Got %s and %s " + "instead.", internalState, failure); this.state = internalState; this.shutdownWhenStartupOrInitFinishes = shutdownWhenStartupFinishes; this.runWhenInitFinishes = runWhenInitFinishes; this.failure = failure; } /** @see Service#state() */ InstanceState externalState() { if (shutdownWhenStartupOrInitFinishes && state == InstanceState.STARTING) { return InstanceState.STOPPING; } else { return state; } } /** @see Service#failureCause() */ Throwable failureCause() { checkState(state == InstanceState.FAILED, "failureCause() is only valid if the service has failed, service is %s", state); return failure; } } private final class IsStartableGuard extends Guard { IsStartableGuard() { super(monitor); } @Override public boolean isSatisfied() { return state().compareTo(InstanceState.INITIALIZED) <= 0; } } private final class IsStoppableGuard extends Guard { IsStoppableGuard() { super(monitor); } @Override public boolean isSatisfied() { InstanceState cs = state(); return cs.compareTo(InstanceState.RUNNING) <= 0 || cs == InstanceState.FAILED; } } private final class IsInitializableGuard extends Guard { IsInitializableGuard() { super(monitor); } @Override public boolean isSatisfied() { return state() == InstanceState.OFFLINE; } } private final class HasReachedRunningGuard extends Guard { HasReachedRunningGuard() { super(monitor); } @Override public boolean isSatisfied() { return state().compareTo(InstanceState.RUNNING) >= 0; } } private final class HasReachedOfflineGuard extends Guard { HasReachedOfflineGuard() { super(monitor); } @Override public boolean isSatisfied() { return state() == InstanceState.OFFLINE; } } private final class HasReachedInitializedGuard extends Guard { HasReachedInitializedGuard() { super(monitor); } @Override public boolean isSatisfied() { return state().compareTo(InstanceState.INITIALIZED) >= 0; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy