Skip to content

Commit

Permalink
Not call java methods from within JNI init code to prevent class load…
Browse files Browse the repository at this point in the history
…ing 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 [netty#7458].
  • Loading branch information
normanmaurer committed Dec 6, 2017
1 parent aabb73a commit 3f101ca
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,16 @@

import io.netty.channel.DefaultFileRegion;
import io.netty.channel.unix.Errors.NativeIoException;
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;

import java.io.IOException;
import java.net.InetAddress;
import java.nio.channels.ClosedChannelException;
import java.util.Locale;

Expand All @@ -40,9 +39,7 @@
import static io.netty.channel.epoll.NativeStaticallyReferencedJniMethods.isSupportingTcpFastopen;
import static io.netty.channel.epoll.NativeStaticallyReferencedJniMethods.kernelVersion;
import static io.netty.channel.epoll.NativeStaticallyReferencedJniMethods.tcpMd5SigMaxKeyLen;
import static io.netty.channel.unix.Errors.ERRNO_EAGAIN_NEGATIVE;
import static io.netty.channel.unix.Errors.ERRNO_EPIPE_NEGATIVE;
import static io.netty.channel.unix.Errors.ERRNO_EWOULDBLOCK_NEGATIVE;
import static io.netty.channel.unix.Errors.ioResult;
import static io.netty.channel.unix.Errors.newConnectionResetException;
import static io.netty.channel.unix.Errors.newIOException;
Expand All @@ -64,6 +61,7 @@ public final class Native {
// The library was not previously loaded, load it now.
loadNativeLibrary();
}
Socket.initialize();
}

// EventLoop operations and constants
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package io.netty.channel.kqueue;

import io.netty.channel.unix.FileDescriptor;
import io.netty.channel.unix.Socket;
import io.netty.util.internal.NativeLibraryLoader;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.SystemPropertyUtil;
Expand Down Expand Up @@ -58,6 +59,7 @@ final class Native {
// The library was not previously loaded, load it now.
loadNativeLibrary();
}
Socket.initialize();
}

static final short EV_ADD = evAdd();
Expand Down
72 changes: 22 additions & 50 deletions transport-native-unix-common/src/main/c/netty_unix_socket.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@ static jclass datagramSocketAddressClass = NULL;
static jmethodID datagramSocketAddrMethodId = NULL;
static jmethodID inetSocketAddrMethodId = 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 };
Expand Down Expand Up @@ -169,30 +167,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 = nettyNonBlockingSocket(AF_INET6, SOCK_STREAM, 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));

close(fd);
return res == 0 ? AF_INET6 : AF_INET;
int fd = nettyNonBlockingSocket(AF_INET6, SOCK_STREAM, 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);
socketType = res == 0 ? AF_INET6 : AF_INET;
}
}
}

Expand Down Expand Up @@ -873,6 +867,8 @@ static jint netty_unix_socket_isBroadcast(JNIEnv* env, jclass clazz, jint fd) {
}
return optval;
}


// JNI Registered Methods End

// JNI Method Registration Table Begin
Expand Down Expand Up @@ -916,7 +912,8 @@ static const JNINativeMethod fixed_method_table[] = {
{ "getSendBufferSize", "(I)I", (void *) netty_unix_socket_getSendBufferSize },
{ "getSoLinger", "(I)I", (void *) netty_unix_socket_getSoLinger },
{ "getTrafficClass", "(I)I", (void *) netty_unix_socket_getTrafficClass },
{ "getSoError", "(I)I", (void *) netty_unix_socket_getSoError }
{ "getSoError", "(I)I", (void *) netty_unix_socket_getSoError },
{ "initialize", "(Z)V", (void *) netty_unix_socket_initialize }
};
static const jint fixed_method_table_size = sizeof(fixed_method_table) / sizeof(fixed_method_table[0]);

Expand Down Expand Up @@ -1001,26 +998,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;
}

void* mem = malloc(1);
if (mem == NULL) {
Expand All @@ -1042,7 +1019,6 @@ jint netty_unix_socket_JNI_OnLoad(JNIEnv* env, const char* packagePrefix) {
}
free(mem);

socketType = socket_type(env);
return NETTY_JNI_VERSION;
}

Expand All @@ -1055,8 +1031,4 @@ void netty_unix_socket_JNI_OnUnLoad(JNIEnv* env) {
(*env)->DeleteGlobalRef(env, inetSocketAddressClass);
inetSocketAddressClass = NULL;
}
if (netUtilClass != NULL) {
(*env)->DeleteGlobalRef(env, netUtilClass);
netUtilClass = NULL;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import io.netty.channel.ChannelException;
import io.netty.util.CharsetUtil;
import io.netty.util.NetUtil;

import java.io.IOException;
import java.net.Inet6Address;
Expand All @@ -26,6 +27,7 @@
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;
Expand Down Expand Up @@ -411,6 +413,8 @@ public String toString() {
'}';
}

private static final AtomicBoolean INITIALIZED = new AtomicBoolean();

public static Socket newSocketStream() {
return new Socket(newSocketStream0());
}
Expand All @@ -423,6 +427,12 @@ public static Socket newSocketDomain() {
return new Socket(newSocketDomain0());
}

public static void initialize() {
if (INITIALIZED.compareAndSet(false, true)) {
initialize(NetUtil.isIpV4StackPreferred());
}
}

protected static int newSocketStream0() {
int res = newSocketStreamFd();
if (res < 0) {
Expand Down Expand Up @@ -498,4 +508,5 @@ private static native DatagramSocketAddress recvFromAddress(
private static native void setSoLinger(int fd, int soLinger) throws IOException;
private static native void setBroadcast(int fd, int broadcast) throws IOException;
private static native void setTrafficClass(int fd, int trafficClass) throws IOException;
private static native void initialize(boolean ipv4Preferred);
}

0 comments on commit 3f101ca

Please sign in to comment.