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
This commit is contained in:
Trustin Lee 2013-08-24 12:25:50 +09:00
parent 9ca20b73d3
commit 8142aae765

View File

@ -22,7 +22,15 @@
package io.netty.util.internal; 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.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 * A random number generator isolated to the current thread. Like the
@ -50,6 +58,90 @@ import java.util.Random;
*/ */
@SuppressWarnings("all") @SuppressWarnings("all")
public class ThreadLocalRandom extends Random { 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<Long> queue = new LinkedBlockingQueue<Long>();
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 // same constants as Random, but must be redeclared because private
private static final long multiplier = 0x5DEECE66DL; private static final long multiplier = 0x5DEECE66DL;
private static final long addend = 0xBL; private static final long addend = 0xBL;
@ -73,24 +165,24 @@ public class ThreadLocalRandom extends Random {
// each other. // each other.
private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7; private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7;
/**
* The actual ThreadLocal
*/
private static final ThreadLocal<ThreadLocalRandom> localRandom =
new ThreadLocal<ThreadLocalRandom>() {
protected ThreadLocalRandom initialValue() {
return new ThreadLocalRandom();
}
};
/** /**
* Constructor called only by localRandom.initialValue. * Constructor called only by localRandom.initialValue.
*/ */
ThreadLocalRandom() { ThreadLocalRandom() {
super(); super(newSeed());
initialized = true; initialized = true;
} }
/**
* The actual ThreadLocal
*/
private static final ThreadLocal<ThreadLocalRandom> localRandom =
new ThreadLocal<ThreadLocalRandom>() {
protected ThreadLocalRandom initialValue() {
return new ThreadLocalRandom();
}
};
/** /**
* Returns the current thread's {@code ThreadLocalRandom}. * Returns the current thread's {@code ThreadLocalRandom}.
* *