Skip to content
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>secretsmanager</artifactId>
<version>2.29.6</version>
<version>2.37.1</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
Expand All @@ -58,13 +58,13 @@
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.17.0</version>
<version>5.20.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-annotations</artifactId>
<version>4.8.6</version>
<version>4.9.8</version>
<scope>compile</scope>
</dependency>
</dependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@

package com.amazonaws.secretsmanager.caching;

import java.util.concurrent.TimeUnit;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;

import java.time.Duration;


/**
* Cache configuration options such as max cache size, ttl for cached items, etc.
Expand All @@ -28,17 +28,36 @@ public class SecretCacheConfiguration {
/** The default cache size. */
public static final int DEFAULT_MAX_CACHE_SIZE = 1024;

/** The default TTL for an item stored in cache before access causing a refresh. */
public static final long DEFAULT_CACHE_ITEM_TTL = TimeUnit.HOURS.toMillis(1);
/**
* The default TTL for an item stored in cache before access causing a refresh.
*/
public static final Duration DEFAULT_CACHE_ITEM_TTL_DURATION = Duration.ofHours(1);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this public? imo we can define this as private.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made it match DEFAULT_CACHE_ITEM_TTL for backwards compatibility, that way if customers are using it they have a path forward.


/**
* The default TTL for an item stored in cache before access causing a refresh.
*
* @deprecated use DEFAULT_CACHE_ITEM_TTL_DURATION instead.
*/
@Deprecated
public static final long DEFAULT_CACHE_ITEM_TTL = DEFAULT_CACHE_ITEM_TTL_DURATION.toMillis();

/** The default version stage to use when retrieving secret values. */
public static final String DEFAULT_VERSION_STAGE = "AWSCURRENT";

/**
* The default maximum jitter value in milliseconds to use when forcing a refresh.
/**
* The default maximum jitter value to use when forcing a refresh.
* This prevents continuous refreshNow() calls by adding a random sleep.
*/
public static final long DEFAULT_FORCE_REFRESH_JITTER = 100;
public static final Duration DEFAULT_FORCE_REFRESH_JITTER_DURATION = Duration.ofMillis(100);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this public? imo its used internally so we can make it private

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made it match DEFAULT_FORCE_REFRESH_JITTER for backwards compatibility, that way if customers are using it they have a path forward.


/**
* The default maximum jitter value to use when forcing a refresh.
* This prevents continuous refreshNow() calls by adding a random sleep.
*
* @deprecated use DEFAULT_FORCE_REFRESH_JITTER_DURATION instead
*/
@Deprecated
public static final long DEFAULT_FORCE_REFRESH_JITTER = DEFAULT_FORCE_REFRESH_JITTER_DURATION.toMillis();

