Skip to content

Commit

Permalink
Merge pull request #58 from salesforce/replace-guava-with-caffeine-fix
Browse files Browse the repository at this point in the history
Configurable cache with Caffeine or Guava
  • Loading branch information
yoikawa authored Jun 10, 2024
2 parents f961c99 + 579f48c commit 43069d5
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,48 @@ public GrammaticalLabelSetLoader(LabelSetLoaderConfig config, boolean useSharedK
this.seedKeyMap = null;
}

this.cache = CaffeinatedGuava.build(getCacheBuilder(config), getCacheLoader());
this.cache = initCache(config);
}

/**
* initialize internal cache that holds {@link GrammaticalLabelSet}.
*
* @param config a configuration to initialize this loader
* @return a {@code LoadingCache} object to use as a cache
*/
protected LoadingCache<GrammaticalLabelSetDescriptor, GrammaticalLabelSet> initCache(LabelSetLoaderConfig config) {
// switch between Caffeine / Guava based on the config
return config.useCaffeine()
? CaffeinatedGuava.build(getCaffeineCacheBuilder(config), getCacheLoader())
: getGuavaCacheBuilder(config).build(getCacheLoader());
}

/**
* @param config
* a configuration to initialize this loader. if the configuration is set to use Guava, the
* {@link UnsupportedOperationException} will be thrown.
* @return Caffeine instance to build a cache
* @deprecated This method is no longer used. In order to obtain a cache builder to modify cache settings, use
* {@link #getCaffeineCacheBuilder(LabelSetLoaderConfig)} for Caffeine, or
* {@link #getGuavaCacheBuilder(LabelSetLoaderConfig)} for Guava.
* @see #initCache(LabelSetLoaderConfig)
* @see LabelSetLoaderConfig#setCaffeine(boolean)
*/
@Deprecated(since = "1.2.27", forRemoval = true)
protected Caffeine<Object, Object> getCacheBuilder(LabelSetLoaderConfig config) {
if (!config.useCaffeine()) throw new UnsupportedOperationException();
return getCaffeineCacheBuilder(config);
}

/**
* Return a Caffeine-based cache builder instance.
* Override this method to replace, or edit cache option.
*
* @param config a config to initialize this loader
* @return Caffeine-based builder
* @see #initCache(LabelSetLoaderConfig)
*/
protected Caffeine<Object, Object> getCaffeineCacheBuilder(LabelSetLoaderConfig config) {
Caffeine<Object, Object> builder = Caffeine.newBuilder().initialCapacity(64);

Duration expiration = config.getCacheExpireAfter();
Expand All @@ -173,6 +211,30 @@ protected Caffeine<Object, Object> getCacheBuilder(LabelSetLoaderConfig config)
return builder;
}

/**
* Return a Guava-based cache builder instance.
* Override this method to replace, or edit cache option.
*
* @param config a config to initialize this loader
* @return Guava-based builder
* @see #initCache(LabelSetLoaderConfig)
*/
protected CacheBuilder<Object, Object> getGuavaCacheBuilder(LabelSetLoaderConfig config) {
CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder().initialCapacity(64);

Duration expiration = config.getCacheExpireAfter();
if (!expiration.isZero() && !expiration.isNegative()) builder.expireAfterAccess(expiration);

long maxSize = config.getCacheMaxSize();
if (maxSize > 0) builder.maximumSize(maxSize);

if (config.isCacheStatsEnabled()) {
builder.recordStats();
}
return builder;
}


