1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.bedstead.nene.utils; 18 19 import android.util.Log; 20 21 import com.android.bedstead.nene.exceptions.NeneException; 22 import com.android.bedstead.nene.exceptions.PollValueFailedException; 23 24 import com.google.errorprone.annotations.CanIgnoreReturnValue; 25 26 import java.time.Duration; 27 import java.time.Instant; 28 import java.util.Objects; 29 import java.util.function.Function; 30 import java.util.function.Supplier; 31 32 /** 33 * Utility class for polling for some state to be reached. 34 * 35 * <p>To use, you first use {@link #forValue(String, ValueSupplier)} to supply the value to be 36 * polled on. It is recommended you provide a descriptive name of the source of the value to improve 37 * failure messages. 38 * 39 * <p>Then you specify the criteria you are polling for, simple criteria are provided 40 * (e.g. {@link #toBeNull()}, {@link #toBeEqualTo(Object)}, etc.) and these should be preferred when 41 * possible as they provide good failure messages by default. If your state cannot be queried using 42 * a simple matcher, you can use {@link #toMeet(ValueChecker)} and pass in an arbitrary function to 43 * check the value. 44 * 45 * <p>By default, this will poll up to {@link #timeout(Duration)} (defaulting to 30 seconds), and 46 * will return after the timeout whatever the value is at that time. If you'd rather a 47 * {@link NeneException} is thrown, you can use {@link #errorOnFail()}. 48 * 49 * <p>You can add more context to failures using the overloaded versions of {@link #errorOnFail()}. 50 * In particular, you should do this if you're using {@link #toMeet(ValueChecker)} as otherwise the 51 * failure message is not helpful. 52 * 53 * <p>Any exceptions thrown when getting the value or when checking it will result in that check 54 * failing and a retry happening. If this is the final iteration the exception will be thrown 55 * wrapped in a {@link NeneException}. 56 * 57 * <p>You should not use this class to retry some state changing logic until it succeeds - it should 58 * only be used for polling a value until it reaches the value you want. 59 */ 60 public final class Poll<E> { 61 62 private static final String LOG_TAG = Poll.class.getName(); 63 64 private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(120); 65 private static final long SLEEP_MILLIS = 200; // TODO(b/223768343): Allow configuring 66 private final String mValueName; 67 private final ValueSupplier<E> mSupplier; 68 private ValueChecker<E> mChecker = (v) -> true; 69 private Function<E, Boolean> mTerminalValueChecker; 70 private Function<Throwable, Boolean> mTerminalExceptionChecker; 71 private Function2<String, E, String> mErrorSupplier = 72 (valueName, value) -> "Expected " 73 + valueName + " to meet checker function. Was " + value; 74 private Duration mTimeout = DEFAULT_TIMEOUT; 75 private boolean mErrorOnFail = false; 76 Poll(String valueName, ValueSupplier<E> supplier)77 private Poll(String valueName, ValueSupplier<E> supplier) { 78 mValueName = valueName; 79 mSupplier = supplier; 80 } 81 82 /** 83 * Begin polling for the given value. 84 * 85 * <p>In general, this method should only be used when you're using the 86 * {@link #errorOnFail(Function)} method, otherwise {@link #forValue(String, ValueSupplier)} 87 * will mean better error messages. 88 */ forValue(ValueSupplier<E> supplier)89 public static <E> Poll<E> forValue(ValueSupplier<E> supplier) { 90 return forValue("value", supplier); 91 } 92 93 /** 94 * Begin polling for the given value. 95 * 96 * <p>The {@code valueName} will be used in error messages. 97 */ forValue(String valueName, ValueSupplier<E> supplier)98 public static <E> Poll<E> forValue(String valueName, ValueSupplier<E> supplier) { 99 return new Poll<>(valueName, supplier); 100 } 101 102 /** Expect the value to be null. */ 103 @CanIgnoreReturnValue toBeNull()104 public Poll<E> toBeNull() { 105 toMeet(Objects::isNull); 106 softErrorOnFail((valueName, value) -> 107 "Expected " + valueName + " to be null. Was " + value); 108 return this; 109 } 110 111 /** Expect the value to not be null. */ 112 @CanIgnoreReturnValue toNotBeNull()113 public Poll<E> toNotBeNull() { 114 toMeet(Objects::nonNull); 115 softErrorOnFail((valueName, value) -> 116 "Expected " + valueName + " to not be null. Was " + value); 117 return this; 118 } 119 120 /** Expect the value to be equal to {@code other}. */ 121 @CanIgnoreReturnValue toBeEqualTo(E other)122 public Poll<E> toBeEqualTo(E other) { 123 toMeet(v -> Objects.equals(v, other)); 124 softErrorOnFail((valueName, value) -> 125 "Expected " + valueName + " to be equal to " + other + ". Was " + value); 126 return this; 127 } 128 129 /** Expect the value to not be equal to {@code other}. */ 130 @CanIgnoreReturnValue toNotBeEqualTo(E other)131 public Poll<E> toNotBeEqualTo(E other) { 132 toMeet(v -> !Objects.equals(v, other)); 133 softErrorOnFail((valueName, value) -> 134 "Expected " + valueName + " to not be equal to " + other + ". Was " + value); 135 return this; 136 } 137 138 /** 139 * Expect the value to meet the requirements specified by {@code checker}. 140 * 141 * <p>If this method throws an exception, or returns false, then the value will be considered 142 * to not have met the requirements. If true is returned then the value will be considered to 143 * have met the requirements. 144 */ 145 @CanIgnoreReturnValue toMeet(ValueChecker<E> checker)146 public Poll<E> toMeet(ValueChecker<E> checker) { 147 mChecker = checker; 148 return this; 149 } 150 151 /** Throw an exception on failure instead of returning the incorrect value. */ 152 @CanIgnoreReturnValue errorOnFail()153 public Poll<E> errorOnFail() { 154 mErrorOnFail = true; 155 return this; 156 } 157 158 /** 159 * Throw an exception on failure instead of returning the incorrect value. 160 * 161 * <p>The {@code errorSupplier} will be passed the latest value. If you do not want to include 162 * the latest value in the error message (and have it auto-provided) use 163 * {@link #errorOnFail(String)}. 164 */ 165 @CanIgnoreReturnValue errorOnFail(Function<E, String> errorSupplier)166 public Poll<E> errorOnFail(Function<E, String> errorSupplier) { 167 softErrorOnFail((vn, v) -> errorSupplier.apply(v)); 168 mErrorOnFail = true; 169 return this; 170 } 171 172 /** 173 * Throw an exception on failure instead of returning the incorrect value. 174 * 175 * <p>The {@code error} will be used as the failure message, with the latest value added. 176 */ 177 @CanIgnoreReturnValue errorOnFail(String error)178 public Poll<E> errorOnFail(String error) { 179 softErrorOnFail((vn, v) -> error + ". " + vn + " was " + v); 180 mErrorOnFail = true; 181 return this; 182 } 183 softErrorOnFail(Function2<String, E, String> errorSupplier)184 private void softErrorOnFail(Function2<String, E, String> errorSupplier) { 185 mErrorSupplier = errorSupplier; 186 } 187 188 /** Change the default timeout before the check is considered failed (default 30 seconds). */ 189 @CanIgnoreReturnValue timeout(Duration timeout)190 public Poll<E> timeout(Duration timeout) { 191 mTimeout = timeout; 192 return this; 193 } 194 195 /** 196 * Await the value meeting the requirements. 197 * 198 * <p>This will retry fetching and checking the value until it meets the requirements or the 199 * timeout expires. 200 * 201 * <p>By default, the most recent value will be returned even after timeout. 202 * See {@link #errorOnFail()} to change this behavior. 203 */ 204 @CanIgnoreReturnValue await()205 public E await() { 206 Instant startTime = Instant.now(); 207 Instant endTime = startTime.plus(mTimeout); 208 209 E value = null; 210 int tries = 0; 211 212 while (!Duration.between(Instant.now(), endTime).isNegative()) { 213 tries++; 214 try { 215 value = mSupplier.get(); 216 if (mChecker.apply(value)) { 217 return value; 218 } 219 if (mTerminalValueChecker != null && mTerminalValueChecker.apply(value)) { 220 break; 221 } 222 } catch (Throwable e) { 223 // Eat the exception until the timeout 224 Log.e(LOG_TAG, "Exception during retries", e); 225 if (mTerminalExceptionChecker != null && mTerminalExceptionChecker.apply(e)) { 226 break; 227 } 228 } 229 230 try { 231 Thread.sleep(SLEEP_MILLIS); 232 } catch (InterruptedException e) { 233 throw new PollValueFailedException("Interrupted while awaiting", e); 234 } 235 } 236 237 if (!mErrorOnFail) { 238 return value; 239 } 240 241 // We call again to allow exceptions to be thrown - if it passes here we can still return 242 try { 243 value = mSupplier.get(); 244 } catch (Throwable e) { 245 long seconds = Duration.between(startTime, Instant.now()).toMillis() / 1000; 246 throw new PollValueFailedException(mErrorSupplier.apply(mValueName, value) 247 + " - Exception when getting value (checked " + tries + " times in " 248 + seconds + " seconds)", e); 249 } 250 251 try { 252 if (mChecker.apply(value)) { 253 return value; 254 } 255 256 long seconds = Duration.between(startTime, Instant.now()).toMillis() / 1000; 257 throw new PollValueFailedException( 258 mErrorSupplier.apply(mValueName, value) + " (checked " + tries + " times in " 259 + seconds + " seconds)"); 260 } catch (Throwable e) { 261 long seconds = Duration.between(startTime, Instant.now()).toMillis() / 1000; 262 throw new PollValueFailedException( 263 mErrorSupplier.apply(mValueName, value) + " (checked " + tries + " times in " 264 + seconds + " seconds)", e); 265 266 } 267 } 268 269 /** 270 * Set a method which, after a value fails the check, can tell if the failure is terminal. 271 * 272 * <p>This method will only be called after the value check fails. It will be passed the most 273 * recent value and should return true if this value is terminal. 274 * 275 * <p>If true is returned, then no more retries will be attempted, otherwise retries will 276 * continue until timeout. 277 */ 278 @CanIgnoreReturnValue terminalValue(Function<E, Boolean> terminalChecker)279 public Poll<E> terminalValue(Function<E, Boolean> terminalChecker) { 280 mTerminalValueChecker = terminalChecker; 281 return this; 282 } 283 284 /** 285 * Set a method which, after a value fails the check, can tell if the failure is terminal. 286 * 287 * <p>This method will only be called after the value check fails with an exception. It will be 288 * passed the exception return true if this exception is terminal. 289 * 290 * <p>If true is returned, then no more retries will be attempted, otherwise retries will 291 * continue until timeout. 292 */ 293 @CanIgnoreReturnValue terminalException(Function<Throwable, Boolean> terminalChecker)294 public Poll<E> terminalException(Function<Throwable, Boolean> terminalChecker) { 295 mTerminalExceptionChecker = terminalChecker; 296 return this; 297 } 298 299 /** 300 * Set a method which, after a value fails the check, can tell if the failure is terminal. 301 * 302 * <p>This method will only be called after the value check fails. It should return true if this 303 * state is terminal. 304 * 305 * <p>If true is returned, then no more retries will be attempted, otherwise retries will 306 * continue until timeout. 307 */ 308 @CanIgnoreReturnValue terminal(Supplier<Boolean> terminalChecker)309 public Poll<E> terminal(Supplier<Boolean> terminalChecker) { 310 terminalValue((e) -> terminalChecker.get()); 311 terminalException((e) -> terminalChecker.get()); 312 return this; 313 } 314 315 /** Interface for supplying values to {@link Poll}. */ 316 public interface ValueSupplier<E> { get()317 E get() throws Throwable; 318 } 319 320 /** Interface for checking values for {@link Poll}. */ 321 public interface ValueChecker<E> { apply(E e)322 boolean apply(E e) throws Throwable; 323 } 324 325 /** Interface for supplying errors for {@link Poll}. */ 326 public interface Function2<E, F, G> { apply(E e, F f)327 G apply(E e, F f); 328 } 329 } 330