001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *       http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing, software
013 *  distributed under the License is distributed on an "AS IS" BASIS,
014 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 *  See the License for the specific language governing permissions and
016 *  limitations under the License.
017 */
018
019package org.apache.hadoop.service;
020
021import org.apache.hadoop.classification.InterfaceAudience.Public;
022import org.apache.hadoop.classification.InterfaceStability.Evolving;
023
024/**
025 * Implements the service state model.
026 */
027@Public
028@Evolving
029public class ServiceStateModel {
030
031  /**
032   * Map of all valid state transitions
033   * [current] [proposed1, proposed2, ...]
034   */
035  private static final boolean[][] statemap =
036    {
037      //                uninited inited started stopped
038      /* uninited  */    {false, true,  false,  true},
039      /* inited    */    {false, true,  true,   true},
040      /* started   */    {false, false, true,   true},
041      /* stopped   */    {false, false, false,  true},
042    };
043
044  /**
045   * The state of the service
046   */
047  private volatile Service.STATE state;
048
049  /**
050   * The name of the service: used in exceptions
051   */
052  private String name;
053
054  /**
055   * Create the service state model in the {@link Service.STATE#NOTINITED}
056   * state.
057   */
058  public ServiceStateModel(String name) {
059    this(name, Service.STATE.NOTINITED);
060  }
061
062  /**
063   * Create a service state model instance in the chosen state
064   * @param state the starting state
065   */
066  public ServiceStateModel(String name, Service.STATE state) {
067    this.state = state;
068    this.name = name;
069  }
070
071  /**
072   * Query the service state. This is a non-blocking operation.
073   * @return the state
074   */
075  public Service.STATE getState() {
076    return state;
077  }
078
079  /**
080   * Query that the state is in a specific state
081   * @param proposed proposed new state
082   * @return the state
083   */
084  public boolean isInState(Service.STATE proposed) {
085    return state.equals(proposed);
086  }
087
088  /**
089   * Verify that that a service is in a given state.
090   * @param expectedState the desired state
091   * @throws ServiceStateException if the service state is different from
092   * the desired state
093   */
094  public void ensureCurrentState(Service.STATE expectedState) {
095    if (state != expectedState) {
096      throw new ServiceStateException(name+ ": for this operation, the " +
097                                      "current service state must be "
098                                      + expectedState
099                                      + " instead of " + state);
100    }
101  }
102
103  /**
104   * Enter a state -thread safe.
105   *
106   * @param proposed proposed new state
107   * @return the original state
108   * @throws ServiceStateException if the transition is not permitted
109   */
110  public synchronized Service.STATE enterState(Service.STATE proposed) {
111    checkStateTransition(name, state, proposed);
112    Service.STATE oldState = state;
113    //atomic write of the new state
114    state = proposed;
115    return oldState;
116  }
117
118  /**
119   * Check that a state tansition is valid and
120   * throw an exception if not
121   * @param name name of the service (can be null)
122   * @param state current state
123   * @param proposed proposed new state
124   */
125  public static void checkStateTransition(String name,
126                                          Service.STATE state,
127                                          Service.STATE proposed) {
128    if (!isValidStateTransition(state, proposed)) {
129      throw new ServiceStateException(name + " cannot enter state "
130                                      + proposed + " from state " + state);
131    }
132  }
133
134  /**
135   * Is a state transition valid?
136   * There are no checks for current==proposed
137   * as that is considered a non-transition.
138   *
139   * using an array kills off all branch misprediction costs, at the expense
140   * of cache line misses.
141   *
142   * @param current current state
143   * @param proposed proposed new state
144   * @return true if the transition to a new state is valid
145   */
146  public static boolean isValidStateTransition(Service.STATE current,
147                                               Service.STATE proposed) {
148    boolean[] row = statemap[current.getValue()];
149    return row[proposed.getValue()];
150  }
151
152  /**
153   * return the state text as the toString() value
154   * @return the current state's description
155   */
156  @Override
157  public String toString() {
158    return (name.isEmpty() ? "" : ((name) + ": "))
159            + state.toString();
160  }
161
162}