Reduce the time taken by NetUtil and DefaultChannelId class initialization

Motivation:

As reported in #2331, some query operations in NetworkInterface takes much longer time than we expected.  For example, specifying -Djava.net.preferIPv4Stack=true option in Window increases the execution time by more than 4 times.  Some Windows systems have more than 20 network interfaces, and this problem gets bigger as the number of unused (virtual) NICs increases.

Modification:

Use NetworkInterface.getInetAddresses() wherever possible.
Before iterating over all NICs reported by NetworkInterface, filter the NICs without proper InetAddresses.  This reduces the number of candidates quite a lot.
NetUtil does not query hardware address of NIC in the first place but uses InetAddress.isLoopbackAddress().
Do not call unnecessary query operations on NetworkInterface.  Just get hardware address and compare.

Result:

Significantly reduced class initialization time, which should fix #2331.
This commit is contained in:
Trustin Lee 2014-03-21 11:39:41 +09:00
parent 241d24cbfa
commit 3621a0169c
2 changed files with 133 additions and 99 deletions

View File

@ -28,6 +28,7 @@ import java.net.NetworkInterface;
import java.net.SocketException; import java.net.SocketException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.List;
import java.util.StringTokenizer; import java.util.StringTokenizer;
/** /**
@ -56,7 +57,7 @@ public final class NetUtil {
public static final InetAddress LOCALHOST; public static final InetAddress LOCALHOST;
/** /**
* The loopback {@link NetworkInterface} on the current machine * The loopback {@link NetworkInterface} of the current machine
*/ */
public static final NetworkInterface LOOPBACK_IF; public static final NetworkInterface LOOPBACK_IF;
@ -72,48 +73,13 @@ public final class NetUtil {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(NetUtil.class); private static final InternalLogger logger = InternalLoggerFactory.getInstance(NetUtil.class);
static { static {
// Find the first loopback interface available. byte[] LOCALHOST4_BYTES = {127, 0, 0, 1};
NetworkInterface loopbackIface = null; byte[] LOCALHOST6_BYTES = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1};
try {
for (Enumeration<NetworkInterface> ifaces = NetworkInterface.getNetworkInterfaces();
ifaces.hasMoreElements();) {
NetworkInterface iface = ifaces.nextElement();
if (iface.isLoopback()) {
// Found
loopbackIface = iface;
break;
}
}
if (loopbackIface == null) {
logger.warn("Failed to find the loopback interface");
}
} catch (SocketException e) {
logger.warn("Failed to find the loopback interface", e);
}
LOOPBACK_IF = loopbackIface;
// Find the localhost address
InetAddress localhost = null;
if (LOOPBACK_IF != null) {
logger.debug("Loopback interface: {}", LOOPBACK_IF.getDisplayName());
for (Enumeration<InetAddress> addrs = LOOPBACK_IF.getInetAddresses();
addrs.hasMoreElements();) {
InetAddress a = addrs.nextElement();
if (localhost == null) {
logger.debug("Loopback address: {} (primary)", a);
localhost = a;
} else {
logger.debug("Loopback address: {}", a);
}
}
}
// Create IPv4 loopback address. // Create IPv4 loopback address.
Inet4Address localhost4 = null; Inet4Address localhost4 = null;
try { try {
localhost4 = (Inet4Address) InetAddress.getByAddress(new byte[]{127, 0, 0, 1}); localhost4 = (Inet4Address) InetAddress.getByAddress(LOCALHOST4_BYTES);
} catch (Exception e) { } catch (Exception e) {
// We should not get here as long as the length of the address is correct. // We should not get here as long as the length of the address is correct.
PlatformDependent.throwException(e); PlatformDependent.throwException(e);
@ -123,33 +89,96 @@ public final class NetUtil {
// Create IPv6 loopback address. // Create IPv6 loopback address.
Inet6Address localhost6 = null; Inet6Address localhost6 = null;
try { try {
localhost6 = (Inet6Address) InetAddress.getByAddress( localhost6 = (Inet6Address) InetAddress.getByAddress(LOCALHOST6_BYTES);
new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1});
} catch (Exception e) { } catch (Exception e) {
// We should not get here as long as the length of the address is correct. // We should not get here as long as the length of the address is correct.
PlatformDependent.throwException(e); PlatformDependent.throwException(e);
} }
LOCALHOST6 = localhost6; LOCALHOST6 = localhost6;
// Try to determine the default loopback address if we couldn't yet. // Retrieve the list of available network interfaces.
if (localhost == null) { List<NetworkInterface> ifaces = new ArrayList<NetworkInterface>();
try { try {
if (NetworkInterface.getByInetAddress(LOCALHOST6) != null) { for (Enumeration<NetworkInterface> i = NetworkInterface.getNetworkInterfaces(); i.hasMoreElements();) {
logger.debug("Using hard-coded IPv6 localhost address: {}", localhost6); NetworkInterface iface = i.nextElement();
localhost = localhost6; // Use the interface with proper INET addresses only.
if (iface.getInetAddresses().hasMoreElements()) {
ifaces.add(iface);
} }
} catch (Exception e) { }
// Ignore } catch (SocketException e) {
} finally { logger.warn("Failed to retrieve the list of available network interfaces", e);
if (localhost == null) { }
logger.debug("Using hard-coded IPv4 localhost address: {}", localhost4);
localhost = localhost4; // Find the first loopback interface available from its INET address (127.0.0.1 or ::1)
// Note that we do not use NetworkInterface.isLoopback() in the first place because it takes long time
// on a certain environment. (e.g. Windows with -Djava.net.preferIPv4Stack=true)
NetworkInterface loopbackIface = null;
InetAddress loopbackAddr = null;
loop: for (NetworkInterface iface: ifaces) {
for (Enumeration<InetAddress> i = iface.getInetAddresses(); i.hasMoreElements();) {
InetAddress addr = i.nextElement();
if (addr.isLoopbackAddress()) {
// Found
loopbackIface = iface;
loopbackAddr = addr;
break loop;
} }
} }
} }
LOCALHOST = localhost; // If failed to find the loopback interface from its INET address, fall back to isLoopback().
if (loopbackIface == null) {
try {
for (NetworkInterface iface: ifaces) {
if (iface.isLoopback()) {
Enumeration<InetAddress> i = iface.getInetAddresses();
if (i.hasMoreElements()) {
// Found the one with INET address.
loopbackIface = iface;
loopbackAddr = i.nextElement();
break;
}
}
}
if (loopbackIface == null) {
logger.warn("Failed to find the loopback interface");
}
} catch (SocketException e) {
logger.warn("Failed to find the loopback interface", e);
}
}
if (loopbackIface != null) {
// Found the loopback interface with an INET address.
logger.debug(
"Loopback interface: {} ({}, {})",
loopbackIface.getName(), loopbackIface.getDisplayName(), loopbackAddr.getHostAddress());
} else {
// Could not find the loopback interface, but we can't leave LOCALHOST as null.
// Use LOCALHOST6 or LOCALHOST4, preferably the IPv6 one.
if (loopbackAddr == null) {
try {
if (NetworkInterface.getByInetAddress(LOCALHOST6) != null) {
logger.debug("Using hard-coded IPv6 localhost address: {}", localhost6);
loopbackAddr = localhost6;
}
} catch (Exception e) {
// Ignore
} finally {
if (loopbackAddr == null) {
logger.debug("Using hard-coded IPv4 localhost address: {}", localhost4);
loopbackAddr = localhost4;
}
}
}
}
LOOPBACK_IF = loopbackIface;
LOCALHOST = loopbackAddr;
// Determine the default somaxconn (server socket backlog) value of the platform.
int somaxconn = 3072; int somaxconn = 3072;
BufferedReader in = null; BufferedReader in = null;
try { try {
@ -446,7 +475,9 @@ public final class NetUtil {
return false; return false;
} }
try { try {
Integer.parseInt(ipAddress.substring(i + 1)); if (Integer.parseInt(ipAddress.substring(i + 1)) < 0) {
return false;
}
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
// right now we just support an integer after the % so if // right now we just support an integer after the % so if
// this is not // this is not
@ -506,10 +537,7 @@ public final class NetUtil {
return false; return false;
} }
} }
if (Integer.parseInt(word) > 255) { return Integer.parseInt(word) <= 255;
return false;
}
return true;
} }
static boolean isValidHexChar(char c) { static boolean isValidHexChar(char c) {
@ -560,10 +588,8 @@ public final class NetUtil {
if (word.length() == 0 || Integer.parseInt(word.toString()) > 255) { if (word.length() == 0 || Integer.parseInt(word.toString()) > 255) {
return false; return false;
} }
if (periods != 3) {
return false; return periods == 3;
}
return true;
} }
/** /**

View File

@ -23,10 +23,13 @@ import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory; import io.netty.util.internal.logging.InternalLoggerFactory;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.NetworkInterface; import java.net.NetworkInterface;
import java.net.SocketException; import java.net.SocketException;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -123,36 +126,36 @@ final class DefaultChannelId implements ChannelId {
final byte[] NOT_FOUND = { -1 }; final byte[] NOT_FOUND = { -1 };
byte[] bestMacAddr = NOT_FOUND; byte[] bestMacAddr = NOT_FOUND;
Enumeration<NetworkInterface> ifaces = null; // Retrieve the list of available network interfaces.
List<NetworkInterface> ifaces = new ArrayList<NetworkInterface>();
try { try {
ifaces = NetworkInterface.getNetworkInterfaces(); for (Enumeration<NetworkInterface> i = NetworkInterface.getNetworkInterfaces(); i.hasMoreElements();) {
NetworkInterface iface = i.nextElement();
// Use the interface with proper INET addresses only.
Enumeration<InetAddress> addrs = iface.getInetAddresses();
if (addrs.hasMoreElements() && !addrs.nextElement().isLoopbackAddress()) {
ifaces.add(iface);
}
}
} catch (SocketException e) { } catch (SocketException e) {
logger.warn("Failed to find the loopback interface", e); logger.warn("Failed to retrieve the list of available network interfaces", e);
} }
if (ifaces != null) { for (NetworkInterface iface: ifaces) {
while (ifaces.hasMoreElements()) { if (iface.isVirtual()) {
NetworkInterface iface = ifaces.nextElement(); continue;
try { }
if (iface.isLoopback() || iface.isPointToPoint() || iface.isVirtual()) {
continue;
}
} catch (SocketException e) {
logger.debug("Failed to determine the type of a network interface: {}", iface, e);
continue;
}
byte[] macAddr; byte[] macAddr;
try { try {
macAddr = iface.getHardwareAddress(); macAddr = iface.getHardwareAddress();
} catch (SocketException e) { } catch (SocketException e) {
logger.debug("Failed to get the hardware address of a network interface: {}", iface, e); logger.debug("Failed to get the hardware address of a network interface: {}", iface, e);
continue; continue;
} }
if (isBetterAddress(bestMacAddr, macAddr)) { if (isBetterAddress(bestMacAddr, macAddr)) {
bestMacAddr = macAddr; bestMacAddr = macAddr;
}
} }
} }
@ -164,8 +167,17 @@ final class DefaultChannelId implements ChannelId {
formatAddress(bestMacAddr)); formatAddress(bestMacAddr));
} }
if (bestMacAddr.length != MACHINE_ID_LEN) { switch (bestMacAddr.length) {
bestMacAddr = Arrays.copyOf(bestMacAddr, MACHINE_ID_LEN); case 6: // EUI-48 - convert to EUI-64
byte[] newAddr = new byte[MACHINE_ID_LEN];
System.arraycopy(bestMacAddr, 0, newAddr, 0, 3);
newAddr[3] = (byte) 0xFF;
newAddr[4] = (byte) 0xFE;
System.arraycopy(bestMacAddr, 3, newAddr, 5, 3);
bestMacAddr = newAddr;
break;
default: // Unknown
bestMacAddr = Arrays.copyOf(bestMacAddr, MACHINE_ID_LEN);
} }
return bestMacAddr; return bestMacAddr;
@ -181,20 +193,16 @@ final class DefaultChannelId implements ChannelId {
return false; return false;
} }
// Must not be filled with only 0 or 1. // Must not be filled with only 0 and 1.
boolean onlyZero = true; boolean onlyZeroAndOne = true;
boolean onlyOne = true;
for (byte b: candidate) { for (byte b: candidate) {
if (b != 0) { if (b != 0 && b != 1) {
onlyZero = false; onlyZeroAndOne = false;
} break;
if (b != -1) {
onlyOne = false;
} }
} }
if (onlyZero || onlyOne) { if (onlyZeroAndOne) {
return false; return false;
} }