Skip to content

Commit c2a39b9

Browse files
authored
Migrating cache to caffeine (#523)
* testing cache ttl * testing * adjusting test * release notes * clean codfe * clean code * clean code * [Gradle Release Plugin] - new version commit: '3.25.2-snapshot'.
1 parent e66a6fc commit c2a39b9

File tree

5 files changed

+145
-18
lines changed

5 files changed

+145
-18
lines changed

RELEASE-NOTES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
## 3.25.2
2+
* Migrating cache to caffeine #522
3+
14
## 3.25.1
25
* Ensure thread will have name at the logs, behavior changed since `3.25.0` #436.
36

build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ dependencies {
7070
implementation('info.picocli:picocli:4.7.1')
7171
implementation('com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.17.1')
7272

73+
implementation('com.github.ben-manes.caffeine:caffeine:3.1.8')
74+
7375
testAnnotationProcessor("com.google.dagger:dagger-compiler:2.45")
7476

7577
testCompileOnly('org.projectlombok:lombok:1.18.+')

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
version=3.25.1-snapshot
1+
version=3.25.2-snapshot

src/main/java/com/mageddo/dnsproxyserver/solver/SolverCache.java

Lines changed: 69 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
package com.mageddo.dnsproxyserver.solver;
22

3-
import com.mageddo.commons.caching.LruTTLCache;
3+
import com.github.benmanes.caffeine.cache.Cache;
4+
import com.github.benmanes.caffeine.cache.Caffeine;
5+
import com.github.benmanes.caffeine.cache.Expiry;
46
import com.mageddo.commons.lang.Objects;
5-
import com.mageddo.commons.lang.tuple.Pair;
67
import com.mageddo.dns.utils.Messages;
78
import com.mageddo.dnsproxyserver.solver.CacheName.Name;
8-
import lombok.RequiredArgsConstructor;
9+
import lombok.Builder;
10+
import lombok.Value;
911
import lombok.extern.slf4j.Slf4j;
1012
import org.xbill.DNS.Message;
1113

1214
import java.time.Duration;
15+
import java.time.LocalDateTime;
1316
import java.util.HashMap;
1417
import java.util.HashSet;
1518
import java.util.Map;
@@ -20,20 +23,26 @@
2023
import static com.mageddo.dns.utils.Messages.findQuestionType;
2124

2225
@Slf4j
23-
@RequiredArgsConstructor
2426
public class SolverCache {
2527

26-
private final LruTTLCache cache = new LruTTLCache(2048, Duration.ofSeconds(5), false);
27-
2828
private final Name name;
29+
private final Cache<String, CacheValue> cache;
30+
31+
public SolverCache(Name name) {
32+
this.name = name;
33+
this.cache = Caffeine.newBuilder()
34+
.maximumSize(2048)
35+
.expireAfter(buildExpiryPolicy())
36+
.build();
37+
}
2938

3039
public Message handle(Message query, Function<Message, Response> delegate) {
3140
return Objects.mapOrNull(this.handleRes(query, delegate), Response::getMessage);
3241
}
3342

3443
public Response handleRes(Message query, Function<Message, Response> delegate) {
3544
final var key = buildKey(query);
36-
final var res = this.cache.computeIfAbsentWithTTL(key, (k) -> {
45+
final var cacheValue = this.cache.get(key, (k) -> {
3746
log.trace("status=lookup, key={}, req={}", key, Messages.simplePrint(query));
3847
final var _res = delegate.apply(query);
3948
if (_res == null) {
@@ -42,12 +51,13 @@ public Response handleRes(Message query, Function<Message, Response> delegate) {
4251
}
4352
final var ttl = _res.getDpsTtl();
4453
log.debug("status=hotload, k={}, ttl={}, simpleMsg={}", k, ttl, Messages.simplePrint(query));
45-
return Pair.of(_res, ttl);
54+
return CacheValue.of(_res, ttl);
4655
});
47-
if (res == null) {
56+
if (cacheValue == null) {
4857
return null;
4958
}
50-
return res.withMessage(Messages.mergeId(query, res.getMessage()));
59+
final var response = cacheValue.getResponse();
60+
return response.withMessage(Messages.mergeId(query, response.getMessage()));
5161
}
5262

5363
static String buildKey(Message reqMsg) {
@@ -56,11 +66,11 @@ static String buildKey(Message reqMsg) {
5666
}
5767

5868
public int getSize() {
59-
return this.cache.getSize();
69+
return (int) this.cache.estimatedSize();
6070
}
6171

6272
public void clear() {
63-
this.cache.clear();
73+
this.cache.invalidateAll();
6474
}
6575

6676
public Map<String, CacheEntry> asMap() {
@@ -82,4 +92,51 @@ public Name name() {
8292
return this.name;
8393
}
8494

95+
public CacheValue get(String key) {
96+
return this.cache.getIfPresent(key);
97+
}
98+
99+
@Value
100+
@Builder
101+
static class CacheValue {
102+
103+
private Response response;
104+
private Duration ttl;
105+
106+
public static CacheValue of(Response res, Duration ttl) {
107+
return CacheValue
108+
.builder()
109+
.response(res)
110+
.ttl(ttl)
111+
.build();
112+
}
113+
114+
public LocalDateTime getExpiresAt() {
115+
return this.response
116+
.getCreatedAt()
117+
.plus(this.ttl)
118+
;
119+
}
120+
}
121+
122+
private static Expiry<String, CacheValue> buildExpiryPolicy() {
123+
return new Expiry<>() {
124+
@Override
125+
public long expireAfterCreate(String key, CacheValue value, long currentTime) {
126+
return value.getTtl().toNanos();
127+
}
128+
129+
@Override
130+
public long expireAfterUpdate(String key, CacheValue value, long currentTime, long currentDuration) {
131+
return currentDuration;
132+
}
133+
134+
@Override
135+
public long expireAfterRead(String key, CacheValue value, long currentTime, long currentDuration) {
136+
return currentDuration;
137+
}
138+
};
139+
}
140+
141+
85142
}

src/test/java/com/mageddo/dnsproxyserver/solver/SolversCacheTest.java

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
11
package com.mageddo.dnsproxyserver.solver;
22

3+
import com.mageddo.commons.concurrent.Threads;
34
import com.mageddo.dns.utils.Messages;
45
import com.mageddo.dnsproxyserver.solver.CacheName.Name;
5-
import com.mageddo.dnsproxyserver.solver.Response;
6-
import com.mageddo.dnsproxyserver.solver.SolverCache;
7-
import testing.templates.MessageTemplates;
6+
import lombok.SneakyThrows;
87
import org.junit.jupiter.api.Test;
98
import org.junit.jupiter.api.extension.ExtendWith;
109
import org.mockito.junit.jupiter.MockitoExtension;
1110
import org.xbill.DNS.Flags;
11+
import org.xbill.DNS.Message;
12+
import testing.templates.MessageTemplates;
13+
14+
import java.time.Duration;
15+
import java.util.ArrayList;
16+
import java.util.Random;
17+
import java.util.concurrent.Callable;
18+
import java.util.concurrent.Executors;
1219

1320
import static org.junit.jupiter.api.Assertions.assertEquals;
1421
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -21,7 +28,39 @@ class SolversCacheTest {
2128
SolverCache cache = new SolverCache(Name.GLOBAL);
2229

2330
@Test
24-
void mustCacheAndGetValidResponse(){
31+
void mustLeadWithConcurrency() {
32+
33+
// arrange
34+
final var req = MessageTemplates.acmeAQuery();
35+
final var r = new Random();
36+
37+
// act
38+
concurrentRequests(1_000, req, r);
39+
40+
}
41+
42+
@Test
43+
void mustCacheForTheSpecifiedTime() {
44+
45+
// arrange
46+
final var req = MessageTemplates.acmeAQuery();
47+
final var key = "A-acme.com";
48+
49+
// act
50+
final var res = this.cache.handleRes(req, message -> {
51+
return Response.of(Messages.aAnswer(message, "0.0.0.0"), Duration.ofMillis(50));
52+
});
53+
54+
// assert
55+
assertNotNull(res);
56+
assertNotNull(this.cache.get(key));
57+
58+
Threads.sleep(res.getDpsTtl().plusMillis(10));
59+
assertNull(this.cache.get(key));
60+
}
61+
62+
@Test
63+
void mustCacheAndGetValidResponse() {
2564

2665
// arrange
2766
final var req = MessageTemplates.acmeAQuery();
@@ -40,7 +79,7 @@ void mustCacheAndGetValidResponse(){
4079
}
4180

4281
@Test
43-
void cantCacheWhenDelegateSolverHasNoAnswer(){
82+
void cantCacheWhenDelegateSolverHasNoAnswer() {
4483
// arrange
4584
final var query = MessageTemplates.acmeAQuery();
4685

@@ -52,4 +91,30 @@ void cantCacheWhenDelegateSolverHasNoAnswer(){
5291
assertEquals(0, this.cache.getSize());
5392
}
5493

94+
@SneakyThrows
95+
private void concurrentRequests(int quantity, Message req, Random r) {
96+
final var runnables = new ArrayList<Callable<Object>>();
97+
for (int i = 0; i < quantity; i++) {
98+
runnables.add(() -> this.handleRequest(req, r));
99+
if (i % 10 == 0) {
100+
runnables.add(() -> {
101+
this.cache.clear();
102+
return null;
103+
});
104+
}
105+
}
106+
107+
try (final var executor = Executors.newVirtualThreadPerTaskExecutor()) {
108+
executor.invokeAll(runnables);
109+
}
110+
}
111+
112+
private Object handleRequest(Message req, Random r) {
113+
this.cache.handleRes(req, message -> {
114+
final var res = Response.internalSuccess(Messages.aAnswer(message, "0.0.0.0"));
115+
Threads.sleep(r.nextInt(10));
116+
return res;
117+
});
118+
return null;
119+
}
55120
}

0 commit comments

Comments
 (0)