From 8142aae765b74ba7934b363b562a5ffe4a01a12c Mon Sep 17 00:00:00 2001 From: Trustin Lee Date: Sat, 24 Aug 2013 12:25:50 +0900 Subject: [PATCH] Improve the randomness of ThreadLocalRandom for all platform - Fixes #1765 Java 6 did a poor job of generating seedUniquifier unlike 7, so I implemented platform-independent seedUniquifier generator with configurability --- .../util/internal/ThreadLocalRandom.java | 114 ++++++++++++++++-- 1 file changed, 103 insertions(+), 11 deletions(-) diff --git a/common/src/main/java/io/netty/util/internal/ThreadLocalRandom.java b/common/src/main/java/io/netty/util/internal/ThreadLocalRandom.java index c0867e2755..22ff7e47e1 100644 --- a/common/src/main/java/io/netty/util/internal/ThreadLocalRandom.java +++ b/common/src/main/java/io/netty/util/internal/ThreadLocalRandom.java @@ -22,7 +22,15 @@ package io.netty.util.internal; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; + +import java.security.SecureRandom; import java.util.Random; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; /** * A random number generator isolated to the current thread. Like the @@ -50,6 +58,90 @@ import java.util.Random; */ @SuppressWarnings("all") public class ThreadLocalRandom extends Random { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(ThreadLocalRandom.class); + + private static final AtomicLong seedUniquifier = new AtomicLong(); + + private static volatile long initialSeedUniquifier; + + public static void setInitialSeedUniquifier(long initialSeedUniquifier) { + ThreadLocalRandom.initialSeedUniquifier = initialSeedUniquifier; + } + + public static synchronized long getInitialSeedUniquifier() { + // Use the value set via the setter. + long initialSeedUniquifier = ThreadLocalRandom.initialSeedUniquifier; + if (initialSeedUniquifier == 0) { + // Use the system property value. + ThreadLocalRandom.initialSeedUniquifier = initialSeedUniquifier = + SystemPropertyUtil.getLong("io.netty.initialSeedUniquifier", 0); + } + + // Otherwise, generate one. + if (initialSeedUniquifier == 0) { + // Try to generate a real random number from /dev/random. + // Get from a different thread to avoid blocking indefinitely on a machine without much entrophy. + final BlockingQueue queue = new LinkedBlockingQueue(); + Thread generatorThread = new Thread("initialSeedUniquifierGenerator") { + @Override + public void run() { + SecureRandom random = new SecureRandom(); // Get the real random seed from /dev/random + queue.add(random.nextLong()); + } + }; + generatorThread.start(); + + // Get the random seed from the thread with timeout. + final long timeoutSeconds = 3; + final long deadLine = System.nanoTime() + TimeUnit.SECONDS.toNanos(timeoutSeconds); + for (;;) { + long waitTime = deadLine - System.nanoTime(); + if (waitTime <= 0) { + logger.warn( + "Failed to get the secure random number from SecureRandom within {} seconds. " + + "Not enough entrophy?", timeoutSeconds); + break; + } + + try { + Long result = queue.poll(waitTime, TimeUnit.NANOSECONDS); + if (result != null) { + initialSeedUniquifier = result; + break; + } + } catch (InterruptedException ignore) { + // Ignore + } + } + + // Just in case the initialSeedUniquifier is zero or some other constant + initialSeedUniquifier ^= 0x3255ecdc33bae119L; // just a meaningless random number + initialSeedUniquifier ^= Long.reverse(System.nanoTime()); + + ThreadLocalRandom.initialSeedUniquifier = initialSeedUniquifier; + } + + return initialSeedUniquifier; + } + + private static long newSeed() { + for (;;) { + final long current = seedUniquifier.get(); + final long actualCurrent = current != 0? current : getInitialSeedUniquifier(); + + // L'Ecuyer, "Tables of Linear Congruential Generators of Different Sizes and Good Lattice Structure", 1999 + final long next = actualCurrent * 181783497276652981L; + + if (seedUniquifier.compareAndSet(current, next)) { + if (current == 0 && logger.isDebugEnabled()) { + logger.debug(String.format("-Dio.netty.initialSeedUniquifier: 0x%016x", actualCurrent)); + } + return next ^ System.nanoTime(); + } + } + } + // same constants as Random, but must be redeclared because private private static final long multiplier = 0x5DEECE66DL; private static final long addend = 0xBL; @@ -73,24 +165,24 @@ public class ThreadLocalRandom extends Random { // each other. private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7; - /** - * The actual ThreadLocal - */ - private static final ThreadLocal localRandom = - new ThreadLocal() { - protected ThreadLocalRandom initialValue() { - return new ThreadLocalRandom(); - } - }; - /** * Constructor called only by localRandom.initialValue. */ ThreadLocalRandom() { - super(); + super(newSeed()); initialized = true; } + /** + * The actual ThreadLocal + */ + private static final ThreadLocal localRandom = + new ThreadLocal() { + protected ThreadLocalRandom initialValue() { + return new ThreadLocalRandom(); + } + }; + /** * Returns the current thread's {@code ThreadLocalRandom}. *