Skip to content

Commit 7723c2b

Browse files
authored
RATIS-2266. Use WeakValueCache instead of Guava cache in RaftId. (#1240)
1 parent ac35188 commit 7723c2b

File tree

7 files changed

+254
-16
lines changed

7 files changed

+254
-16
lines changed

ratis-common/src/main/java/org/apache/ratis/protocol/ClientId.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package org.apache.ratis.protocol;
1919

2020
import org.apache.ratis.thirdparty.com.google.protobuf.ByteString;
21+
import org.apache.ratis.util.WeakValueCache;
2122

2223
import java.util.UUID;
2324

@@ -26,13 +27,17 @@
2627
* to correctly identify retry requests from the same client.
2728
*/
2829
public final class ClientId extends RaftId {
29-
private static final Factory<ClientId> FACTORY = new Factory<ClientId>() {
30+
private static final Factory<ClientId> FACTORY = new Factory<ClientId>(ClientId.class) {
3031
@Override
3132
ClientId newInstance(UUID uuid) {
3233
return new ClientId(uuid);
3334
}
3435
};
3536

37+
static WeakValueCache<UUID, ClientId> getCache() {
38+
return FACTORY.getCache();
39+
}
40+
3641
public static ClientId emptyClientId() {
3742
return FACTORY.emptyId();
3843
}

ratis-common/src/main/java/org/apache/ratis/protocol/RaftGroupId.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package org.apache.ratis.protocol;
1919

2020
import org.apache.ratis.thirdparty.com.google.protobuf.ByteString;
21+
import org.apache.ratis.util.WeakValueCache;
2122

2223
import java.util.UUID;
2324

@@ -27,13 +28,17 @@
2728
* This is a value-based class.
2829
*/
2930
public final class RaftGroupId extends RaftId {
30-
private static final Factory<RaftGroupId> FACTORY = new Factory<RaftGroupId>() {
31+
private static final Factory<RaftGroupId> FACTORY = new Factory<RaftGroupId>(RaftGroupId.class) {
3132
@Override
3233
RaftGroupId newInstance(UUID uuid) {
3334
return new RaftGroupId(uuid);
3435
}
3536
};
3637

38+
static WeakValueCache<UUID, RaftGroupId> getCache() {
39+
return FACTORY.getCache();
40+
}
41+
3742
public static RaftGroupId emptyGroupId() {
3843
return FACTORY.emptyId();
3944
}

ratis-common/src/main/java/org/apache/ratis/protocol/RaftId.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,15 @@
1717
*/
1818
package org.apache.ratis.protocol;
1919

20-
import org.apache.ratis.thirdparty.com.google.common.cache.Cache;
21-
import org.apache.ratis.thirdparty.com.google.common.cache.CacheBuilder;
2220
import org.apache.ratis.thirdparty.com.google.protobuf.ByteString;
2321
import org.apache.ratis.thirdparty.com.google.protobuf.UnsafeByteOperations;
2422
import org.apache.ratis.util.JavaUtils;
2523
import org.apache.ratis.util.Preconditions;
24+
import org.apache.ratis.util.WeakValueCache;
2625

2726
import java.nio.ByteBuffer;
2827
import java.util.Objects;
2928
import java.util.UUID;
30-
import java.util.concurrent.ExecutionException;
3129
import java.util.function.Supplier;
3230

3331
/** Unique identifier implemented using {@link UUID}. */
@@ -53,18 +51,20 @@ static ByteString toByteString(UUID uuid) {
5351
}
5452

5553
abstract static class Factory<ID extends RaftId> {
56-
private final Cache<UUID, ID> cache = CacheBuilder.newBuilder()
57-
.weakValues()
58-
.build();
54+
private final WeakValueCache<UUID, ID> cache;
55+
56+
Factory(Class<ID> clazz) {
57+
this.cache = new WeakValueCache<>(clazz.getSimpleName() + "_UUID", this::newInstance);
58+
}
5959

6060
abstract ID newInstance(UUID uuid);
6161

62+
WeakValueCache<UUID, ID> getCache() {
63+
return cache;
64+
}
65+
6266
final ID valueOf(UUID uuid) {
63-
try {
64-
return cache.get(uuid, () -> newInstance(uuid));
65-
} catch (ExecutionException e) {
66-
throw new IllegalStateException("Failed to valueOf(" + uuid + ")", e);
67-
}
67+
return cache.getOrCreate(uuid);
6868
}
6969

7070
final ID valueOf(ByteString bytes) {

ratis-common/src/main/java/org/apache/ratis/util/BiWeakValueCache.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,15 @@
3333
* Note that the cached values are weakly referenced.
3434
* A cached value could be garage-collected (i.e. evicted from the cache)
3535
* when there are no external (strong) references.
36+
* <p>
37+
* For key types with a component, use {@link WeakValueCache}.
3638
*
3739
* @param <OUTER> the type of the outer keys.
3840
* @param <INNER> the type of the inner keys.
3941
* @param <T> the type to be cached.
4042
*/
4143
public final class BiWeakValueCache<OUTER, INNER, T> {
42-
private static <K, V> ConcurrentMap<K, V> newMap() {
44+
static <K, V> ConcurrentMap<K, V> newMap() {
4345
return new MapMaker().weakValues().makeMap();
4446
}
4547

@@ -61,8 +63,8 @@ private static <K, V> ConcurrentMap<K, V> newMap() {
6163
/**
6264
* Create a cache for mapping ({@link OUTER}, {@link INNER}) keys to {@link T} values.
6365
*
64-
* @param outerName the name of the outer long.
65-
* @param innerName the name of the inner long.
66+
* @param outerName the name of the outer keys.
67+
* @param innerName the name of the inner keys.
6668
* @param constructor for constructing {@link T} values.
6769
*/
6870
public BiWeakValueCache(String outerName, String innerName, BiFunction<OUTER, INNER, T> constructor) {
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.apache.ratis.util;
19+
20+
import java.util.ArrayList;
21+
import java.util.List;
22+
import java.util.Objects;
23+
import java.util.concurrent.ConcurrentMap;
24+
import java.util.concurrent.atomic.AtomicInteger;
25+
import java.util.function.Function;
26+
27+
import static org.apache.ratis.util.BiWeakValueCache.newMap;
28+
29+
/**
30+
* Weak Value Cache: {@link K} -> {@link V}.
31+
* <p>
32+
* Note that the cached values are weakly referenced.
33+
* A cached value could be garage-collected (i.e. evicted from the cache)
34+
* when there are no external (strong) references.
35+
* <p>
36+
* For key types with two components, use {@link BiWeakValueCache}.
37+
*
38+
* @param <K> the type of the keys.
39+
* @param <V> the type to be cached values.
40+
*/
41+
public final class WeakValueCache<K, V> {
42+
private final String keyName;
43+
private final String name;
44+
45+
/** For constructing a value from a key. */
46+
private final Function<K, V> constructor;
47+
/** Count the number of values constructed. */
48+
private final AtomicInteger constructionCount = new AtomicInteger(0);
49+
50+
/** Map: {@link K} -> {@link V}. */
51+
private final ConcurrentMap<K, V> map = newMap();
52+
53+
/**
54+
* Create a cache for mapping {@link K} keys to {@link V} values.
55+
*
56+
* @param keyName the name of the key.
57+
* @param constructor for constructing {@link V} values.
58+
*/
59+
public WeakValueCache(String keyName, Function<K, V> constructor) {
60+
this.keyName = keyName;
61+
this.name = keyName + "-cache";
62+
this.constructor = constructor;
63+
}
64+
65+
private V construct(K key) {
66+
final V constructed = constructor.apply(key);
67+
Objects.requireNonNull(constructed, "constructed == null");
68+
constructionCount.incrementAndGet();
69+
return constructed;
70+
}
71+
72+
/**
73+
* If the given key is in the cache, return its cached values.
74+
* Otherwise, create a new value, put it in the cache and then return it.
75+
*/
76+
public V getOrCreate(K key) {
77+
Objects.requireNonNull(key, () -> keyName + " (key) == null");
78+
return map.computeIfAbsent(key, this::construct);
79+
}
80+
81+
List<V> getValues() {
82+
return new ArrayList<>(map.values());
83+
}
84+
85+
@Override
86+
public String toString() {
87+
return name;
88+
}
89+
}

ratis-test/src/test/java/org/apache/ratis/protocol/TestRaftId.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import org.apache.ratis.BaseTest;
2121
import org.apache.ratis.thirdparty.com.google.protobuf.ByteString;
22+
import org.apache.ratis.util.WeakValueCache;
2223
import org.junit.jupiter.api.Assertions;
2324
import org.junit.jupiter.api.Test;
2425
import org.junit.jupiter.api.Timeout;
@@ -27,6 +28,13 @@
2728

2829
@Timeout(value = 1)
2930
public class TestRaftId extends BaseTest {
31+
public static WeakValueCache<UUID, ClientId> getClientIdCache() {
32+
return ClientId.getCache();
33+
}
34+
35+
public static WeakValueCache<UUID, RaftGroupId> getRaftGroupIdCache() {
36+
return RaftGroupId.getCache();
37+
}
3038

3139
@Test
3240
public void testRaftId() {
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.apache.ratis.util;
19+
20+
import org.apache.ratis.BaseTest;
21+
import org.apache.ratis.RaftTestUtil;
22+
import org.apache.ratis.protocol.ClientId;
23+
import org.apache.ratis.protocol.TestRaftId;
24+
import org.junit.jupiter.api.Test;
25+
26+
import java.util.ArrayList;
27+
import java.util.Comparator;
28+
import java.util.LinkedList;
29+
import java.util.List;
30+
import java.util.UUID;
31+
import java.util.concurrent.ThreadLocalRandom;
32+
33+
import static org.junit.jupiter.api.Assertions.assertEquals;
34+
import static org.junit.jupiter.api.Assertions.assertSame;
35+
36+
/** Testing {@link WeakValueCache}. */
37+
public class TestRaftIdCache extends BaseTest {
38+
static WeakValueCache<UUID, ClientId> CACHE = TestRaftId.getClientIdCache();
39+
40+
static String dumpCache() {
41+
final List<ClientId> values = CACHE.getValues();
42+
values.sort(Comparator.comparing(ClientId::getUuid));
43+
String header = CACHE + ": " + values.size();
44+
System.out.println(header);
45+
System.out.println(" " + values);
46+
return header;
47+
}
48+
49+
static void assertCache(IDs expectedIDs) {
50+
final List<ClientId> computed = CACHE.getValues();
51+
computed.sort(Comparator.comparing(ClientId::getUuid));
52+
53+
final List<ClientId> expected = expectedIDs.getIds();
54+
expected.sort(Comparator.comparing(ClientId::getUuid));
55+
56+
assertEquals(expected, computed, TestRaftIdCache::dumpCache);
57+
}
58+
59+
void assertCacheSizeWithGC(IDs expectedIDs) throws Exception{
60+
JavaUtils.attempt(() -> {
61+
RaftTestUtil.gc();
62+
assertCache(expectedIDs);
63+
}, 5, HUNDRED_MILLIS, "assertCacheSizeWithGC", LOG);
64+
}
65+
66+
class IDs {
67+
private final List<ClientId> ids = new LinkedList<>();
68+
69+
List<ClientId> getIds() {
70+
return new ArrayList<>(ids);
71+
}
72+
73+
int size() {
74+
return ids.size();
75+
}
76+
77+
ClientId allocate() {
78+
final ClientId id = ClientId.randomId();
79+
LOG.info("allocate {}", id);
80+
ids.add(id);
81+
return id;
82+
}
83+
84+
void release() {
85+
final int r = ThreadLocalRandom.current().nextInt(size());
86+
final ClientId removed = ids.remove(r);
87+
LOG.info("release {}", removed);
88+
}
89+
}
90+
91+
@Test
92+
public void testCaching() throws Exception {
93+
final int n = 100;
94+
final IDs ids = new IDs();
95+
assertEquals(0, ids.size());
96+
assertCache(ids);
97+
98+
for(int i = 0; i < n; i++) {
99+
final ClientId id = ids.allocate();
100+
assertSame(id, ClientId.valueOf(id.getUuid()));
101+
assertCache(ids);
102+
}
103+
104+
for(int i = 0; i < n/2; i++) {
105+
ids.release();
106+
if (ThreadLocalRandom.current().nextInt(10) == 0) {
107+
assertCacheSizeWithGC(ids);
108+
}
109+
}
110+
assertCacheSizeWithGC(ids);
111+
112+
for(int i = 0; i < n/2; i++) {
113+
final ClientId id = ids.allocate();
114+
assertSame(id, ClientId.valueOf(id.getUuid()));
115+
assertCache(ids);
116+
}
117+
118+
119+
for(int i = 0; i < n; i++) {
120+
ids.release();
121+
if (ThreadLocalRandom.current().nextInt(10) == 0) {
122+
assertCacheSizeWithGC(ids);
123+
}
124+
}
125+
assertCacheSizeWithGC(ids);
126+
127+
assertEquals(0, ids.size());
128+
}
129+
}

0 commit comments

Comments
 (0)