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 }