Skip to content

Commit d4aa1cf

Browse files
1 parent c4caf6d commit d4aa1cf

File tree

2 files changed

+54
-1
lines changed

2 files changed

+54
-1
lines changed

src/main/kotlin/com/statsig/sdk/Hashing.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ enum class HashAlgo {
1414

1515
class Hashing {
1616
companion object {
17+
private val threadLocalDigest: ThreadLocal<MessageDigest> = ThreadLocal.withInitial {
18+
MessageDigest.getInstance("SHA-256")
19+
}
20+
1721
fun djb2(input: String): String {
1822
var hash = 0
1923
for (element in input) {
@@ -36,7 +40,8 @@ class Hashing {
3640
}
3741

3842
fun sha256ToLong(input: String): Long {
39-
val md = MessageDigest.getInstance("SHA-256")
43+
val md = threadLocalDigest.get()
44+
md.reset()
4045
val hashBytes = md.digest(input.toByteArray(Charsets.UTF_8))
4146
// Combine more bytes to increase randomness
4247
val buffer = ByteBuffer.wrap(hashBytes)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package com.statsig.sdk
2+
3+
import org.junit.Assert.assertEquals
4+
import org.junit.Test
5+
import java.nio.ByteBuffer
6+
import java.security.MessageDigest
7+
import java.util.concurrent.Executors
8+
import java.util.concurrent.TimeUnit
9+
import kotlin.math.absoluteValue
10+
11+
class ThreadLocalHashingTest {
12+
13+
@Test
14+
fun testSingleSha256ToLong() {
15+
val input = "test"
16+
val expected = generateSha256ToLongExpected(input)
17+
val result = Hashing.sha256ToLong(input)
18+
assertEquals(expected, result)
19+
}
20+
21+
@Test
22+
fun testConcurrentSha256ToLong() {
23+
val executor = Executors.newFixedThreadPool(10)
24+
val tasks = List(10) {
25+
Runnable {
26+
repeat(100) {
27+
val input = "test$it"
28+
val expected = generateSha256ToLongExpected(input)
29+
val result = Hashing.sha256ToLong(input)
30+
assertEquals(expected, result)
31+
}
32+
}
33+
}
34+
tasks.forEach { executor.submit(it) }
35+
executor.shutdown()
36+
executor.awaitTermination(1, TimeUnit.MINUTES)
37+
}
38+
39+
private fun generateSha256ToLongExpected(input: String): Long {
40+
val md = MessageDigest.getInstance("SHA-256")
41+
val hashBytes = md.digest(input.toByteArray(Charsets.UTF_8))
42+
val buffer = ByteBuffer.wrap(hashBytes)
43+
val high = buffer.long
44+
val low = buffer.long
45+
46+
return (high xor low).absoluteValue
47+
}
48+
}

0 commit comments

Comments
 (0)