Skip to content

Commit 3842f13

Browse files
committed
utils: add wrapper for the loading cache
Follow up for apache#9638 Creates a utility class LazyCache which currently wraps Caffeine library Cache class. Signed-off-by: Abhishek Kumar <[email protected]>
1 parent 2245d98 commit 3842f13

File tree

3 files changed

+168
-9
lines changed

3 files changed

+168
-9
lines changed

framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigDepotImpl.java

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import java.util.HashSet;
2424
import java.util.List;
2525
import java.util.Set;
26-
import java.util.concurrent.TimeUnit;
2726

2827
import javax.annotation.PostConstruct;
2928
import javax.inject.Inject;
@@ -36,6 +35,7 @@
3635
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
3736
import org.apache.cloudstack.framework.config.dao.ConfigurationGroupDao;
3837
import org.apache.cloudstack.framework.config.dao.ConfigurationSubGroupDao;
38+
import org.apache.cloudstack.utils.cache.LazyCache;
3939
import org.apache.commons.lang.ObjectUtils;
4040
import org.apache.commons.lang3.StringUtils;
4141
import org.apache.logging.log4j.LogManager;
@@ -44,8 +44,6 @@
4444
import com.cloud.utils.Pair;
4545
import com.cloud.utils.Ternary;
4646
import com.cloud.utils.exception.CloudRuntimeException;
47-
import com.github.benmanes.caffeine.cache.Cache;
48-
import com.github.benmanes.caffeine.cache.Caffeine;
4947

