Skip to content

Commit 3cd4a34

Browse files
committed
GEOMESA-3439 Converters - fix thread-safety of md5 function (#3265)
1 parent 1fec6a4 commit 3cd4a34

File tree

4 files changed

+72
-2
lines changed

4 files changed

+72
-2
lines changed

build/copyright/ccri-2025.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Copyright (c) ${project.inceptionYear}-2025 ${owner}
2+
All rights reserved. This program and the accompanying materials
3+
are made available under the terms of the Apache License, Version 2.0
4+
which accompanies this distribution and is available at
5+
http://www.opensource.org/licenses/apache2.0.php.

geomesa-convert/geomesa-convert-common/src/main/scala/org/locationtech/geomesa/convert2/transforms/IdFunctionFactory.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,16 @@ class IdFunctionFactory extends TransformerFunctionFactory with LazyLogging {
6161
}
6262

6363
private val md5: TransformerFunction = new NamedTransformerFunction(Seq("md5"), pure = true) {
64-
private val hasher = MessageDigest.getInstance("MD5")
64+
private val hashers = new ThreadLocal[MessageDigest]() {
65+
override def initialValue(): MessageDigest = MessageDigest.getInstance("MD5")
66+
}
6567
override def apply(args: Array[AnyRef]): AnyRef = {
6668
val bytes = args(0) match {
6769
case s: String => s.getBytes(StandardCharsets.UTF_8)
6870
case b: Array[Byte] => b
6971
case a => throw new IllegalArgumentException(s"Expected String or byte[] but got: $a")
7072
}
71-
ByteArrays.toHex(hasher.digest(bytes))
73+
ByteArrays.toHex(hashers.get.digest(bytes))
7274
}
7375
}
7476

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/***********************************************************************
2+
* Copyright (c) 2013-2025 Commonwealth Computer Research, Inc.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Apache License, Version 2.0
5+
* which accompanies this distribution and is available at
6+
* http://www.opensource.org/licenses/apache2.0.php.
7+
***********************************************************************/
8+
9+
package org.locationtech.geomesa.convert2.transforms
10+
11+
import org.locationtech.geomesa.utils.concurrent.CachedThreadPool
12+
import org.specs2.matcher.MatchResult
13+
import org.specs2.mutable.Specification
14+
15+
import java.nio.charset.StandardCharsets
16+
import java.util.Collections
17+
import java.util.concurrent.{ConcurrentHashMap, CopyOnWriteArrayList, TimeUnit}
18+
import scala.util.Random
19+
20+
class IdFunctionFactoryTest extends Specification {
21+
22+
"IdFunctionFactoryTest" should {
23+
"generate hashes in a thread-safe way" in {
24+
testHash("md5")
25+
testHash("murmurHash3")
26+
testHash("murmur3_32")
27+
testHash("murmur3_64")
28+
}
29+
}
30+
31+
def testHash(alg: String): MatchResult[_] = {
32+
val exp = Expression(s"$alg($$0)")
33+
val results = Array.fill(3)(Collections.newSetFromMap(new ConcurrentHashMap[AnyRef, java.lang.Boolean]()))
34+
val exceptions = new CopyOnWriteArrayList[Throwable]()
35+
val runnables = Array("foo", "bar", "blubaz").map(_.getBytes(StandardCharsets.UTF_8)).zipWithIndex.map { case (input, i) =>
36+
new Runnable() {
37+
override def run(): Unit = {
38+
try { results(i).add(exp.apply(Array(input))) } catch {
39+
case e: Throwable => exceptions.add(e)
40+
}
41+
}
42+
}
43+
}
44+
// baseline results
45+
runnables.foreach(_.run())
46+
47+
val r = new Random(-1)
48+
val pool = new CachedThreadPool(10)
49+
try {
50+
var i = 0
51+
while (i < 1000) {
52+
pool.submit(runnables(r.nextInt(3)))
53+
i += 1
54+
}
55+
} finally {
56+
pool.shutdown()
57+
}
58+
pool.awaitTermination(1, TimeUnit.SECONDS)
59+
foreach(results)(_ must haveSize(1))
60+
exceptions must haveSize(0)
61+
}
62+
}

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3143,6 +3143,7 @@
31433143
<configuration>
31443144
<header>build/copyright/ccri.txt</header>
31453145
<validHeaders>
3146+
<validHeader>build/copyright/ccri-2025.txt</validHeader>
31463147
<validHeader>build/copyright/ccri-azavea.txt</validHeader>
31473148
<validHeader>build/copyright/ccri-dstl.txt</validHeader>
31483149
<validHeader>build/copyright/ccri-dstl-mitre.txt</validHeader>

0 commit comments

Comments
 (0)