Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions actor/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -1145,11 +1145,13 @@ pekko {
# other platforms, will default to 1).
ndots = default

# The policy used to generate dns transaction ids. Options are thread-local-random or secure-random.
# Defaults to thread-local-random similar to Netty, secure-random produces FIPS compliant random numbers but
# could block looking for entropy (these are short integers so are easy to bruit-force, use thread-local-random
# unless you really require FIPS compliant random numbers).
id-generator-policy = thread-local-random
# The policy used to generate dns transaction ids. Options are `thread-local-random`,
# `enhanced-double-hash-random` or `secure-random`. Defaults to `enhanced-double-hash-random` which uses an
# enhanced double hashing algorithm optimized for minimizing collisions with a FIPS compliant initial seed.
# `thread-local-random` is similar to Netty and `secure-random` produces FIPS compliant random numbers every
# time but could block looking for entropy (these are short integers so are easy to brute-force, use
# `enhanced-double-hash-random` unless you really require FIPS compliant random numbers).
id-generator-policy = enhanced-double-hash-random
}
}
}
Expand Down
16 changes: 12 additions & 4 deletions actor/src/main/scala/org/apache/pekko/io/dns/DnsSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import pekko.actor.ExtendedActorSystem
import pekko.annotation.InternalApi
import pekko.event.Logging
import pekko.io.dns.CachePolicy.{ CachePolicy, Forever, Never, Ttl }
import pekko.io.dns.IdGenerator.Policy
import pekko.io.dns.internal.{ ResolvConf, ResolvConfParser }
import pekko.util.Helpers
import pekko.util.Helpers.Requiring
Expand Down Expand Up @@ -67,10 +68,17 @@ private[dns] final class DnsSettings(system: ExtendedActorSystem, c: Config) {
val PositiveCachePolicy: CachePolicy = getTtl("positive-ttl")
val NegativeCachePolicy: CachePolicy = getTtl("negative-ttl")

lazy val IdGeneratorPolicy: IdGenerator.Policy = IdGenerator
.Policy(c.getString("id-generator-policy"))
.getOrElse(throw new IllegalArgumentException("id-generator-policy must be 'thread-local-random' or " +
s"'secure-random' value was '${c.getString("id-generator-policy")}'"))
lazy val IdGeneratorPolicy: IdGenerator.Policy = {
c.getString("id-generator-policy") match {
case "thread-local-random" => Policy.ThreadLocalRandom
case "secure-random" => Policy.SecureRandom
case s if s.isEmpty | s == "enhanced-double-hash-random" => Policy.EnhancedDoubleHashRandom
case _ =>
throw new IllegalArgumentException(
"Invalid value for id-generator-policy, id-generator-policy must be 'thread-local-random', 'secure-random' or" +
s"`enhanced-double-hash-random`")
}
}

private def getTtl(path: String): CachePolicy =
c.getString(path) match {
Expand Down
41 changes: 32 additions & 9 deletions actor/src/main/scala/org/apache/pekko/io/dns/IdGenerator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ package org.apache.pekko.io.dns
import org.apache.pekko.annotation.InternalApi

import java.security.SecureRandom
import java.util.Random
import java.util.concurrent.ThreadLocalRandom

/**
Expand Down Expand Up @@ -39,18 +40,13 @@ private[pekko] object IdGenerator {
object Policy {
case object ThreadLocalRandom extends Policy
case object SecureRandom extends Policy
val Default: Policy = ThreadLocalRandom

def apply(name: String): Option[Policy] = name.toLowerCase match {
case "thread-local-random" => Some(ThreadLocalRandom)
case "secure-random" => Some(SecureRandom)
case _ => Some(ThreadLocalRandom)
}
case object EnhancedDoubleHashRandom extends Policy
}

def apply(policy: Policy): IdGenerator = policy match {
case Policy.ThreadLocalRandom => random(ThreadLocalRandom.current())
case Policy.SecureRandom => random(new SecureRandom())
case Policy.ThreadLocalRandom => random(ThreadLocalRandom.current())
case Policy.SecureRandom => random(new SecureRandom())
case Policy.EnhancedDoubleHashRandom => new EnhancedDoubleHashGenerator(new SecureRandom())
}

def apply(): IdGenerator = random(ThreadLocalRandom.current())
Expand All @@ -60,4 +56,31 @@ private[pekko] object IdGenerator {
*/
def random(rand: java.util.Random): IdGenerator =
() => (rand.nextInt(UnsignedShortBound) + Short.MinValue).toShort

private[pekko] class EnhancedDoubleHashGenerator(seed: Random) extends IdGenerator {

/**
* An efficient thread safe generator of pseudo random shorts based on
* https://en.wikipedia.org/wiki/Double_hashing#Enhanced_double_hashing.
*
* Note that due to the usage of synchronized this method is optimized
* for the happy case (i.e. least contention) on multiple threads.
*/
private var index = seed.nextLong
private var increment = seed.nextLong
private var count = 1L

override final def nextId(): Short = synchronized {
val result = (0xFFFFFFFF & index).asInstanceOf[Short]
index -= increment

// Incorporate the counter into the increment to create a
// tetrahedral number additional term.
increment -= {
count += 1
count - 1
}
result
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,34 @@ import java.security.SecureRandom
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 3, time = 5, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 3, time = 5)
@Threads(8)
@Fork(1)
@State(Scope.Benchmark)
class IdGeneratorBanchmark {
val threadLocalRandom = IdGenerator.random(ThreadLocalRandom.current())
val secureRandom = IdGenerator.random(new SecureRandom())
val enhancedDoubleHash = new IdGenerator.EnhancedDoubleHashGenerator(new SecureRandom())

@Threads(1)
@Benchmark
def measureThreadLocalRandom(): Short = threadLocalRandom.nextId()

@Threads(1)
@Benchmark
def measureSecureRandom(): Short = secureRandom.nextId()

@Threads(1)
@Benchmark
def measureEnhancedDoubleHash(): Short = enhancedDoubleHash.nextId()

@Threads(2)
@Benchmark
def multipleThreadsMeasureThreadLocalRandom(): Short = threadLocalRandom.nextId()

@Threads(2)
@Benchmark
def multipleThreadsMeasureSecureRandom(): Short = secureRandom.nextId()

@Threads(2)
@Benchmark
def multipleThreadsMeasureEnhancedDoubleHash(): Short = enhancedDoubleHash.nextId()
}