5048
/**
5149
* ConfigDepotImpl implements the ConfigDepot and ConfigDepotAdmin interface.
@@ -87,17 +85,15 @@ public class ConfigDepotImpl implements ConfigDepot, ConfigDepotAdmin {
8785
List<ScopedConfigStorage> _scopedStorages;
8886
Set<Configurable> _configured = Collections.synchronizedSet(new HashSet<Configurable>());
8987
Set<String> newConfigs = Collections.synchronizedSet(new HashSet<>());
90-
Cache<String, String> configCache;
88+
LazyCache<String, String> configCache;
9189

9290
private HashMap<String, Pair<String, ConfigKey<?>>> _allKeys = new HashMap<String, Pair<String, ConfigKey<?>>>(1007);
9391

9492
HashMap<ConfigKey.Scope, Set<ConfigKey<?>>> _scopeLevelConfigsMap = new HashMap<ConfigKey.Scope, Set<ConfigKey<?>>>();
9593

9694
public ConfigDepotImpl() {
97-
configCache = Caffeine.newBuilder()
98-
.maximumSize(512)
99-
.expireAfterWrite(CONFIG_CACHE_EXPIRE_SECONDS, TimeUnit.SECONDS)
100-
.build();
95+
configCache = new LazyCache<>(512,
96+
CONFIG_CACHE_EXPIRE_SECONDS, this::getConfigStringValueInternal);
10197
ConfigKey.init(this);
10298
createEmptyScopeLevelMappings();
10399
}
@@ -311,7 +307,7 @@ private String getConfigCacheKey(String key, ConfigKey.Scope scope, Long scopeId
311307

312308
@Override
313309
public String getConfigStringValue(String key, ConfigKey.Scope scope, Long scopeId) {
314-
return configCache.get(getConfigCacheKey(key, scope, scopeId), this::getConfigStringValueInternal);
310+
return configCache.get(getConfigCacheKey(key, scope, scopeId));
315311
}
316312

317313
@Override
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.apache.cloudstack.utils.cache;
19+
20+
import java.util.concurrent.TimeUnit;
21+
import java.util.function.Function;
22+
23+
import com.github.benmanes.caffeine.cache.Caffeine;
24+
import com.github.benmanes.caffeine.cache.LoadingCache;
25+
26+
public class LazyCache<K, V> {
27+
28+
private final LoadingCache<K, V> cache;
29+
30+
public LazyCache(long maximumSize, long expireAfterWriteSeconds, Function<K, V> loader) {
31+
this.cache = Caffeine.newBuilder()
32+
.maximumSize(maximumSize)
33+
.expireAfterWrite(expireAfterWriteSeconds, TimeUnit.SECONDS)
34+
.build(loader::apply);
35+
}
36+
37+
public V get(K key) {
38+
return cache.get(key);
39+
}
40+
41+
public void invalidate(K key) {
42+
cache.invalidate(key);
43+
}
44+
45+
public void clear() {
46+
cache.invalidateAll();
47+
}
48+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.apache.cloudstack.utils.cache;
19+
20+
import static org.junit.Assert.assertEquals;
21+
22+
import java.util.function.Function;
23+
24+
import org.junit.Assert;
25+
import org.junit.Before;
26+
import org.junit.Test;
27+
import org.junit.runner.RunWith;
28+
import org.mockito.Mockito;
29+
import org.mockito.junit.MockitoJUnitRunner;
30+
31+
@RunWith(MockitoJUnitRunner.class)
32+
public class LazyCacheTest {
33+
private final long expireSeconds = 1;
34+
private final String cacheValuePrefix = "ComputedValueFor:";
35+
private LazyCache<String, String> cache;
36+
private Function<String, String> mockLoader;
37+
38+
@Before
39+
public void setUp() {
40+
mockLoader = Mockito.mock(Function.class);
41+
Mockito.when(mockLoader.apply(Mockito.anyString())).thenAnswer(invocation -> cacheValuePrefix + invocation.getArgument(0));
42+
cache = new LazyCache<>(4, expireSeconds, mockLoader);
43+
}
44+
45+
@Test
46+
public void testCacheMissAndLoader() {
47+
String key = "key1";
48+
String value = cache.get(key);
49+
assertEquals(cacheValuePrefix + key, value);
50+
Mockito.verify(mockLoader).apply(key);
51+
}
52+
53+
@Test
54+
public void testLoaderNotCalledIfPresent() {
55+
String key = "key2";
56+
cache.get(key);
57+
try {
58+
Thread.sleep((long)(0.9 * expireSeconds * 1000));
59+
} catch (InterruptedException ie) {
60+
Assert.fail(String.format("Exception occurred: %s", ie.getMessage()));
61+
}
62+
cache.get(key);
63+
Mockito.verify(mockLoader, Mockito.times(1)).apply(key);
64+
}
65+
66+
@Test
67+
public void testCacheExpiration() {
68+
String key = "key3";
69+
cache.get(key);
70+
try {
71+
Thread.sleep((long)(1.1 * expireSeconds * 1000));
72+
} catch (InterruptedException ie) {
73+
Assert.fail(String.format("Exception occurred: %s", ie.getMessage()));
74+
}
75+
cache.get(key);
76+
Mockito.verify(mockLoader, Mockito.times(2)).apply(key);
77+
}
78+
79+
@Test
80+
public void testInvalidateKey() {
81+
String key = "key4";
82+
cache.get(key);
83+
cache.invalidate(key);
84+
cache.get(key);
85+
Mockito.verify(mockLoader, Mockito.times(2)).apply(key);
86+
}
87+
88+
@Test
89+
public void testClearCache() {
90+
String key1 = "key5";
91+
String key2 = "key6";
92+
cache.get(key1);
93+
cache.get(key2);
94+
cache.clear();
95+
cache.get(key1);
96+
Mockito.verify(mockLoader, Mockito.times(2)).apply(key1);
97+
Mockito.verify(mockLoader, Mockito.times(1)).apply(key2);
98+
}
99+
100+
@Test
101+
public void testMaximumSize() {
102+
String key = "key7";
103+
cache.get(key);
104+
for (int i = 0; i < 4; i++) {
105+
cache.get(String.format("newkey-%d", i));
106+
}
107+
try {
108+
Thread.sleep(100);
109+
} catch (InterruptedException ie) {
110+
Assert.fail(String.format("Exception occurred: %s", ie.getMessage()));
111+
}
112+
cache.get(key);
113+
Mockito.verify(mockLoader, Mockito.times(2)).apply(key);
114+
}
115+
}

0 commit comments

Comments
 (0)