typelevel/spire

`seedUniquifier` ritual doesn't uniquify seeds

bbjubjub2494 opened this issue · 0 comments

In random.rng.Utils.longFromSeed, a @volatile counter variable is used. The intent is presumably to guarantee that two RNG constructions initiated during the same nanosecond will result in two independent RNGs. The problem is that the @volatile construct isn't enough to secure a proper concurrent counter variable: it is very possible for two clients to receive the same value from the counter. Thus, the uniquifier isn't doing what it says on the label. This is showcased by the attached ammonite script. (Please run a few times if the result is ∅ at first)

This could be solved using different concurrency primitives, such as synchronized or monix.execution.atomic.

import $ivy.`org.typelevel::spire:0.17.0-M1`

import spire.random.rng.Utils.longFromTime

import concurrent.{Future, ExecutionContext, Await}
import concurrent.duration._

val nanoTime = 0L  // fake frozen timer

// collect 100 values from the uniquifier successively
def observeUniquifier()(implicit ec: ExecutionContext): Future[Vector[Long]] =
  Future { Vector.fill(100) { longFromTime(nanoTime) } }

@main def main(): Unit = {
  implicit val ec = ExecutionContext.global
  // collect two concurrent sequences from the uniquifier
  val (l1, l2) = Await.result(observeUniquifier() zip observeUniquifier(), Duration.Inf)
  // There should be no overlap, but there usually is
  println(l1.toSet & l2.toSet)
}