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

org.apache.ratis.util.LifeCycle Maven / Gradle / Ivy

There is a newer version: 3.1.2
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.ratis.util;

import org.apache.ratis.util.function.CheckedRunnable;
import org.apache.ratis.util.function.CheckedSupplier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;
import java.util.Collections;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;
import java.util.function.UnaryOperator;

/**
 * The life cycle of a machine.
 * 
 *   -------------------------------------------------
 *  |        --------------------------------         |
 *  |       |     ------------------------   |        |
 *  |       |    |                        |  |        |
 *  |       |  PAUSED <---- PAUSING----   |  |        |
 *  |       |    |          ^     |    |  |  |        |
 *  |       |    V          |     |    V  V  V        V
 * NEW --> STARTING --> RUNNING --|--> CLOSING --> [CLOSED]
 *  ^       |    |          |     |       ^
 *  |       |    |          V     V       |
 *   -------      -------> EXCEPTION -----
 * 
*/ public class LifeCycle { public static final Logger LOG = LoggerFactory.getLogger(LifeCycle.class); /** The states in the life cycle. */ public enum State { /** The machine is newly created and holds zero resource. */ NEW, /** The machine is starting and does not yet provide any service. */ STARTING, /** The machine is running and providing service. */ RUNNING, /** The machine is pausing and stopping providing service. */ PAUSING, /** The machine is paused and does not provide any service. */ PAUSED, /** The machine catches an internal exception so that it must be closed. */ EXCEPTION, /** The machine is closing, stopping providing service and releasing resources. */ CLOSING, /** The machine is closed, a final state. */ CLOSED; private static final Map> PREDECESSORS; /** Is this {@link State#RUNNING}? */ public boolean isRunning() { return this == RUNNING; } /** Is this {@link State#CLOSING} or {@link State#CLOSED}? */ public boolean isClosingOrClosed() { return States.CLOSING_OR_CLOSED.contains(this); } /** Is this {@link State#PAUSING} or {@link State#PAUSED}? */ public boolean isPausingOrPaused() { return States.PAUSING_OR_PAUSED.contains(this); } static void put(State key, Map> map, State... values) { map.put(key, Collections.unmodifiableList(Arrays.asList(values))); } static { final Map> predecessors = new EnumMap<>(State.class); put(NEW, predecessors, STARTING); put(STARTING, predecessors, NEW, PAUSED); put(RUNNING, predecessors, STARTING); put(PAUSING, predecessors, RUNNING); put(PAUSED, predecessors, PAUSING); put(EXCEPTION, predecessors, STARTING, PAUSING, RUNNING); put(CLOSING, predecessors, STARTING, RUNNING, PAUSING, PAUSED, EXCEPTION); put(CLOSED, predecessors, NEW, CLOSING); PREDECESSORS = Collections.unmodifiableMap(predecessors); } /** Is the given transition valid? */ public static boolean isValid(State from, State to) { return PREDECESSORS.get(to).contains(from); } /** Validate the given transition. */ static void validate(Object name, State from, State to) { LOG.debug("{}: {} -> {}", name, from, to); if (LOG.isTraceEnabled()) { LOG.trace("TRACE", new Throwable()); } Preconditions.assertTrue(isValid(from, to), "ILLEGAL TRANSITION: In %s, %s -> %s", name, from, to); } } public static final class States { public static final Set RUNNING = Collections.unmodifiableSet(EnumSet.of(State.RUNNING)); public static final Set STARTING_OR_RUNNING = Collections.unmodifiableSet(EnumSet.of(State.STARTING, State.RUNNING)); public static final Set CLOSING_OR_CLOSED = Collections.unmodifiableSet(EnumSet.of(State.CLOSING, State.CLOSED)); public static final Set PAUSING_OR_PAUSED = Collections.unmodifiableSet(EnumSet.of(State.PAUSING, State.PAUSED)); public static final Set CLOSING_OR_CLOSED_OR_EXCEPTION = Collections.unmodifiableSet(EnumSet.of(State.CLOSING, State.CLOSED, State.EXCEPTION)); private States() { // no instances } } private volatile String name; private final AtomicReference current = new AtomicReference<>(State.NEW); public LifeCycle(Object name) { this.name = name.toString(); LOG.debug("{}: {}", name, current); } public void setName(String name) { this.name = name; } /** Transition from the current state to the given state. */ public void transition(final State to) { current.updateAndGet(from -> { State.validate(name, from, to); return to; }); } /** Transition from the current state to the given state if the current state is not equal to the given state. */ public void transitionIfNotEqual(final State to) { current.updateAndGet(from -> { if (from != to) { State.validate(name, from, to); } return to; }); } /** * Transition from the current state to the given state only if the transition is valid. * If the transition is invalid, this is a no-op. * * @return true if the updated state equals to the given state. */ public boolean transitionIfValid(final State to) { final State updated = current.updateAndGet(from -> State.isValid(from, to)? to : from); return updated == to; } /** * Transition using the given operator. * * @return the updated state if there is a transition; * otherwise, return null to indicate no state change. */ public State transition(UnaryOperator operator) { for(;;) { final State previous = current.get(); final State applied = operator.apply(previous); if (previous == applied) { return null; // no change required } State.validate(name, previous, applied); if (current.compareAndSet(previous, applied)) { return applied; } // state has been changed, retry } } /** * Transition using the given operator. * * @return the updated state. */ public State transitionAndGet(UnaryOperator operator) { return current.updateAndGet(previous -> { final State applied = operator.apply(previous); if (applied != previous) { State.validate(name, previous, applied); } return applied; }); } /** * If the current state is equal to the specified from state, * then transition to the give to state; otherwise, make no change. * * @return true iff the current state is equal to the specified from state. */ public boolean compareAndTransition(final State from, final State to) { final State previous = current.getAndUpdate(state -> { if (state != from) { return state; } State.validate(name, from, to); return to; }); return previous == from; } /** @return the current state. */ public State getCurrentState() { return current.get(); } /** Assert if the current state equals to one of the expected states. */ public void assertCurrentState(Set expected) { assertCurrentState((n, c) -> new IllegalStateException("STATE MISMATCHED: In " + n + ", current state " + c + " is not one of the expected states " + expected), expected); } /** Assert if the current state equals to one of the expected states. */ public State assertCurrentState( BiFunction newThrowable, Set expected) throws T { final State c = getCurrentState(); if (!expected.contains(c)) { throw newThrowable.apply(name, c); } return c; } @Override public String toString() { return name + ":" + getCurrentState(); } /** Run the given start method and transition the current state accordingly. */ @SafeVarargs public final void startAndTransition( CheckedRunnable startImpl, Class... exceptionClasses) throws T { transition(State.STARTING); try { startImpl.run(); transition(State.RUNNING); } catch (Throwable t) { transition(ReflectionUtils.isInstance(t, exceptionClasses)? State.NEW: State.EXCEPTION); throw t; } } /** * Check the current state and, if applicable, transit to {@link State#CLOSING}. * If this is already in {@link State#CLOSING} or {@link State#CLOSED}, * then invoking this method has no effect. * In other words, this method can be safely called multiple times. */ public State checkStateAndClose() { return checkStateAndClose(() -> State.CLOSING); } /** * Check the current state and, if applicable, run the given close method. * If this is already in {@link State#CLOSING} or {@link State#CLOSED}, * then invoking this method has no effect. * In other words, this method can be safely called multiple times * while the given close method will only be executed at most once. */ public State checkStateAndClose(CheckedRunnable closeMethod) throws T { return checkStateAndClose(() -> { try { closeMethod.run(); } finally { transition(State.CLOSED); } return State.CLOSED; }); } private State checkStateAndClose(CheckedSupplier closeMethod) throws T { if (compareAndTransition(State.NEW, State.CLOSED)) { return State.CLOSED; } for(;;) { final State c = getCurrentState(); if (c.isClosingOrClosed()) { return c; //already closing or closed. } if (compareAndTransition(c, State.CLOSING)) { return closeMethod.get(); } // lifecycle state is changed, retry. } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy