All Downloads are FREE. Search and download functionalities are using the official Maven repository.

software.amazon.awssdk.imds.internal.AsyncTokenCache Maven / Gradle / Ivy

/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file is distributed
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

package software.amazon.awssdk.imds.internal;

import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.utils.Logger;

/**
 * A cache for the IMDS {@link Token}, which can be refreshed through an asynchronous operation.
 * A call to the {@link AsyncTokenCache#get()} method returns an already completed future if the cached token is still fresh.
 * If the cached token was expired, the returned future will be completed once the refresh process has been
 * completed.
 * In the case where multiple call to 
get
are made while the token is expired, all CompletableFuture returned * will be completed once the single refresh process completes. * */ @SdkInternalApi final class AsyncTokenCache implements Supplier> { private static final Logger log = Logger.loggerFor(AsyncTokenCache.class); /** * The currently cached value. * Only modified through synchronized block, under the refreshLock. */ private volatile Token cachedToken; /** * The asynchronous operation that is used to refresh the token. * The Supplier must not block the current thread and is responsible to start the process that will complete the future. * A call the {@link AsyncTokenCache#get} method does not join or wait for the supplied future to finish, it only refreshes * the token once it finishes. */ private final Supplier> supplier; /** * The collection of future that are waiting for the refresh call to complete. If a call to {@link AsyncTokenCache#get()} * is made while the token request is happening, a future will be added to this collection. All future in this collection * are completed once the token request is done. * Should only be modified while holding the lock on {@link AsyncTokenCache#refreshLock}. */ private Collection> waitingFutures = new ArrayList<>(); /** * Indicates if the token refresh request is currently running or not. */ private final AtomicBoolean refreshRunning = new AtomicBoolean(false); private final Object refreshLock = new Object(); AsyncTokenCache(Supplier> supplier) { this.supplier = supplier; } @Override public CompletableFuture get() { Token currentValue = cachedToken; if (!needsRefresh(currentValue)) { log.debug(() -> "IMDS Token is not expired"); return CompletableFuture.completedFuture(currentValue); } synchronized (refreshLock) { // Make sure the value wasn't refreshed while we were waiting for the lock. currentValue = cachedToken; if (!needsRefresh(currentValue)) { return CompletableFuture.completedFuture(currentValue); } CompletableFuture result = new CompletableFuture<>(); waitingFutures.add(result); if (!refreshRunning.get()) { startRefresh(); } return result; } } private void startRefresh() { log.debug(() -> "IMDS token expired or null, starting asynchronous refresh."); CompletableFuture tokenRequest = supplier.get(); refreshRunning.set(true); // After supplier.get(), in case that throws an exception tokenRequest.whenComplete((token, throwable) -> { Collection> toComplete; synchronized (refreshLock) { // Instead of completing the waiting future while holding the lock, we copy the list reference and // release the lock before completing them. This is just in case someone (naughty) is doing something // blocking on the complete calls. It's not good that they're doing that, but at least // it won't block other threads trying to acquire the lock. toComplete = waitingFutures; waitingFutures = new ArrayList<>(); refreshRunning.set(false); if (token != null) { log.debug(() -> "IMDS token refresh completed. Token value: " + token.value()); cachedToken = token; } else { log.error(() -> "IMDS token refresh completed with error.", throwable); } } toComplete.forEach(future -> { if (throwable == null) { future.complete(token); } else { future.completeExceptionally(throwable); } }); }); } private boolean needsRefresh(Token token) { return token == null || token.isExpired(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy