Wrap CacheKeyGenerator MessageDigest in ThreadLocal (#2829)

Co-authored-by: Simone Gerevini <sgerevini@atrify.com>
This commit is contained in:
Simone Gerevini
2023-01-18 20:51:32 +01:00
committed by GitHub
parent b0d9c9af1d
commit 5ca26cdebc
2 changed files with 61 additions and 8 deletions

View File

@@ -31,6 +31,7 @@ import org.springframework.http.server.reactive.ServerHttpRequest;
/**
* @author Marta Medio
* @author Ignacio Lozano
* @author Simone Gerevini
*/
public class CacheKeyGenerator {
@@ -38,19 +39,21 @@ public class CacheKeyGenerator {
private static final byte[] KEY_SEPARATOR_BYTES = KEY_SEPARATOR.getBytes();
private final MessageDigest messageDigest;
private final ThreadLocal<MessageDigest> messageDigest;
/* for testing */ static final List<KeyValueGenerator> DEFAULT_KEY_VALUE_GENERATORS = List.of(
new UriKeyValueGenerator(), new HeaderKeyValueGenerator(HttpHeaders.AUTHORIZATION, KEY_SEPARATOR),
new CookiesKeyValueGenerator(KEY_SEPARATOR));
public CacheKeyGenerator() {
try {
messageDigest = MessageDigest.getInstance("MD5");
}
catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Error creating CacheKeyGenerator", e);
}
messageDigest = ThreadLocal.withInitial(() -> {
try {
return MessageDigest.getInstance("MD5");
}
catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Error creating CacheKeyGenerator", e);
}
});
}
public String generateMetadataKey(ServerHttpRequest request, String... varyHeaders) {
@@ -63,7 +66,7 @@ public class CacheKeyGenerator {
public String generateKey(ServerHttpRequest request, List<String> varyHeaders) {
byte[] rawKey = generateRawKey(request, varyHeaders);
byte[] digest = messageDigest.digest(rawKey);
byte[] digest = messageDigest.get().digest(rawKey);
return Base64.getEncoder().encodeToString(digest);
}

View File

@@ -16,7 +16,11 @@
package org.springframework.cloud.gateway.filter.factory.cache;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import org.junit.jupiter.api.Test;
@@ -30,6 +34,7 @@ import static org.springframework.http.HttpHeaders.AUTHORIZATION;
/**
* @author Ignacio Lozano
* @author Simone Gerevini
*/
class CacheKeyGeneratorTest {
@@ -201,4 +206,49 @@ class CacheKeyGeneratorTest {
assertThat(keyWithoutVary).isEqualTo(keyWithFirstVary);
}
@Test
public void shouldNotFailWhenRunningInParallel() throws InterruptedException {
MockServerHttpRequest request = MockServerHttpRequest.get("http://this").build();
int numberOfThreads = 100;
List<Exception> exceptions = executeInParallel(Executors.newFixedThreadPool(numberOfThreads), numberOfThreads,
() -> cacheKeyGenerator.generateKey(request));
assertThat(exceptions.size()).isEqualTo(0);
}
private List<Exception> executeInParallel(Executor executor, int nThreads, Runnable action)
throws InterruptedException {
CountDownLatch ready = new CountDownLatch(nThreads);
CountDownLatch start = new CountDownLatch(1);
CountDownLatch done = new CountDownLatch(nThreads);
List<Exception> exceptions = new ArrayList<>(nThreads);
for (int i = 0; i < nThreads; i++) {
executor.execute(() -> {
ready.countDown();
try {
start.await();
action.run();
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
catch (RuntimeException e) {
exceptions.add(e);
}
finally {
done.countDown();
}
});
}
ready.await();
start.countDown();
done.await();
return exceptions;
}
}