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