Fix Fingerprinter
Change-Id: Ie6ad71a184aef656781837ebb2e9b5031bef500a
diff --git a/Changes b/Changes
index fbe8c4c..606d9aa 100644
--- a/Changes
+++ b/Changes
@@ -1,4 +1,5 @@
- [performance] Add leaf cache. (diewald)
+ - [bugfix] Fix fingerprinter (wasn't threadsafe; diewald)
0.64.5 2025-12-03
- [maintenance] Update to Java 21 (diewald)
diff --git a/src/main/java/de/ids_mannheim/korap/util/Fingerprinter.java b/src/main/java/de/ids_mannheim/korap/util/Fingerprinter.java
index 52d0d57..980894d 100644
--- a/src/main/java/de/ids_mannheim/korap/util/Fingerprinter.java
+++ b/src/main/java/de/ids_mannheim/korap/util/Fingerprinter.java
@@ -12,22 +12,15 @@
private final static Logger log = LoggerFactory
.getLogger(Fingerprinter.class);
- private static MessageDigest md;
-
public static String create (String key) {
try {
- md = MessageDigest.getInstance("MD5");
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ md.update(key.getBytes());
+ return new String(Base64.getUrlEncoder().encode(md.digest()));
}
catch (NoSuchAlgorithmException e) {
log.error(e.getMessage());
return e.getMessage();
- };
-
- md.update(key.getBytes());
- String code = new String(Base64.getUrlEncoder().encode(md.digest()));
-
- md.reset();
- return code;
-
+ }
}
}
diff --git a/src/test/java/de/ids_mannheim/korap/util/TestKrillFingerprint.java b/src/test/java/de/ids_mannheim/korap/util/TestKrillFingerprint.java
new file mode 100644
index 0000000..c0f53a3
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/util/TestKrillFingerprint.java
@@ -0,0 +1,60 @@
+package de.ids_mannheim.korap.util;
+
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import org.junit.Test;
+
+public class TestKrillFingerprint {
+
+ @Test
+ public void testMessageDigestThreadSafety() throws Exception {
+
+ int threads = 16;
+ int iterationsPerThread = 5_000;
+
+ ExecutorService pool = Executors.newFixedThreadPool(threads);
+
+ CountDownLatch start = new CountDownLatch(1);
+ CountDownLatch done = new CountDownLatch(threads);
+
+ List<Throwable> errors = new ArrayList<>();
+
+ for (int t = 0; t < threads; t++) {
+ pool.submit(() -> {
+ try {
+ start.await();
+ for (int i = 0; i < iterationsPerThread; i++) {
+ Fingerprinter.create("key-" + i);
+ }
+ }
+ catch (Throwable e) {
+ synchronized (errors) {
+ errors.add(e);
+ }
+ }
+ finally {
+ done.countDown();
+ }
+ });
+ }
+
+ start.countDown();
+ done.await();
+ pool.shutdown();
+
+ if (!errors.isEmpty()) {
+ errors.forEach(Throwable::printStackTrace);
+ }
+
+ assertTrue(
+ "Thread-safety violation detected: " + errors,
+ errors.isEmpty()
+ );
+ }
+}