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.hbase.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 }