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  
20  package org.apache.hadoop.hbase;
21  
22  import java.text.MessageFormat;
23  
24  import junit.framework.Assert;
25  
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  import org.apache.hadoop.classification.InterfaceAudience;
29  import org.apache.hadoop.conf.Configuration;
30  
31  /**
32   * A class that provides a standard waitFor pattern
33   * See details at https://issues.apache.org/jira/browse/HBASE-7384
34   */
35  @InterfaceAudience.Private
36  public final class Waiter {
37  
38    private static final Log LOG = LogFactory.getLog(Waiter.class);
39  
40    /**
41     * System property name whose value is a scale factor to increase time out values dynamically used
42     * in {@link #sleep(Configuration, long)}, {@link #waitFor(Configuration, long, Predicate)},
43     * {@link #waitFor(Configuration, long, long, Predicate)}, and
44     * {@link #waitFor(Configuration, long, long, boolean, Predicate)} method
45     * <p/>
46     * The actual time out value will equal to hbase.test.wait.for.ratio * passed-in timeout
47     */
48    public static final String HBASE_TEST_WAIT_FOR_RATIO = "hbase.test.wait.for.ratio";
49  
50    private static float HBASE_WAIT_FOR_RATIO_DEFAULT = 1;
51  
52    private static float waitForRatio = -1;
53  
54    private Waiter() {
55    }
56  
57    /**
58     * Returns the 'wait for ratio' used in the {@link #sleep(Configuration, long)},
59     * {@link #waitFor(Configuration, long, Predicate)},
60     * {@link #waitFor(Configuration, long, long, Predicate)} and
61     * {@link #waitFor(Configuration, long, long, boolean, Predicate)} methods of the class
62     * <p/>
63     * This is useful to dynamically adjust max time out values when same test cases run in different
64     * test machine settings without recompiling & re-deploying code.
65     * <p/>
66     * The value is obtained from the Java System property or configuration setting
67     * <code>hbase.test.wait.for.ratio</code> which defaults to <code>1</code>.
68     * @param conf the configuration
69     * @return the 'wait for ratio' for the current test run.
70     */
71    public static float getWaitForRatio(Configuration conf) {
72      if (waitForRatio < 0) {
73        // System property takes precedence over configuration setting
74        if (System.getProperty(HBASE_TEST_WAIT_FOR_RATIO) != null) {
75          waitForRatio = Float.parseFloat(System.getProperty(HBASE_TEST_WAIT_FOR_RATIO));
76        } else {
77          waitForRatio = conf.getFloat(HBASE_TEST_WAIT_FOR_RATIO, HBASE_WAIT_FOR_RATIO_DEFAULT);
78        }
79      }
80      return waitForRatio;
81    }
82  
83    /**
84     * A predicate 'closure' used by the {@link Waiter#waitFor(Configuration, long, Predicate)} and
85     * {@link Waiter#waitFor(Configuration, long, Predicate)} and
86     * {@link Waiter#waitFor(Configuration, long, long, boolean, Predicate) methods.
87     */
88    @InterfaceAudience.Private
89    public interface Predicate<E extends Exception> {
90  
91      /**
92       * Perform a predicate evaluation.
93       * @return the boolean result of the evaluation.
94       * @throws Exception thrown if the predicate evaluation could not evaluate.
95       */
96      boolean evaluate() throws E;
97  
98    }
99  
100   /**
101    * Makes the current thread sleep for the duration equal to the specified time in milliseconds
102    * multiplied by the {@link #getWaitForRatio(Configuration)}.
103    * @param conf the configuration
104    * @param time the number of milliseconds to sleep.
105    */
106   public static void sleep(Configuration conf, long time) {
107     try {
108       Thread.sleep((long) (getWaitForRatio(conf) * time));
109     } catch (InterruptedException ex) {
110       LOG.warn(MessageFormat.format("Sleep interrupted, {0}", ex.toString()));
111     }
112   }
113 
114   /**
115    * Waits up to the duration equal to the specified timeout multiplied by the
116    * {@link #getWaitForRatio(Configuration)} for the given {@link Predicate} to become
117    * <code>true</code>, failing the test if the timeout is reached and the Predicate is still
118    * <code>false</code>.
119    * <p/>
120    * @param conf the configuration
121    * @param timeout the timeout in milliseconds to wait for the predicate.
122    * @param predicate the predicate to evaluate.
123    * @return the effective wait, in milli-seconds until the predicate becomes <code>true</code> or
124    *         wait is interrupted otherwise <code>-1</code> when times out
125    */
126   public static <E extends Exception> long waitFor(Configuration conf, long timeout,
127       Predicate<E> predicate) throws E {
128     return waitFor(conf, timeout, 100, true, predicate);
129   }
130 
131   /**
132    * Waits up to the duration equal to the specified timeout multiplied by the
133    * {@link #getWaitForRatio(Configuration)} for the given {@link Predicate} to become
134    * <code>true</code>, failing the test if the timeout is reached and the Predicate is still
135    * <code>false</code>.
136    * <p/>
137    * @param conf the configuration
138    * @param timeout the max timeout in milliseconds to wait for the predicate.
139    * @param interval the interval in milliseconds to evaluate predicate.
140    * @param predicate the predicate to evaluate.
141    * @return the effective wait, in milli-seconds until the predicate becomes <code>true</code> or
142    *         wait is interrupted otherwise <code>-1</code> when times out
143    */
144   public static <E extends Exception> long waitFor(Configuration conf, long timeout, long interval,
145       Predicate<E> predicate) throws E {
146     return waitFor(conf, timeout, interval, true, predicate);
147   }
148 
149   /**
150    * Waits up to the duration equal to the specified timeout multiplied by the
151    * {@link #getWaitForRatio(Configuration)} for the given {@link Predicate} to become
152    * <code>true</code>, failing the test if the timeout is reached, the Predicate is still
153    * <code>false</code> and failIfTimeout is set as <code>true</code>.
154    * <p/>
155    * @param conf the configuration
156    * @param timeout the timeout in milliseconds to wait for the predicate.
157    * @param interval the interval in milliseconds to evaluate predicate.
158    * @param failIfTimeout indicates if should fail current test case when times out.
159    * @param predicate the predicate to evaluate.
160    * @return the effective wait, in milli-seconds until the predicate becomes <code>true</code> or
161    *         wait is interrupted otherwise <code>-1</code> when times out
162    */
163   public static <E extends Exception> long waitFor(Configuration conf, long timeout, long interval,
164       boolean failIfTimeout, Predicate<E> predicate) throws E {
165     long started = System.currentTimeMillis();
166     long adjustedTimeout = (long) (getWaitForRatio(conf) * timeout);
167     long mustEnd = started + adjustedTimeout;
168     long remainderWait = 0;
169     long sleepInterval = 0;
170     Boolean eval = false;
171     Boolean interrupted = false;
172 
173     try {
174       LOG.info(MessageFormat.format("Waiting up to [{0}] milli-secs(wait.for.ratio=[{1}])",
175         adjustedTimeout, getWaitForRatio(conf)));
176       while (!(eval = predicate.evaluate())
177               && (remainderWait = mustEnd - System.currentTimeMillis()) > 0) {
178         try {
179           // handle tail case when remainder wait is less than one interval
180           sleepInterval = (remainderWait > interval) ? interval : remainderWait;
181           Thread.sleep(sleepInterval);
182         } catch (InterruptedException e) {
183           eval = predicate.evaluate();
184           interrupted = true;
185           break;
186         }
187       }
188       if (!eval) {
189         if (interrupted) {
190           LOG.warn(MessageFormat.format("Waiting interrupted after [{0}] msec",
191             System.currentTimeMillis() - started));
192         } else if (failIfTimeout) {
193           Assert.fail(MessageFormat.format("Waiting timed out after [{0}] msec", adjustedTimeout));
194         } else {
195           LOG.warn(MessageFormat.format("Waiting timed out after [{0}] msec", adjustedTimeout));
196         }
197       }
198       return (eval || interrupted) ? (System.currentTimeMillis() - started) : -1;
199     } catch (Exception ex) {
200       throw new RuntimeException(ex);
201     }
202   }
203 
204 }