Not call java methods from within JNI init code to prevent class loading deadlocks.

Motivation:

We used NetUtil.isIpV4StackPreferred() when loading JNI code which tries to load NetworkInterface in its static initializer. Unfortunally a lock on the NetworkInterface class init may be already hold somewhere else which may cause a loader deadlock.

Modifications:

Add a new Socket.initialize() method that will be called when init the library and pass everything needed to the JNI level so we not need to call back to java.

Result:

Fixes [#7458].
This commit is contained in:
Norman Maurer 2017-12-06 14:51:02 +01:00
parent 88b9f8caf8
commit f921ea344e
3 changed files with 33 additions and 50 deletions

View File

@ -40,9 +40,7 @@ static jmethodID datagramSocketAddrMethodId = NULL;
static jmethodID inetSocketAddrMethodId = NULL;
static jmethodID peerCredentialsMethodId = NULL;
static jclass inetSocketAddressClass = NULL;
static jclass netUtilClass = NULL;
static jmethodID netUtilClassIpv4PreferredMethodId = NULL;
static int socketType;
static int socketType = AF_INET;
static const char* ip4prefix = "::ffff:";
static const unsigned char wildcardAddress[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
static const unsigned char ipv4MappedWildcardAddress[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00 };
@ -155,30 +153,26 @@ static jbyteArray createInetSocketAddressArray(JNIEnv* env, const struct sockadd
return bArray;
}
static int socket_type(JNIEnv* env) {
jboolean ipv4Preferred = (*env)->CallStaticBooleanMethod(env, netUtilClass, netUtilClassIpv4PreferredMethodId);
static void netty_unix_socket_initialize(JNIEnv* env, jclass clazz, jboolean ipv4Preferred) {
if (ipv4Preferred) {
// User asked to use ipv4 explicitly.
return AF_INET;
}
int fd = socket(AF_INET6, SOCK_STREAM | SOCK_NONBLOCK, 0);
if (fd == -1) {
if (errno == EAFNOSUPPORT) {
return AF_INET;
}
return AF_INET6;
socketType = AF_INET;
} else {
// Explicitly try to bind to ::1 to ensure IPV6 can really be used.
// See https://github.com/netty/netty/issues/7021.
struct sockaddr_in6 addr;
memset(&addr, 0, sizeof(addr));
addr.sin6_family = AF_INET6;
addr.sin6_addr.s6_addr[15] = 1; /* [::1]:0 */
int res = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
int fd = socket(AF_INET6, SOCK_STREAM | SOCK_NONBLOCK, 0);
if (fd == -1) {
socketType = errno == EAFNOSUPPORT ? AF_INET : AF_INET6;
} else {
// Explicitly try to bind to ::1 to ensure IPV6 can really be used.
// See https://github.com/netty/netty/issues/7021.
struct sockaddr_in6 addr;
memset(&addr, 0, sizeof(addr));
addr.sin6_family = AF_INET6;
addr.sin6_addr.s6_addr[15] = 1; /* [::1]:0 */
int res = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
close(fd);
return res == 0 ? AF_INET6 : AF_INET;
close(fd);
socketType = res == 0 ? AF_INET6 : AF_INET;
}
}
}
@ -746,7 +740,8 @@ static const JNINativeMethod fixed_method_table[] = {
{ "getSoLinger", "(I)I", (void *) netty_unix_socket_getSoLinger },
{ "getSoError", "(I)I", (void *) netty_unix_socket_getSoError },
{ "getTcpDeferAccept", "(I)I", (void *) netty_unix_socket_getTcpDeferAccept },
{ "isTcpQuickAck", "(I)I", (void *) netty_unix_socket_isTcpQuickAck }
{ "isTcpQuickAck", "(I)I", (void *) netty_unix_socket_isTcpQuickAck },
{ "initialize", "(Z)V", (void *) netty_unix_socket_initialize }
};
static const jint fixed_method_table_size = sizeof(fixed_method_table) / sizeof(fixed_method_table[0]);
@ -835,26 +830,6 @@ jint netty_unix_socket_JNI_OnLoad(JNIEnv* env, const char* packagePrefix) {
netty_unix_errors_throwRuntimeException(env, "failed to get method ID: InetSocketAddress.<init>(String, int)");
return JNI_ERR;
}
nettyClassName = netty_unix_util_prepend(packagePrefix, "io/netty/util/NetUtil");
jclass localNetUtilClass = (*env)->FindClass(env, nettyClassName);
free(nettyClassName);
nettyClassName = NULL;
if (localNetUtilClass == NULL) {
// pending exception...
return JNI_ERR;
}
netUtilClass = (jclass) (*env)->NewGlobalRef(env, localNetUtilClass);
if (netUtilClass == NULL) {
// out-of-memory!
netty_unix_errors_throwOutOfMemoryError(env);
return JNI_ERR;
}
netUtilClassIpv4PreferredMethodId = (*env)->GetStaticMethodID(env, netUtilClass, "isIpV4StackPreferred", "()Z" );
if (netUtilClassIpv4PreferredMethodId == NULL) {
// position method was not found.. something is wrong so bail out
netty_unix_errors_throwRuntimeException(env, "failed to get method ID: NetUild.isIpV4StackPreferred()");
return JNI_ERR;
}
nettyClassName = netty_unix_util_prepend(packagePrefix, "io/netty/channel/unix/PeerCredentials");
jclass localPeerCredsClass = (*env)->FindClass(env, nettyClassName);
@ -896,7 +871,6 @@ jint netty_unix_socket_JNI_OnLoad(JNIEnv* env, const char* packagePrefix) {
}
free(mem);
socketType = socket_type(env);
return JNI_VERSION_1_6;
}
@ -909,10 +883,6 @@ void netty_unix_socket_JNI_OnUnLoad(JNIEnv* env) {
(*env)->DeleteGlobalRef(env, inetSocketAddressClass);
inetSocketAddressClass = NULL;
}
if (netUtilClass != NULL) {
(*env)->DeleteGlobalRef(env, netUtilClass);
netUtilClass = NULL;
}
if (peerCredentialsClass != NULL) {
(*env)->DeleteGlobalRef(env, peerCredentialsClass);
peerCredentialsClass = NULL;

View File

@ -17,11 +17,12 @@ package io.netty.channel.epoll;
import io.netty.channel.DefaultFileRegion;
import io.netty.channel.unix.Errors.NativeIoException;
import io.netty.channel.unix.NativeInetAddress;
import io.netty.channel.unix.Socket;
import io.netty.util.internal.NativeLibraryLoader;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.SystemPropertyUtil;
import io.netty.channel.unix.FileDescriptor;
import io.netty.channel.unix.NativeInetAddress;
import io.netty.util.internal.ThrowableUtil;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
@ -67,6 +68,7 @@ public final class Native {
// The library was not previously loaded, load it now.
loadNativeLibrary();
}
Socket.initialize();
}
// EventLoop operations and constants

View File

@ -17,6 +17,7 @@ package io.netty.channel.unix;
import io.netty.channel.ChannelException;
import io.netty.util.CharsetUtil;
import io.netty.util.NetUtil;
import java.io.IOException;
import java.net.Inet6Address;
@ -26,6 +27,7 @@ import java.net.PortUnreachableException;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.util.concurrent.atomic.AtomicBoolean;
import static io.netty.channel.unix.Errors.ERRNO_EAGAIN_NEGATIVE;
import static io.netty.channel.unix.Errors.ERROR_ECONNREFUSED_NEGATIVE;
@ -381,6 +383,14 @@ public final class Socket extends FileDescriptor {
'}';
}
private static final AtomicBoolean INITIALIZED = new AtomicBoolean();
public static void initialize() {
if (INITIALIZED.compareAndSet(false, true)) {
initialize(NetUtil.isIpV4StackPreferred());
}
}
public static Socket newSocketStream() {
int res = newSocketStreamFd();
if (res < 0) {
@ -453,4 +463,5 @@ public final class Socket extends FileDescriptor {
private static native void setSoLinger(int fd, int soLinger) throws IOException;
private static native void setTcpDeferAccept(int fd, int deferAccept) throws IOException;
private static native void setTcpQuickAck(int fd, int quickAck) throws IOException;
private static native void initialize(boolean ipv4Preferred);
}