1 /* 2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 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 * A copy of the License is located at 7 * 8 * http://aws.amazon.com/apache2.0 9 * 10 * or in the "license" file accompanying this file. This file is distributed 11 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 * express or implied. See the License for the specific language governing 13 * permissions and limitations under the License. 14 */ 15 16 package software.amazon.awssdk.core.internal.waiters; 17 18 import java.time.Duration; 19 import java.util.List; 20 import java.util.Optional; 21 import software.amazon.awssdk.annotations.SdkInternalApi; 22 import software.amazon.awssdk.core.exception.SdkClientException; 23 import software.amazon.awssdk.core.retry.RetryPolicyContext; 24 import software.amazon.awssdk.core.retry.backoff.BackoffStrategy; 25 import software.amazon.awssdk.core.waiters.WaiterAcceptor; 26 import software.amazon.awssdk.core.waiters.WaiterResponse; 27 import software.amazon.awssdk.utils.Either; 28 29 /** 30 * The waiter executor helper class. Contains the logic shared by {@link WaiterExecutor} and 31 * {@link AsyncWaiterExecutor} 32 */ 33 @SdkInternalApi 34 public final class WaiterExecutorHelper<T> { 35 private final List<WaiterAcceptor<? super T>> waiterAcceptors; 36 private final BackoffStrategy backoffStrategy; 37 private final Duration waitTimeout; 38 private final int maxAttempts; 39 WaiterExecutorHelper(List<WaiterAcceptor<? super T>> waiterAcceptors, WaiterConfiguration configuration)40 public WaiterExecutorHelper(List<WaiterAcceptor<? super T>> waiterAcceptors, 41 WaiterConfiguration configuration) { 42 this.waiterAcceptors = waiterAcceptors; 43 this.backoffStrategy = configuration.backoffStrategy(); 44 this.waitTimeout = configuration.waitTimeout(); 45 this.maxAttempts = configuration.maxAttempts(); 46 } 47 createWaiterResponse(Either<T, Throwable> responseOrException, int attempts)48 public WaiterResponse<T> createWaiterResponse(Either<T, Throwable> responseOrException, int attempts) { 49 return responseOrException.map( 50 r -> DefaultWaiterResponse.<T>builder().response(r).attemptsExecuted(attempts).build(), 51 e -> DefaultWaiterResponse.<T>builder().exception(e).attemptsExecuted(attempts).build() 52 ); 53 } 54 firstWaiterAcceptorIfMatched(Either<T, Throwable> responseOrException)55 public Optional<WaiterAcceptor<? super T>> firstWaiterAcceptorIfMatched(Either<T, Throwable> responseOrException) { 56 return responseOrException.map(this::responseMatches, this::exceptionMatches); 57 } 58 computeNextDelayInMills(int attemptNumber)59 public long computeNextDelayInMills(int attemptNumber) { 60 return backoffStrategy.computeDelayBeforeNextRetry(RetryPolicyContext.builder() 61 .retriesAttempted(attemptNumber) 62 .build()) 63 .toMillis(); 64 } 65 exceedsMaxWaitTime(long startTime, long nextDelayInMills)66 public boolean exceedsMaxWaitTime(long startTime, long nextDelayInMills) { 67 if (waitTimeout == null) { 68 return false; 69 } 70 71 long elapsedTime = System.currentTimeMillis() - startTime; 72 return elapsedTime + nextDelayInMills > waitTimeout.toMillis(); 73 } 74 nextDelayOrUnretryableException(int attemptNumber, long startTime)75 public Either<Long, SdkClientException> nextDelayOrUnretryableException(int attemptNumber, long startTime) { 76 if (attemptNumber >= maxAttempts) { 77 return Either.right(SdkClientException.create("The waiter has exceeded the max retry attempts: " + 78 maxAttempts)); 79 } 80 81 long nextDelay = computeNextDelayInMills(attemptNumber); 82 if (exceedsMaxWaitTime(startTime, nextDelay)) { 83 return Either.right(SdkClientException.create("The waiter has exceeded the max wait time or the " 84 + "next retry will exceed the max wait time + " + 85 waitTimeout)); 86 } 87 88 return Either.left(nextDelay); 89 } 90 noneMatchException(Either<T, Throwable> responseOrException)91 public SdkClientException noneMatchException(Either<T, Throwable> responseOrException) { 92 return responseOrException.map( 93 r -> SdkClientException.create("No acceptor was matched for the response: " + r), 94 t -> SdkClientException.create("An exception was thrown and did not match any " 95 + "waiter acceptors", t)); 96 } 97 waiterFailureException(WaiterAcceptor<? super T> acceptor)98 public SdkClientException waiterFailureException(WaiterAcceptor<? super T> acceptor) { 99 return SdkClientException.create(acceptor.message().orElse("A waiter acceptor was matched and transitioned " 100 + "the waiter to failure state")); 101 } 102 responseMatches(T response)103 private Optional<WaiterAcceptor<? super T>> responseMatches(T response) { 104 return waiterAcceptors.stream() 105 .filter(acceptor -> acceptor.matches(response)) 106 .findFirst(); 107 } 108 exceptionMatches(Throwable exception)109 private Optional<WaiterAcceptor<? super T>> exceptionMatches(Throwable exception) { 110 return waiterAcceptors.stream() 111 .filter(acceptor -> acceptor.matches(exception)) 112 .findFirst(); 113 } 114 } 115