/** The client this cache instance will use for accessing AWS Secrets Manager. */
private SecretsManagerClient client = null;
Expand All @@ -53,25 +72,20 @@ public class SecretCacheConfiguration {
private int maxCacheSize = DEFAULT_MAX_CACHE_SIZE;

/**
* The number of milliseconds that a cached item is considered valid before
* requiring a refresh of the secret state. Items that have exceeded this
* TTL will be refreshed synchronously when requesting the secret value. If
* The duration that a cached item is considered valid before
* requiring a refresh of the secret state. Items that have exceeded this
* TTL will be refreshed synchronously when requesting the secret value. If
* the synchronous refresh failed, the stale secret will be returned.
*/
private long cacheItemTTL = DEFAULT_CACHE_ITEM_TTL;
private Duration cacheItemTTL = DEFAULT_CACHE_ITEM_TTL_DURATION;

/**
* The version stage that will be used when requesting the secret values for
* this cache.
*/
private String versionStage = DEFAULT_VERSION_STAGE;

/**
* When forcing a refresh using the refreshNow method, a random sleep
* will be performed using this value. This helps prevent code from
* executing a refreshNow in a continuous loop without waiting.
*/
private long forceRefreshJitterMillis = DEFAULT_FORCE_REFRESH_JITTER;
private Duration forceRefreshJitter = DEFAULT_FORCE_REFRESH_JITTER_DURATION;

/**
* Default constructor for the SecretCacheConfiguration object.
Expand Down Expand Up @@ -186,37 +200,75 @@ public SecretCacheConfiguration withMaxCacheSize(int maxCacheSize) {

/**
* Returns the TTL for the cached items.
* @deprecated use getCacheItemTTLDuration() instead
*
* @return The TTL in milliseconds before refreshing cached items.
*/
@Deprecated
public long getCacheItemTTL() {
return this.cacheItemTTL.toMillis();
}

/**
* Returns the TTL for the cached items.
*
* @return The TTL in milliseconds before refreshing cached items.
*/
public Duration getCacheItemTTLDuration() {
return this.cacheItemTTL;
}

/**
* Sets the TTL in milliseconds for the cached items. Once cached items exceed this
* TTL, the item will be refreshed using the AWS Secrets Manager client.
*
* @deprecated use setCacheItemTTL(Duration cacheItemTTL) instead
* @param cacheItemTTL
* The TTL for cached items before requiring a refresh.
*/
@Deprecated
public void setCacheItemTTL(long cacheItemTTL) {
this.cacheItemTTL = Duration.ofMillis(cacheItemTTL);
}

/**
* Sets the TTL for the cached items. Once cached items exceed this
* TTL, the item will be refreshed using the AWS Secrets Manager client.
*
* @param cacheItemTTL
* The TTL for cached items before requiring a refresh.
*/
public void setCacheItemTTL(Duration cacheItemTTL) {
this.cacheItemTTL = cacheItemTTL;
}

/**
* Sets the TTL in milliseconds for the cached items. Once cached items exceed this
* TTL, the item will be refreshed using the AWS Secrets Manager client.
*
* @deprecated use withCacheItemTTL(Duration cacheItemTTL) instead
* @param cacheItemTTL
* The TTL for cached items before requiring a refresh.
* @return The updated ClientConfiguration object with the new TTL setting.
*/
@Deprecated
public SecretCacheConfiguration withCacheItemTTL(long cacheItemTTL) {
this.setCacheItemTTL(cacheItemTTL);
return this;
}

/**
* Sets the TTL for the cached items. Once cached items exceed this
* TTL, the item will be refreshed using the AWS Secrets Manager client.
*
* @param cacheItemTTL The TTL for cached items before requiring a refresh.
* @return The updated ClientConfiguration object with the new TTL setting.
*/
public SecretCacheConfiguration withCacheItemTTL(Duration cacheItemTTL) {
this.setCacheItemTTL(cacheItemTTL);
return this;
}

/**
* Returns the version stage that is used for requesting secret values.
*
Expand Down Expand Up @@ -252,43 +304,94 @@ public SecretCacheConfiguration withVersionStage(String versionStage) {

/**
* Returns the refresh jitter that is used when force refreshing secrets.
*
* @deprecated use getForceRefreshJitter() instead
*
* @return The maximum jitter sleep time in milliseconds used with refreshing secrets.
* @return The maximum jitter sleep time in milliseconds used with refreshing
* secrets.
*/
@Deprecated
public long getForceRefreshJitterMillis() {
return this.forceRefreshJitterMillis;
return this.forceRefreshJitter.toMillis();
}

/**
* Returns the refresh jitter that is used when force refreshing secrets.
*
* @return The jitter sleep time used with refreshing secrets.
*/
public Duration getForceRefreshJitter() {
return this.forceRefreshJitter;
}

/**
* Sets the maximum sleep time in milliseconds between force refresh calls.
* This value is used to prevent continuous refreshNow() calls in tight loops
* by adding a random sleep between half the configured value and the full value.
* by adding a random sleep between half the configured value and the full
* value.
* The value must be greater than or equal to zero.
*
* @deprecated use setForceRefreshJitter(Duration forceRefreshJitter) instead
*
* @param forceRefreshJitterMillis
* The maximum sleep time in milliseconds between force refresh calls.
* @param forceRefreshJitterMillis The maximum sleep time in milliseconds
* between force refresh calls.
* @throws IllegalArgumentException if the value is negative
*/
@Deprecated
public void setForceRefreshJitterMillis(long forceRefreshJitterMillis) {
if (forceRefreshJitterMillis < 0) {
this.setForceRefreshJitter(Duration.ofMillis(forceRefreshJitterMillis));
}

/**
* Sets the maximum sleep time between force refresh calls.
* This value is used to prevent continuous refreshNow() calls in tight loops
* by adding a random sleep between half the configured value and the full
* value.
* The value must be greater than or equal to zero.
*
* @param forceRefreshJitter The maximum sleep time between force refresh calls.
* @throws IllegalArgumentException if the value is negative
*/
public void setForceRefreshJitter(Duration forceRefreshJitter) {
if (forceRefreshJitter.isNegative()) {
throw new IllegalArgumentException("Force refresh jitter must be greater than or equal to zero");
}
this.forceRefreshJitterMillis = forceRefreshJitterMillis;
this.forceRefreshJitter = forceRefreshJitter;
}

/**
* Sets the maximum sleep time in milliseconds between force refresh calls.
* This value is used to prevent continuous refreshNow() calls in tight loops
* by adding a random sleep between half the configured value and the full value.
* by adding a random sleep between half the configured value and the full
* value.
*
* @deprecated use withForceRefreshJitter(Duration forceRefreshJitter) instead
*
* @param forceRefreshJitterMillis
* The maximum sleep time in milliseconds between force refresh calls.
* @return The updated ClientConfiguration object with the new refresh sleep time.
* @param forceRefreshJitterMillis The maximum sleep time in milliseconds
* between force refresh calls.
* @return The updated ClientConfiguration object with the new refresh sleep
* time.
* @throws IllegalArgumentException if the value is negative
*/
@Deprecated
public SecretCacheConfiguration withForceRefreshJitterMillis(long forceRefreshJitterMillis) {
this.setForceRefreshJitterMillis(forceRefreshJitterMillis);
return this;
}

/**
* Sets the maximum sleep time between force refresh calls.
* This value is used to prevent continuous refreshNow() calls in tight loops
* by adding a random sleep between half the configured value and the full
* value.
*
* @param forceRefreshJitter The maximum sleep time between force refresh calls.
* @return The updated ClientConfiguration object with the new refresh sleep
* time.
* @throws IllegalArgumentException if the value is negative
*/
public SecretCacheConfiguration withForceRefreshJitter(Duration forceRefreshJitter) {
this.setForceRefreshJitter(forceRefreshJitter);
return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,18 @@

package com.amazonaws.secretsmanager.caching.cache;

import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ThreadLocalRandom;

import com.amazonaws.secretsmanager.caching.SecretCacheConfiguration;

import software.amazon.awssdk.retries.api.internal.backoff.FixedDelayWithJitter;
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
import software.amazon.awssdk.services.secretsmanager.model.DescribeSecretRequest;
import software.amazon.awssdk.services.secretsmanager.model.DescribeSecretResponse;
import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueResponse;

import java.time.Instant;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ThreadLocalRandom;

/**
* The cached secret item which contains information from the DescribeSecret
* request to AWS Secrets Manager along with any associated GetSecretValue
Expand All @@ -39,7 +40,8 @@ public class SecretCacheItem extends SecretCacheObject<DescribeSecretResponse> {
* The next scheduled refresh time for this item. Once the item is accessed
* after this time, the item will be synchronously refreshed.
*/
private long nextRefreshTime = 0;
private Instant nextRefreshTime = Instant.ofEpochMilli(0);
private FixedDelayWithJitter fixedDelayWithJitter;

/**
* Construct a new cached item for the secret.
Expand All @@ -56,6 +58,8 @@ public SecretCacheItem(final String secretId,
final SecretsManagerClient client,
final SecretCacheConfiguration config) {
super(secretId, client, config);
this.fixedDelayWithJitter = new FixedDelayWithJitter(ThreadLocalRandom::current,
config.getCacheItemTTLDuration());
}

@Override
Expand Down Expand Up @@ -87,10 +91,7 @@ public String toString() {
protected boolean isRefreshNeeded() {
if (super.isRefreshNeeded()) { return true; }
if (null != this.exception) { return false; }
if (System.currentTimeMillis() >= this.nextRefreshTime) {
return true;
}
return false;
return Instant.now().isAfter(nextRefreshTime);
}

/**
Expand All @@ -101,9 +102,8 @@ protected boolean isRefreshNeeded() {
@Override
protected DescribeSecretResponse executeRefresh() {
DescribeSecretResponse describeSecretResponse = client.describeSecret(DescribeSecretRequest.builder().secretId(this.secretId).build());
long ttl = this.config.getCacheItemTTL();
this.nextRefreshTime = System.currentTimeMillis() +
ThreadLocalRandom.current().nextLong(ttl / 2,ttl + 1) ;
// Attempt count is irrelevant for fixed delay refresh strategy
this.nextRefreshTime = Instant.now().plus(this.fixedDelayWithJitter.computeDelay(1));

return describeSecretResponse;
}
Expand Down
Loading