protected CacheLoader<GrammaticalLabelSetDescriptor, GrammaticalLabelSet> getCacheLoader() {
return new CacheLoader<GrammaticalLabelSetDescriptor, GrammaticalLabelSet>() {
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public class LabelSetLoaderConfig {
public static final String RECORD_STATS = "loader.cache.stats";
public static final String LOADER_EXPIRE_AFTER = "loader.cache.expireAfter";
public static final String LOADER_MAX_SIZE = "loader.cache.maxSize";
public static final String USE_CAFFEINE = "loader.cache.useCaffeine";

private final GrammaticalLabelSetDescriptor desc;
private final GrammaticalLabelSetProvider parent;
Expand All @@ -33,6 +34,7 @@ public class LabelSetLoaderConfig {
private boolean isCacheStatsEnabled;
private Duration cacheExpireAfter; // expiration in minues
private long cacheMaxSize; // max allowed entires
private boolean useCaffeine;

public LabelSetLoaderConfig(GrammaticalLabelSetDescriptor baseDesc, GrammaticalLabelSetProvider parent) {
this.desc = baseDesc;
Expand All @@ -45,6 +47,7 @@ public LabelSetLoaderConfig(GrammaticalLabelSetDescriptor baseDesc, GrammaticalL
setCacheStatsEnabled(BasePropertyFile.stringToBoolean(getProperty(RECORD_STATS)));
setCacheExpireAfter(Duration.ofMinutes(getPropertyLong(LOADER_EXPIRE_AFTER)));
setCacheMaxSize(getPropertyLong(LOADER_MAX_SIZE));
setCaffeine(BasePropertyFile.stringToBoolean(getProperty(USE_CAFFEINE)));
}

public LabelSetLoaderConfig(LabelSetLoaderConfig copyFrom) {
Expand All @@ -56,6 +59,7 @@ public LabelSetLoaderConfig(LabelSetLoaderConfig copyFrom) {
setCacheStatsEnabled(copyFrom.isCacheStatsEnabled());
setCacheExpireAfter(copyFrom.getCacheExpireAfter());
setCacheMaxSize(copyFrom.getCacheMaxSize());
setCaffeine(copyFrom.useCaffeine());
}

public static String getProperty(String prop) {
Expand Down Expand Up @@ -136,13 +140,23 @@ public LabelSetLoaderConfig setCacheMaxSize(long newVal) {
return this;
}

public LabelSetLoaderConfig setCaffeine(boolean useCaffeine) {
this.useCaffeine = useCaffeine;
return this;
}

public boolean useCaffeine() {
return this.useCaffeine;
}

@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("stats=").append(this.isCacheStatsEnabled)
.append(", expire=").append(this.cacheExpireAfter)
.append(", size=").append(this.cacheMaxSize)
.append(", dir=").append(this.cacheDir.toAbsolutePath());
.append(", dir=").append(this.cacheDir.toAbsolutePath())
.append(", useCaffeine=").append(this.useCaffeine);
return sb.toString();
}
}
3 changes: 3 additions & 0 deletions src/main/java/com/force/i18n/grammaticus.properties
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,6 @@ loader.cache.expireAfter=0

# maximum size of cache entry. This cache key is LabelSetDescriptor(language). no limit for 0.
loader.cache.maxSize=0

# use Caffeine as internal cache otherwise, Guava LoadingCache will be used.
loader.cache.useCaffeine=true
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

package com.force.i18n.grammar.parser;

import static org.junit.Assert.assertThrows;

import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URL;
Expand Down Expand Up @@ -547,11 +549,24 @@ public void testCacheConfig() throws InterruptedException {

GrammaticalLabelSetDescriptor desc = getDescriptor();

// Test expiration
LabelSetLoaderConfig config = new LabelSetLoaderConfig(desc, null);
GrammaticalLabelSetLoader loader = new GrammaticalLabelSetLoader(config);
// default is Ceffeine. comparing with the class name because the class is not public
assertEquals("CaffeinatedGuavaLoadingCache", loader.getCache().getClass().getSimpleName());

// try switching to Guava cache.
config.setCaffeine(false);
GrammaticalLabelSetLoader guavaLoader = new GrammaticalLabelSetLoader(config);
assertEquals("LocalLoadingCache", guavaLoader.getCache().getClass().getSimpleName());

assertThrows(UnsupportedOperationException.class, () -> guavaLoader.getCacheBuilder(config));

// Test expiration
// re-construct the loader with Caffeine and set expiration period to 1ms
config.setCaffeine(true);
config.setCacheExpireAfter(Duration.ofMillis(1));
loader = new GrammaticalLabelSetLoader(config);

GrammaticalLabelSetLoader loader = new GrammaticalLabelSetLoader(config);
loader.getSet(ENGLISH);
Thread.sleep(1);
assertNull(loader.getCache().getIfPresent(desc));
Expand Down Expand Up @@ -580,12 +595,12 @@ public void testCacheConfig() throws InterruptedException {

// Caffeine does not evict prior the threadshold, but after the size crossed
loader.getSet(FRENCH);
Thread.sleep(1);
Thread.sleep(5);
assertEquals(3, loader.getCache().size());
assertNotNull(loader.getCache().getIfPresent(desc.getForOtherLanguage(FRENCH)));

loader.getSet(GERMAN);
Thread.sleep(1);
Thread.sleep(5);
assertEquals(3, loader.getCache().size());
assertNotNull(loader.getCache().getIfPresent(desc.getForOtherLanguage(GERMAN)));
}
Expand Down

0 comments on commit 43069d5

Please sign in to comment.