View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.apache.hadoop.hbase.procedure2;
20  
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.OutputStream;
24  import java.util.ArrayList;
25  import java.util.Arrays;
26  
27  import org.apache.hadoop.hbase.classification.InterfaceAudience;
28  import org.apache.hadoop.hbase.classification.InterfaceStability;
29  import org.apache.hadoop.hbase.protobuf.generated.ProcedureProtos.StateMachineProcedureData;
30  
31  /**
32   * Procedure described by a series of steps.
33   *
34   * The procedure implementor must have an enum of 'states', describing
35   * the various step of the procedure.
36   * Once the procedure is running, the procedure-framework will call executeFromState()
37   * using the 'state' provided by the user. The first call to executeFromState()
38   * will be performed with 'state = null'. The implementor can jump between
39   * states using setNextState(MyStateEnum.ordinal()).
40   * The rollback will call rollbackState() for each state that was executed, in reverse order.
41   */
42  @InterfaceAudience.Private
43  @InterfaceStability.Evolving
44  public abstract class StateMachineProcedure<TEnvironment, TState>
45      extends Procedure<TEnvironment> {
46    private Flow stateFlow = Flow.HAS_MORE_STATE;
47    private int stateCount = 0;
48    private int[] states = null;
49  
50    private ArrayList<Procedure> subProcList = null;
51  
52    protected enum Flow {
53      HAS_MORE_STATE,
54      NO_MORE_STATE,
55    }
56  
57    /**
58     * called to perform a single step of the specified 'state' of the procedure
59     * @param state state to execute
60     * @return Flow.NO_MORE_STATE if the procedure is completed,
61     *         Flow.HAS_MORE_STATE if there is another step.
62     */
63    protected abstract Flow executeFromState(TEnvironment env, TState state)
64      throws ProcedureYieldException, InterruptedException;
65  
66    /**
67     * called to perform the rollback of the specified state
68     * @param state state to rollback
69     * @throws IOException temporary failure, the rollback will retry later
70     */
71    protected abstract void rollbackState(TEnvironment env, TState state)
72      throws IOException, InterruptedException;
73  
74    /**
75     * Convert an ordinal (or state id) to an Enum (or more descriptive) state object.
76     * @param stateId the ordinal() of the state enum (or state id)
77     * @return the state enum object
78     */
79    protected abstract TState getState(int stateId);
80  
81    /**
82     * Convert the Enum (or more descriptive) state object to an ordinal (or state id).
83     * @param state the state enum object
84     * @return stateId the ordinal() of the state enum (or state id)
85     */
86    protected abstract int getStateId(TState state);
87  
88    /**
89     * Return the initial state object that will be used for the first call to executeFromState().
90     * @return the initial state enum object
91     */
92    protected abstract TState getInitialState();
93  
94    /**
95     * Set the next state for the procedure.
96     * @param state the state enum object
97     */
98    protected void setNextState(final TState state) {
99      setNextState(getStateId(state));
100   }
101 
102   /**
103    * By default, the executor will try ro run all the steps of the procedure start to finish.
104    * Return true to make the executor yield between execution steps to
105    * give other procedures time to run their steps.
106    * @param state the state we are going to execute next.
107    * @return Return true if the executor should yield before the execution of the specified step.
108    *         Defaults to return false.
109    */
110   protected boolean isYieldBeforeExecuteFromState(TEnvironment env, TState state) {
111     return false;
112   }
113 
114   /**
115    * Add a child procedure to execute
116    * @param subProcedure the child procedure
117    */
118   protected void addChildProcedure(Procedure... subProcedure) {
119     if (subProcList == null) {
120       subProcList = new ArrayList<Procedure>(subProcedure.length);
121     }
122     for (int i = 0; i < subProcedure.length; ++i) {
123       subProcList.add(subProcedure[i]);
124     }
125   }
126 
127   @Override
128   protected Procedure[] execute(final TEnvironment env)
129       throws ProcedureYieldException, InterruptedException {
130     updateTimestamp();
131     try {
132       if (!hasMoreState()) return null;
133 
134       TState state = getCurrentState();
135       if (stateCount == 0) {
136         setNextState(getStateId(state));
137       }
138 
139       stateFlow = executeFromState(env, state);
140 
141       if (subProcList != null && subProcList.size() != 0) {
142         Procedure[] subProcedures = subProcList.toArray(new Procedure[subProcList.size()]);
143         subProcList = null;
144         return subProcedures;
145       }
146 
147       return (isWaiting() || isFailed() || !hasMoreState()) ? null : new Procedure[] {this};
148     } finally {
149       updateTimestamp();
150     }
151   }
152 
153   @Override
154   protected void rollback(final TEnvironment env)
155       throws IOException, InterruptedException {
156     try {
157       updateTimestamp();
158       rollbackState(env, getCurrentState());
159       stateCount--;
160     } finally {
161       updateTimestamp();
162     }
163   }
164 
165   @Override
166   protected boolean isYieldAfterExecutionStep(final TEnvironment env) {
167     return isYieldBeforeExecuteFromState(env, getCurrentState());
168   }
169 
170   private boolean hasMoreState() {
171     return stateFlow != Flow.NO_MORE_STATE;
172   }
173 
174   private TState getCurrentState() {
175     return stateCount > 0 ? getState(states[stateCount-1]) : getInitialState();
176   }
177 
178   /**
179    * Set the next state for the procedure.
180    * @param stateId the ordinal() of the state enum (or state id)
181    */
182   private void setNextState(final int stateId) {
183     if (states == null || states.length == stateCount) {
184       int newCapacity = stateCount + 8;
185       if (states != null) {
186         states = Arrays.copyOf(states, newCapacity);
187       } else {
188         states = new int[newCapacity];
189       }
190     }
191     states[stateCount++] = stateId;
192   }
193 
194   @Override
195   protected void toStringState(StringBuilder builder) {
196     super.toStringState(builder);
197     if (!isFinished() && getCurrentState() != null) {
198       builder.append(":").append(getCurrentState());
199     }
200   }
201 
202   @Override
203   protected void serializeStateData(final OutputStream stream) throws IOException {
204     StateMachineProcedureData.Builder data = StateMachineProcedureData.newBuilder();
205     for (int i = 0; i < stateCount; ++i) {
206       data.addState(states[i]);
207     }
208     data.build().writeDelimitedTo(stream);
209   }
210 
211   @Override
212   protected void deserializeStateData(final InputStream stream) throws IOException {
213     StateMachineProcedureData data = StateMachineProcedureData.parseDelimitedFrom(stream);
214     stateCount = data.getStateCount();
215     if (stateCount > 0) {
216       states = new int[stateCount];
217       for (int i = 0; i < stateCount; ++i) {
218         states[i] = data.getState(i);
219       }
220     } else {
221       states = null;
222     }
223   }
224 }