View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase;
20  
21  import java.text.MessageFormat;
22  
23  import junit.framework.Assert;
24  
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  import org.apache.hadoop.conf.Configuration;
28  import org.apache.hadoop.hbase.classification.InterfaceAudience;
29  
30  /**
31   * A class that provides a standard waitFor pattern
32   * See details at https://issues.apache.org/jira/browse/HBASE-7384
33   */
34  @InterfaceAudience.Private
35  public final class Waiter {
36    private static final Log LOG = LogFactory.getLog(Waiter.class);
37  
38    /**
39     * System property name whose value is a scale factor to increase time out values dynamically used
40     * in {@link #sleep(Configuration, long)}, {@link #waitFor(Configuration, long, Predicate)},
41     * {@link #waitFor(Configuration, long, long, Predicate)}, and
42     * {@link #waitFor(Configuration, long, long, boolean, Predicate)} method
43     * <p/>
44     * The actual time out value will equal to hbase.test.wait.for.ratio * passed-in timeout
45     */
46    public static final String HBASE_TEST_WAIT_FOR_RATIO = "hbase.test.wait.for.ratio";
47  
48    private static float HBASE_WAIT_FOR_RATIO_DEFAULT = 1;
49  
50    private static float waitForRatio = -1;
51  
52    private Waiter() {
53    }
54  
55    /**
56     * Returns the 'wait for ratio' used in the {@link #sleep(Configuration, long)},
57     * {@link #waitFor(Configuration, long, Predicate)},
58     * {@link #waitFor(Configuration, long, long, Predicate)} and
59     * {@link #waitFor(Configuration, long, long, boolean, Predicate)} methods of the class
60     * <p/>
61     * This is useful to dynamically adjust max time out values when same test cases run in different
62     * test machine settings without recompiling & re-deploying code.
63     * <p/>
64     * The value is obtained from the Java System property or configuration setting
65     * <code>hbase.test.wait.for.ratio</code> which defaults to <code>1</code>.
66     * @param conf the configuration
67     * @return the 'wait for ratio' for the current test run.
68     */
69    public static float getWaitForRatio(Configuration conf) {
70      if (waitForRatio < 0) {
71        // System property takes precedence over configuration setting
72        if (System.getProperty(HBASE_TEST_WAIT_FOR_RATIO) != null) {
73          waitForRatio = Float.parseFloat(System.getProperty(HBASE_TEST_WAIT_FOR_RATIO));
74        } else {
75          waitForRatio = conf.getFloat(HBASE_TEST_WAIT_FOR_RATIO, HBASE_WAIT_FOR_RATIO_DEFAULT);
76        }
77      }
78      return waitForRatio;
79    }
80  
81    /**
82     * A predicate 'closure' used by the {@link Waiter#waitFor(Configuration, long, Predicate)} and
83     * {@link Waiter#waitFor(Configuration, long, Predicate)} and
84     * {@link Waiter#waitFor(Configuration, long, long, boolean, Predicate)} methods.
85     */
86    @InterfaceAudience.Private
87    public interface Predicate<E extends Exception> {
88      /**
89       * Perform a predicate evaluation.
90       * @return the boolean result of the evaluation.
91       * @throws E thrown if the predicate evaluation could not evaluate.
92       */
93      boolean evaluate() throws E;
94    }
95  
96    /**
97     * A mixin interface, can be used with {@link Waiter} to explain failed state.
98     */
99    @InterfaceAudience.Private
100   public interface ExplainingPredicate<E extends Exception> extends Predicate<E> {
101     /**
102      * Perform a predicate evaluation.
103      *
104      * @return explanation of failed state
105      */
106     String explainFailure() throws E;
107   }
108 
109   /**
110    * Makes the current thread sleep for the duration equal to the specified time in milliseconds
111    * multiplied by the {@link #getWaitForRatio(Configuration)}.
112    * @param conf the configuration
113    * @param time the number of milliseconds to sleep.
114    */
115   public static void sleep(Configuration conf, long time) {
116     try {
117       Thread.sleep((long) (getWaitForRatio(conf) * time));
118     } catch (InterruptedException ex) {
119       LOG.warn(MessageFormat.format("Sleep interrupted, {0}", ex.toString()));
120     }
121   }
122 
123   /**
124    * Waits up to the duration equal to the specified timeout multiplied by the
125    * {@link #getWaitForRatio(Configuration)} for the given {@link Predicate} to become
126    * <code>true</code>, failing the test if the timeout is reached and the Predicate is still
127    * <code>false</code>.
128    * <p/>
129    * @param conf the configuration
130    * @param timeout the timeout in milliseconds to wait for the predicate.
131    * @param predicate the predicate to evaluate.
132    * @return the effective wait, in milli-seconds until the predicate becomes <code>true</code> or
133    *         wait is interrupted otherwise <code>-1</code> when times out
134    */
135   public static <E extends Exception> long waitFor(Configuration conf, long timeout,
136       Predicate<E> predicate) {
137     return waitFor(conf, timeout, 100, true, predicate);
138   }
139 
140   /**
141    * Waits up to the duration equal to the specified timeout multiplied by the
142    * {@link #getWaitForRatio(Configuration)} for the given {@link Predicate} to become
143    * <code>true</code>, failing the test if the timeout is reached and the Predicate is still
144    * <code>false</code>.
145    * <p/>
146    * @param conf the configuration
147    * @param timeout the max timeout in milliseconds to wait for the predicate.
148    * @param interval the interval in milliseconds to evaluate predicate.
149    * @param predicate the predicate to evaluate.
150    * @return the effective wait, in milli-seconds until the predicate becomes <code>true</code> or
151    *         wait is interrupted otherwise <code>-1</code> when times out
152    */
153   public static <E extends Exception> long waitFor(Configuration conf, long timeout, long interval,
154       Predicate<E> predicate) {
155     return waitFor(conf, timeout, interval, true, predicate);
156   }
157 
158   /**
159    * Waits up to the duration equal to the specified timeout multiplied by the
160    * {@link #getWaitForRatio(Configuration)} for the given {@link Predicate} to become
161    * <code>true</code>, failing the test if the timeout is reached, the Predicate is still
162    * <code>false</code> and failIfTimeout is set as <code>true</code>.
163    * <p/>
164    * @param conf the configuration
165    * @param timeout the timeout in milliseconds to wait for the predicate.
166    * @param interval the interval in milliseconds to evaluate predicate.
167    * @param failIfTimeout indicates if should fail current test case when times out.
168    * @param predicate the predicate to evaluate.
169    * @return the effective wait, in milli-seconds until the predicate becomes <code>true</code> or
170    *         wait is interrupted otherwise <code>-1</code> when times out
171    */
172   public static <E extends Exception> long waitFor(Configuration conf, long timeout, long interval,
173       boolean failIfTimeout, Predicate<E> predicate) {
174     long started = System.currentTimeMillis();
175     long adjustedTimeout = (long) (getWaitForRatio(conf) * timeout);
176     long mustEnd = started + adjustedTimeout;
177     long remainderWait;
178     long sleepInterval;
179     boolean eval;
180     boolean interrupted = false;
181 
182     try {
183       LOG.info(MessageFormat.format("Waiting up to [{0}] milli-secs(wait.for.ratio=[{1}])",
184         adjustedTimeout, getWaitForRatio(conf)));
185       while (!(eval = predicate.evaluate())
186               && (remainderWait = mustEnd - System.currentTimeMillis()) > 0) {
187         try {
188           // handle tail case when remainder wait is less than one interval
189           sleepInterval = Math.min(remainderWait, interval);
190           Thread.sleep(sleepInterval);
191         } catch (InterruptedException e) {
192           eval = predicate.evaluate();
193           interrupted = true;
194           break;
195         }
196       }
197       if (!eval) {
198         if (interrupted) {
199           LOG.warn(MessageFormat.format("Waiting interrupted after [{0}] msec",
200             System.currentTimeMillis() - started));
201         } else if (failIfTimeout) {
202           String msg = getExplanation(predicate);
203           Assert.fail(MessageFormat
204               .format("Waiting timed out after [{0}] msec", adjustedTimeout) + msg);
205         } else {
206           String msg = getExplanation(predicate);
207           LOG.warn(
208               MessageFormat.format("Waiting timed out after [{0}] msec", adjustedTimeout) + msg);
209         }
210       }
211       return (eval || interrupted) ? (System.currentTimeMillis() - started) : -1;
212     } catch (Exception ex) {
213       throw new RuntimeException(ex);
214     }
215   }
216 
217   public static String getExplanation(Predicate explain) {
218     if (explain instanceof ExplainingPredicate) {
219       try {
220         return " " + ((ExplainingPredicate) explain).explainFailure();
221       } catch (Exception e) {
222         LOG.error("Failed to get explanation, ", e);
223         return e.getMessage();
224       }
225     } else {
226       return "";
227     }
228   }
229 }