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-04 14:10:40 +01:00
parent aabb73a9d2
commit 3f101caa4c
4 changed files with 36 additions and 53 deletions

View File

@ -17,17 +17,16 @@ package io.netty.channel.epoll;
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;
@ -40,9 +39,7 @@ import static io.netty.channel.epoll.NativeStaticallyReferencedJniMethods.isSupp
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;
@ -64,6 +61,7 @@ public final class Native {
// The library was not previously loaded, load it now.
loadNativeLibrary();
}
Socket.initialize();
}
// EventLoop operations and constants

View File

@ -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;
@ -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();

View File

@ -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 };
@ -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));
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);
return res == 0 ? AF_INET6 : AF_INET;
close(fd);
socketType = res == 0 ? AF_INET6 : AF_INET;
}
}
}
@ -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
@ -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]);
@ -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) {
@ -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;
}
@ -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;
}
}

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;
@ -411,6 +413,8 @@ public class Socket extends FileDescriptor {
'}';
}
private static final AtomicBoolean INITIALIZED = new AtomicBoolean();
public static Socket newSocketStream() {
return new Socket(newSocketStream0());
}
@ -423,6 +427,12 @@ public class Socket extends FileDescriptor {
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) {
@ -498,4 +508,5 @@ public class Socket extends FileDescriptor {
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);
}