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.lang.reflect.Constructor;
25  import java.lang.reflect.Modifier;
26  import java.util.Arrays;
27  import java.util.List;
28  import java.util.Map;
29  
30  import org.apache.hadoop.hbase.ProcedureInfo;
31  import org.apache.hadoop.hbase.classification.InterfaceAudience;
32  import org.apache.hadoop.hbase.classification.InterfaceStability;
33  import org.apache.hadoop.hbase.exceptions.TimeoutIOException;
34  import org.apache.hadoop.hbase.procedure2.util.StringUtils;
35  import org.apache.hadoop.hbase.protobuf.generated.ProcedureProtos;
36  import org.apache.hadoop.hbase.protobuf.generated.ProcedureProtos.ProcedureState;
37  import org.apache.hadoop.hbase.util.ByteStringer;
38  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
39  
40  import com.google.common.annotations.VisibleForTesting;
41  import com.google.common.base.Preconditions;
42  import com.google.protobuf.ByteString;
43  
44  /**
45   * Base Procedure class responsible to handle the Procedure Metadata
46   * e.g. state, startTime, lastUpdate, stack-indexes, ...
47   *
48   * execute() is called each time the procedure is executed.
49   * it may be called multiple times in case of failure and restart, so the
50   * code must be idempotent.
51   * the return is a set of sub-procedures or null in case the procedure doesn't
52   * have sub-procedures. Once the sub-procedures are successfully completed
53   * the execute() method is called again, you should think at it as a stack:
54   *  -> step 1
55   *  ---> step 2
56   *  -> step 1
57   *
58   * rollback() is called when the procedure or one of the sub-procedures is failed.
59   * the rollback step is supposed to cleanup the resources created during the
60   * execute() step. in case of failure and restart rollback() may be called
61   * multiple times, so the code must be idempotent.
62   */
63  @InterfaceAudience.Private
64  @InterfaceStability.Evolving
65  public abstract class Procedure<TEnvironment> implements Comparable<Procedure> {
66    // unchanged after initialization
67    private String owner = null;
68    private Long parentProcId = null;
69    private Long procId = null;
70    private long startTime;
71  
72    // runtime state, updated every operation
73    private ProcedureState state = ProcedureState.INITIALIZING;
74    private Integer timeout = null;
75    private int[] stackIndexes = null;
76    private int childrenLatch = 0;
77    private long lastUpdate;
78  
79    private RemoteProcedureException exception = null;
80    private byte[] result = null;
81  
82    /**
83     * The main code of the procedure. It must be idempotent since execute()
84     * may be called multiple time in case of machine failure in the middle
85     * of the execution.
86     * @return a set of sub-procedures or null if there is nothing else to execute.
87     */
88    protected abstract Procedure[] execute(TEnvironment env)
89      throws ProcedureYieldException;
90  
91    /**
92     * The code to undo what done by the execute() code.
93     * It is called when the procedure or one of the sub-procedure failed or an
94     * abort was requested. It should cleanup all the resources created by
95     * the execute() call. The implementation must be idempotent since rollback()
96     * may be called multiple time in case of machine failure in the middle
97     * of the execution.
98     * @throws IOException temporary failure, the rollback will retry later
99     */
100   protected abstract void rollback(TEnvironment env)
101     throws IOException;
102 
103   /**
104    * The abort() call is asynchronous and each procedure must decide how to deal
105    * with that, if they want to be abortable. The simplest implementation
106    * is to have an AtomicBoolean set in the abort() method and then the execute()
107    * will check if the abort flag is set or not.
108    * abort() may be called multiple times from the client, so the implementation
109    * must be idempotent.
110    *
111    * NOTE: abort() is not like Thread.interrupt() it is just a notification
112    * that allows the procedure implementor where to abort to avoid leak and
113    * have a better control on what was executed and what not.
114    */
115   protected abstract boolean abort(TEnvironment env);
116 
117   /**
118    * The user-level code of the procedure may have some state to
119    * persist (e.g. input arguments) to be able to resume on failure.
120    * @param stream the stream that will contain the user serialized data
121    */
122   protected abstract void serializeStateData(final OutputStream stream)
123     throws IOException;
124 
125   /**
126    * Called on store load to allow the user to decode the previously serialized
127    * state.
128    * @param stream the stream that contains the user serialized data
129    */
130   protected abstract void deserializeStateData(final InputStream stream)
131     throws IOException;
132 
133   /**
134    * The user should override this method, and try to take a lock if necessary.
135    * A lock can be anything, and it is up to the implementor.
136    * Example: in our Master we can execute request in parallel for different tables
137    *          create t1 and create t2 can be executed at the same time.
138    *          anything else on t1/t2 is queued waiting that specific table create to happen.
139    *
140    * @return true if the lock was acquired and false otherwise
141    */
142   protected boolean acquireLock(final TEnvironment env) {
143     return true;
144   }
145 
146   /**
147    * The user should override this method, and release lock if necessary.
148    */
149   protected void releaseLock(final TEnvironment env) {
150     // no-op
151   }
152 
153   /**
154    * Called when the procedure is loaded for replay.
155    * The procedure implementor may use this method to perform some quick
156    * operation before replay.
157    * e.g. failing the procedure if the state on replay may be unknown.
158    */
159   protected void beforeReplay(final TEnvironment env) {
160     // no-op
161   }
162 
163   /**
164    * Called when the procedure is marked as completed (success or rollback).
165    * The procedure implementor may use this method to cleanup in-memory states.
166    * This operation will not be retried on failure.
167    */
168   protected void completionCleanup(final TEnvironment env) {
169     // no-op
170   }
171 
172   @Override
173   public String toString() {
174     // Return the simple String presentation of the procedure.
175     return toStringSimpleSB().toString();
176   }
177 
178   /**
179    * Build the StringBuilder for the simple form of
180    * procedure string.
181    * @return the StringBuilder
182    */
183   protected StringBuilder toStringSimpleSB() {
184     StringBuilder sb = new StringBuilder();
185     toStringClassDetails(sb);
186 
187     if (procId != null) {
188       sb.append(" id=");
189       sb.append(getProcId());
190     }
191 
192     if (hasParent()) {
193       sb.append(" parent=");
194       sb.append(getParentProcId());
195     }
196 
197     if (hasOwner()) {
198       sb.append(" owner=");
199       sb.append(getOwner());
200     }
201 
202     sb.append(" state=");
203     sb.append(getState());
204 
205     return sb;
206   }
207 
208   /**
209    * Extend the toString() information with more procedure
210    * details
211    */
212   public String toStringDetails() {
213     StringBuilder sb = toStringSimpleSB();
214 
215     sb.append(" startTime=");
216     sb.append(getStartTime());
217 
218     sb.append(" lastUpdate=");
219     sb.append(getLastUpdate());
220 
221     if (stackIndexes != null) {
222       sb.append("\n");
223       sb.append("stackIndexes=");
224       sb.append(Arrays.toString(getStackIndexes()));
225     }
226 
227     return sb.toString();
228   }
229 
230   protected String toStringClass() {
231     StringBuilder sb = new StringBuilder();
232     toStringClassDetails(sb);
233 
234     return sb.toString();
235   }
236 
237   /**
238    * Extend the toString() information with the procedure details
239    * e.g. className and parameters
240    * @param builder the string builder to use to append the proc specific information
241    */
242   protected void toStringClassDetails(StringBuilder builder) {
243     builder.append(getClass().getName());
244   }
245 
246   /**
247    * @return the serialized result if any, otherwise null
248    */
249   public byte[] getResult() {
250     return result;
251   }
252 
253   /**
254    * The procedure may leave a "result" on completion.
255    * @param result the serialized result that will be passed to the client
256    */
257   protected void setResult(final byte[] result) {
258     this.result = result;
259   }
260 
261   public long getProcId() {
262     return procId;
263   }
264 
265   public boolean hasParent() {
266     return parentProcId != null;
267   }
268 
269   public boolean hasException() {
270     return exception != null;
271   }
272 
273   public boolean hasTimeout() {
274     return timeout != null;
275   }
276 
277   public long getParentProcId() {
278     return parentProcId;
279   }
280 
281   /**
282    * @return true if the procedure has failed.
283    *         true may mean failed but not yet rolledback or failed and rolledback.
284    */
285   public synchronized boolean isFailed() {
286     return exception != null || state == ProcedureState.ROLLEDBACK;
287   }
288 
289   /**
290    * @return true if the procedure is finished successfully.
291    */
292   public synchronized boolean isSuccess() {
293     return state == ProcedureState.FINISHED && exception == null;
294   }
295 
296   /**
297    * @return true if the procedure is finished. The Procedure may be completed
298    *         successfuly or failed and rolledback.
299    */
300   public synchronized boolean isFinished() {
301     switch (state) {
302       case ROLLEDBACK:
303         return true;
304       case FINISHED:
305         return exception == null;
306       default:
307         break;
308     }
309     return false;
310   }
311 
312   /**
313    * @return true if the procedure is waiting for a child to finish or for an external event.
314    */
315   public synchronized boolean isWaiting() {
316     switch (state) {
317       case WAITING:
318       case WAITING_TIMEOUT:
319         return true;
320       default:
321         break;
322     }
323     return false;
324   }
325 
326   public synchronized RemoteProcedureException getException() {
327     return exception;
328   }
329 
330   public long getStartTime() {
331     return startTime;
332   }
333 
334   public synchronized long getLastUpdate() {
335     return lastUpdate;
336   }
337 
338   public synchronized long elapsedTime() {
339     return lastUpdate - startTime;
340   }
341 
342   /**
343    * @param timeout timeout in msec
344    */
345   protected void setTimeout(final int timeout) {
346     this.timeout = timeout;
347   }
348 
349   /**
350    * @return the timeout in msec
351    */
352   public int getTimeout() {
353     return timeout;
354   }
355 
356   /**
357    * @return the remaining time before the timeout
358    */
359   public long getTimeRemaining() {
360     return Math.max(0, timeout - (EnvironmentEdgeManager.currentTime() - startTime));
361   }
362 
363   @VisibleForTesting
364   @InterfaceAudience.Private
365   public void setOwner(final String owner) {
366     this.owner = StringUtils.isEmpty(owner) ? null : owner;
367   }
368 
369   public String getOwner() {
370     return owner;
371   }
372 
373   public boolean hasOwner() {
374     return owner != null;
375   }
376 
377   @VisibleForTesting
378   @InterfaceAudience.Private
379   protected synchronized void setState(final ProcedureState state) {
380     this.state = state;
381     updateTimestamp();
382   }
383 
384   @InterfaceAudience.Private
385   protected synchronized ProcedureState getState() {
386     return state;
387   }
388 
389   protected void setFailure(final String source, final Throwable cause) {
390     setFailure(new RemoteProcedureException(source, cause));
391   }
392 
393   protected synchronized void setFailure(final RemoteProcedureException exception) {
394     this.exception = exception;
395     if (!isFinished()) {
396       setState(ProcedureState.FINISHED);
397     }
398   }
399 
400   protected void setAbortFailure(final String source, final String msg) {
401     setFailure(source, new ProcedureAbortedException(msg));
402   }
403 
404   @InterfaceAudience.Private
405   protected synchronized boolean setTimeoutFailure() {
406     if (state == ProcedureState.WAITING_TIMEOUT) {
407       long timeDiff = EnvironmentEdgeManager.currentTime() - lastUpdate;
408       setFailure("ProcedureExecutor", new TimeoutIOException(
409         "Operation timed out after " + StringUtils.humanTimeDiff(timeDiff)));
410       return true;
411     }
412     return false;
413   }
414 
415   /**
416    * Called by the ProcedureExecutor to assign the ID to the newly created procedure.
417    */
418   @VisibleForTesting
419   @InterfaceAudience.Private
420   protected void setProcId(final long procId) {
421     this.procId = procId;
422     this.startTime = EnvironmentEdgeManager.currentTime();
423     setState(ProcedureState.RUNNABLE);
424   }
425 
426   /**
427    * Called by the ProcedureExecutor to assign the parent to the newly created procedure.
428    */
429   @InterfaceAudience.Private
430   protected void setParentProcId(final long parentProcId) {
431     this.parentProcId = parentProcId;
432   }
433 
434   /**
435    * Internal method called by the ProcedureExecutor that starts the
436    * user-level code execute().
437    */
438   @InterfaceAudience.Private
439   protected Procedure[] doExecute(final TEnvironment env)
440       throws ProcedureYieldException {
441     try {
442       updateTimestamp();
443       return execute(env);
444     } finally {
445       updateTimestamp();
446     }
447   }
448 
449   /**
450    * Internal method called by the ProcedureExecutor that starts the
451    * user-level code rollback().
452    */
453   @InterfaceAudience.Private
454   protected void doRollback(final TEnvironment env) throws IOException {
455     try {
456       updateTimestamp();
457       rollback(env);
458     } finally {
459       updateTimestamp();
460     }
461   }
462 
463   /**
464    * Called on store load to initialize the Procedure internals after
465    * the creation/deserialization.
466    */
467   @InterfaceAudience.Private
468   protected void setStartTime(final long startTime) {
469     this.startTime = startTime;
470   }
471 
472   /**
473    * Called on store load to initialize the Procedure internals after
474    * the creation/deserialization.
475    */
476   private synchronized void setLastUpdate(final long lastUpdate) {
477     this.lastUpdate = lastUpdate;
478   }
479 
480   protected synchronized void updateTimestamp() {
481     this.lastUpdate = EnvironmentEdgeManager.currentTime();
482   }
483 
484   /**
485    * Called by the ProcedureExecutor on procedure-load to restore the latch state
486    */
487   @InterfaceAudience.Private
488   protected synchronized void setChildrenLatch(final int numChildren) {
489     this.childrenLatch = numChildren;
490   }
491 
492   /**
493    * Called by the ProcedureExecutor on procedure-load to restore the latch state
494    */
495   @InterfaceAudience.Private
496   protected synchronized void incChildrenLatch() {
497     // TODO: can this be inferred from the stack? I think so...
498     this.childrenLatch++;
499   }
500 
501   /**
502    * Called by the ProcedureExecutor to notify that one of the sub-procedures
503    * has completed.
504    */
505   @InterfaceAudience.Private
506   protected synchronized boolean childrenCountDown() {
507     assert childrenLatch > 0;
508     return --childrenLatch == 0;
509   }
510 
511   /**
512    * Called by the RootProcedureState on procedure execution.
513    * Each procedure store its stack-index positions.
514    */
515   @InterfaceAudience.Private
516   protected synchronized void addStackIndex(final int index) {
517     if (stackIndexes == null) {
518       stackIndexes = new int[] { index };
519     } else {
520       int count = stackIndexes.length;
521       stackIndexes = Arrays.copyOf(stackIndexes, count + 1);
522       stackIndexes[count] = index;
523     }
524   }
525 
526   @InterfaceAudience.Private
527   protected synchronized boolean removeStackIndex() {
528     if (stackIndexes.length > 1) {
529       stackIndexes = Arrays.copyOf(stackIndexes, stackIndexes.length - 1);
530       return false;
531     } else {
532       stackIndexes = null;
533       return true;
534     }
535   }
536 
537   /**
538    * Called on store load to initialize the Procedure internals after
539    * the creation/deserialization.
540    */
541   @InterfaceAudience.Private
542   protected synchronized void setStackIndexes(final List<Integer> stackIndexes) {
543     this.stackIndexes = new int[stackIndexes.size()];
544     for (int i = 0; i < this.stackIndexes.length; ++i) {
545       this.stackIndexes[i] = stackIndexes.get(i);
546     }
547   }
548 
549   @InterfaceAudience.Private
550   protected synchronized boolean wasExecuted() {
551     return stackIndexes != null;
552   }
553 
554   @InterfaceAudience.Private
555   protected synchronized int[] getStackIndexes() {
556     return stackIndexes;
557   }
558 
559   @Override
560   public int compareTo(final Procedure other) {
561     long diff = getProcId() - other.getProcId();
562     return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
563   }
564 
565   /*
566    * Helper to lookup the root Procedure ID given a specified procedure.
567    */
568   @InterfaceAudience.Private
569   protected static Long getRootProcedureId(final Map<Long, Procedure> procedures, Procedure proc) {
570     while (proc.hasParent()) {
571       proc = procedures.get(proc.getParentProcId());
572       if (proc == null) return null;
573     }
574     return proc.getProcId();
575   }
576 
577   protected static Procedure newInstance(final String className) throws IOException {
578     try {
579       Class<?> clazz = Class.forName(className);
580       if (!Modifier.isPublic(clazz.getModifiers())) {
581         throw new Exception("the " + clazz + " class is not public");
582       }
583 
584       Constructor<?> ctor = clazz.getConstructor();
585       assert ctor != null : "no constructor found";
586       if (!Modifier.isPublic(ctor.getModifiers())) {
587         throw new Exception("the " + clazz + " constructor is not public");
588       }
589       return (Procedure)ctor.newInstance();
590     } catch (Exception e) {
591       throw new IOException("The procedure class " + className +
592           " must be accessible and have an empty constructor", e);
593     }
594   }
595 
596   protected static void validateClass(final Procedure proc) throws IOException {
597     try {
598       Class<?> clazz = proc.getClass();
599       if (!Modifier.isPublic(clazz.getModifiers())) {
600         throw new Exception("the " + clazz + " class is not public");
601       }
602 
603       Constructor<?> ctor = clazz.getConstructor();
604       assert ctor != null;
605       if (!Modifier.isPublic(ctor.getModifiers())) {
606         throw new Exception("the " + clazz + " constructor is not public");
607       }
608     } catch (Exception e) {
609       throw new IOException("The procedure class " + proc.getClass().getName() +
610           " must be accessible and have an empty constructor", e);
611     }
612   }
613 
614   /**
615    * Helper to create the ProcedureInfo from Procedure.
616    */
617   @InterfaceAudience.Private
618   public static ProcedureInfo createProcedureInfo(final Procedure proc) {
619     RemoteProcedureException exception = proc.hasException() ? proc.getException() : null;
620     return new ProcedureInfo(
621       proc.getProcId(),
622       proc.toStringClass(),
623       proc.getOwner(),
624       proc.getState(),
625       proc.hasParent() ? proc.getParentProcId() : -1,
626       exception != null ?
627         RemoteProcedureException.toProto(exception.getSource(), exception.getCause()) : null,
628       proc.getLastUpdate(),
629       proc.getStartTime(),
630       proc.getResult());
631   }
632 
633   /**
634    * Helper to convert the procedure to protobuf.
635    * Used by ProcedureStore implementations.
636    */
637   @InterfaceAudience.Private
638   public static ProcedureProtos.Procedure convert(final Procedure proc)
639       throws IOException {
640     Preconditions.checkArgument(proc != null);
641     validateClass(proc);
642 
643     ProcedureProtos.Procedure.Builder builder = ProcedureProtos.Procedure.newBuilder()
644       .setClassName(proc.getClass().getName())
645       .setProcId(proc.getProcId())
646       .setState(proc.getState())
647       .setStartTime(proc.getStartTime())
648       .setLastUpdate(proc.getLastUpdate());
649 
650     if (proc.hasParent()) {
651       builder.setParentId(proc.getParentProcId());
652     }
653 
654     if (proc.hasTimeout()) {
655       builder.setTimeout(proc.getTimeout());
656     }
657 
658     if (proc.hasOwner()) {
659       builder.setOwner(proc.getOwner());
660     }
661 
662     int[] stackIds = proc.getStackIndexes();
663     if (stackIds != null) {
664       for (int i = 0; i < stackIds.length; ++i) {
665         builder.addStackId(stackIds[i]);
666       }
667     }
668 
669     if (proc.hasException()) {
670       RemoteProcedureException exception = proc.getException();
671       builder.setException(
672         RemoteProcedureException.toProto(exception.getSource(), exception.getCause()));
673     }
674 
675     byte[] result = proc.getResult();
676     if (result != null) {
677       builder.setResult(ByteStringer.wrap(result));
678     }
679 
680     ByteString.Output stateStream = ByteString.newOutput();
681     proc.serializeStateData(stateStream);
682     if (stateStream.size() > 0) {
683       builder.setStateData(stateStream.toByteString());
684     }
685 
686     return builder.build();
687   }
688 
689   /**
690    * Helper to convert the protobuf procedure.
691    * Used by ProcedureStore implementations.
692    *
693    * TODO: OPTIMIZATION: some of the field never change during the execution
694    *                     (e.g. className, procId, parentId, ...).
695    *                     We can split in 'data' and 'state', and the store
696    *                     may take advantage of it by storing the data only on insert().
697    */
698   @InterfaceAudience.Private
699   public static Procedure convert(final ProcedureProtos.Procedure proto)
700       throws IOException {
701     // Procedure from class name
702     Procedure proc = Procedure.newInstance(proto.getClassName());
703 
704     // set fields
705     proc.setProcId(proto.getProcId());
706     proc.setState(proto.getState());
707     proc.setStartTime(proto.getStartTime());
708     proc.setLastUpdate(proto.getLastUpdate());
709 
710     if (proto.hasParentId()) {
711       proc.setParentProcId(proto.getParentId());
712     }
713 
714     if (proto.hasOwner()) {
715       proc.setOwner(proto.getOwner());
716     }
717 
718     if (proto.hasTimeout()) {
719       proc.setTimeout(proto.getTimeout());
720     }
721 
722     if (proto.getStackIdCount() > 0) {
723       proc.setStackIndexes(proto.getStackIdList());
724     }
725 
726     if (proto.hasException()) {
727       assert proc.getState() == ProcedureState.FINISHED ||
728              proc.getState() == ProcedureState.ROLLEDBACK :
729              "The procedure must be failed (waiting to rollback) or rolledback";
730       proc.setFailure(RemoteProcedureException.fromProto(proto.getException()));
731     }
732 
733     if (proto.hasResult()) {
734       proc.setResult(proto.getResult().toByteArray());
735     }
736 
737     // we want to call deserialize even when the stream is empty, mainly for testing.
738     proc.deserializeStateData(proto.getStateData().newInput());
739 
740     return proc;
741   }
742 }