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
019 package org.apache.hadoop.service;
020
021 import org.apache.hadoop.classification.InterfaceAudience.Public;
022 import org.apache.hadoop.classification.InterfaceStability.Evolving;
023
024 /**
025 * Implements the service state model.
026 */
027 @Public
028 @Evolving
029 public 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 }