diff --git a/transport-native-io_uring/src/main/c/netty_io_uring_native.c b/transport-native-io_uring/src/main/c/netty_io_uring_native.c index 0438ecbf28..3c1ab0149c 100644 --- a/transport-native-io_uring/src/main/c/netty_io_uring_native.c +++ b/transport-native-io_uring/src/main/c/netty_io_uring_native.c @@ -64,6 +64,21 @@ static jclass ringBufferClass = NULL; static jclass ioUringCompletionQueueClass = NULL; static jclass ioUringSubmissionQueueClass = NULL; +static void netty_io_uring_native_JNI_OnUnLoad(JNIEnv* env) { + netty_unix_limits_JNI_OnUnLoad(env); + netty_unix_errors_JNI_OnUnLoad(env); + netty_unix_filedescriptor_JNI_OnUnLoad(env); + netty_unix_socket_JNI_OnUnLoad(env); + netty_unix_buffer_JNI_OnUnLoad(env); + + ringBufferMethodId = NULL; + ioUringSubmissionQueueMethodId = NULL; + ioUringCommpletionQueueMethodId = NULL; + ringBufferClass = NULL; + ioUringCompletionQueueClass = NULL; + ioUringSubmissionQueueClass = NULL; +} + void io_uring_unmap_rings(struct io_uring_sq *sq, struct io_uring_cq *cq) { munmap(sq->ring_ptr, sq->ring_sz); if (cq->ring_ptr && cq->ring_ptr != sq->ring_ptr) { @@ -210,6 +225,43 @@ static void netty_epoll_native_eventFdWrite(JNIEnv* env, jclass clazz, jint fd, } } +static void netty_io_uring_ring_buffer_exit(JNIEnv *env, jclass class, jobject ringBuffer) { + // Find the id of the Java method to be called + + jmethodID submissionQueueMethodId = (*env)->GetMethodID(env, ringBufferClass, "getIoUringSubmissionQueue", "()Lio/netty/channel/uring/IOUringSubmissionQueue;"); + jmethodID completionQueueMethodId = (*env)->GetMethodID(env, ringBufferClass, "getIoUringCompletionQueue", "()Lio/netty/channel/uring/IOUringCompletionQueue;"); + + jobject submissionQueue = (*env)->CallObjectMethod(env, ringBuffer, submissionQueueMethodId); + jobject completionQueue = (*env)->CallObjectMethod(env, ringBuffer, completionQueueMethodId); + + jmethodID submissionQueueArrayAddressMethodId = (*env)->GetMethodID(env, ioUringSubmissionQueueClass, "getSubmissionQueueArrayAddress", "()J"); + jmethodID submissionQueueKringEntriesAddressMethodId = (*env)->GetMethodID(env, ioUringSubmissionQueueClass, "getKRingEntriesAddress", "()J"); + jmethodID submissionQueueRingFdMethodId = (*env)->GetMethodID(env, ioUringSubmissionQueueClass, "getRingFd", "()I"); + jmethodID submissionQueueRingAddressMethodId = (*env)->GetMethodID(env, ioUringSubmissionQueueClass, "getRingAddress", "()J"); + jmethodID submissionQueueRingSizeMethodId = (*env)->GetMethodID(env, ioUringSubmissionQueueClass, "getRingSize", "()I"); + + jmethodID completionQueueRingAddressMethodId = (*env)->GetMethodID(env, ioUringCompletionQueueClass, "getRingAddress", "()J"); + jmethodID completionQueueRingSizeMethodId = (*env)->GetMethodID(env, ioUringCompletionQueueClass, "getRingSize", "()I"); + + jlong submissionQueueArrayAddress = (*env)->CallLongMethod(env, submissionQueue, submissionQueueArrayAddressMethodId); + jlong submissionQueueKringEntriesAddress = (*env)->CallLongMethod(env, submissionQueue, submissionQueueKringEntriesAddressMethodId); + jint submissionQueueRingFd = (*env)->CallIntMethod(env, submissionQueue, submissionQueueRingFdMethodId); + jlong submissionQueueRingAddress = (*env)->CallLongMethod(env, submissionQueue, submissionQueueRingAddressMethodId); + jint submissionQueueRingSize = (*env)->CallIntMethod(env, submissionQueue, submissionQueueRingSizeMethodId); + + jlong completionQueueRingAddress = (*env)->CallLongMethod(env, completionQueue, completionQueueRingAddressMethodId); + jint completionQueueRingSize = (*env)->CallIntMethod(env, completionQueue, completionQueueRingSizeMethodId); + + unsigned submissionQueueKringEntries = *((unsigned int *) submissionQueueKringEntriesAddress); + + munmap((struct io_uring_sqe*) submissionQueueArrayAddress, submissionQueueKringEntries * sizeof(struct io_uring_sqe)); + munmap((void*) submissionQueueRingAddress, submissionQueueRingSize); + if (((void *) completionQueueRingAddress) && ((void *) completionQueueRingAddress) != ((void *) submissionQueueRingAddress)) { + munmap((void *)completionQueueRingAddress, completionQueueRingSize); + } + close(submissionQueueRingFd); +} + static int nettyBlockingSocket(int domain, int type, int protocol) { return socket(domain, type, protocol); } @@ -261,17 +313,14 @@ static jint netty_create_file(JNIEnv *env, jclass class) { return open("io-uring-test.txt", O_RDWR | O_TRUNC | O_CREAT, 0644); } -static void netty_io_uring_native_JNI_OnUnLoad(JNIEnv *env) { - // Todo OnUnLoad -} - // JNI Method Registration Table Begin static const JNINativeMethod method_table[] = { {"ioUringSetup", "(I)Lio/netty/channel/uring/RingBuffer;", (void *) netty_io_uring_setup}, + {"ioUringExit", "(Lio/netty/channel/uring/RingBuffer;)V", (void *) netty_io_uring_ring_buffer_exit}, {"createFile", "()I", (void *) netty_create_file}, {"ioUringEnter", "(IIII)I", (void *)netty_io_uring_enter}, {"eventFd", "()I", (void *) netty_epoll_native_eventFd}, - { "eventFdWrite", "(IJ)V", (void *) netty_epoll_native_eventFdWrite }, + {"eventFdWrite", "(IJ)V", (void *) netty_epoll_native_eventFdWrite }, {"ioUringRegisterEventFd", "(II)I", (void *) netty_io_uring_register_event_fd}, {"ioUringUnregisterEventFd", "(I)I", (void *) netty_io_uring_unregister_event_fd} }; @@ -280,6 +329,12 @@ static const jint method_table_size = // JNI Method Registration Table End JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) { + int ret = JNI_ERR; + int limitsOnLoadCalled = 0; + int errorsOnLoadCalled = 0; + int filedescriptorOnLoadCalled = 0; + int socketOnLoadCalled = 0; + int bufferOnLoadCalled = 0; JNIEnv *env; char *nettyClassName = NULL; @@ -316,22 +371,27 @@ JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) { if (netty_unix_limits_JNI_OnLoad(env, packagePrefix) == JNI_ERR) { goto done; } + limitsOnLoadCalled = 1; if (netty_unix_errors_JNI_OnLoad(env, packagePrefix) == JNI_ERR) { goto done; } + errorsOnLoadCalled = 1; if (netty_unix_filedescriptor_JNI_OnLoad(env, packagePrefix) == JNI_ERR) { goto done; } + filedescriptorOnLoadCalled = 1; if (netty_unix_socket_JNI_OnLoad(env, packagePrefix) == JNI_ERR) { goto done; } + socketOnLoadCalled = 1; if (netty_unix_buffer_JNI_OnLoad(env, packagePrefix) == JNI_ERR) { goto done; } + bufferOnLoadCalled = 1; NETTY_PREPEND(packagePrefix, "io/netty/channel/uring/RingBuffer", nettyClassName, done); @@ -354,9 +414,45 @@ JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) { NETTY_GET_METHOD(env, ioUringCompletionQueueClass, ioUringCommpletionQueueMethodId, "", "(JJJJJJIJI)V", done); - - done: + ret = NETTY_JNI_VERSION; +done: //unload + free(nettyClassName); - return NETTY_JNI_VERSION; + if (ret == JNI_ERR) { + if (limitsOnLoadCalled == 1) { + netty_unix_limits_JNI_OnUnLoad(env); + } + if (errorsOnLoadCalled == 1) { + netty_unix_errors_JNI_OnUnLoad(env); + } + if (filedescriptorOnLoadCalled == 1) { + netty_unix_filedescriptor_JNI_OnUnLoad(env); + } + if (socketOnLoadCalled == 1) { + netty_unix_socket_JNI_OnUnLoad(env); + } + if (bufferOnLoadCalled == 1) { + netty_unix_buffer_JNI_OnUnLoad(env); + } + + ringBufferMethodId = NULL; + ioUringSubmissionQueueMethodId = NULL; + ioUringCommpletionQueueMethodId = NULL; + ringBufferClass = NULL; + ioUringCompletionQueueClass = NULL; + ioUringSubmissionQueueClass = NULL; + + } + return ret; } + +JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved) { + // Todo OnUnLoad + JNIEnv* env; + if ((*vm)->GetEnv(vm, (void**) &env, NETTY_JNI_VERSION) != JNI_OK) { + // Something is wrong but nothing we can do about this :( + return; + } + netty_io_uring_native_JNI_OnUnLoad(env); +} \ No newline at end of file diff --git a/transport-native-io_uring/src/main/java/io/netty/channel/uring/IOUring.java b/transport-native-io_uring/src/main/java/io/netty/channel/uring/IOUring.java new file mode 100644 index 0000000000..187101f54e --- /dev/null +++ b/transport-native-io_uring/src/main/java/io/netty/channel/uring/IOUring.java @@ -0,0 +1,68 @@ +/* + * Copyright 2020 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.uring; + +import io.netty.channel.unix.FileDescriptor; +import io.netty.util.internal.SystemPropertyUtil; + +final class IOUring { + + private static final Throwable UNAVAILABILITY_CAUSE; + + static { + Throwable cause = null; + + if (SystemPropertyUtil.getBoolean("io.netty.transport.noNative", false)) { + cause = new UnsupportedOperationException( + "Native transport was explicit disabled with -Dio.netty.transport.noNative=true"); + } else { + RingBuffer ringBuffer = null; + try { + ringBuffer = Native.createRingBuffer(); + } catch (Throwable t) { + cause = t; + } finally { + if (ringBuffer != null) { + try { + ringBuffer.close(); + } catch (Exception ignore) { + // ignore + } + } + } + } + + UNAVAILABILITY_CAUSE = cause; + } + + public static boolean isAvailable() { + return UNAVAILABILITY_CAUSE == null; + } + + public static void ensureAvailability() { + if (UNAVAILABILITY_CAUSE != null) { + throw (Error) new UnsatisfiedLinkError( + "failed to load the required native library").initCause(UNAVAILABILITY_CAUSE); + } + } + + public static Throwable unavailabilityCause() { + return UNAVAILABILITY_CAUSE; + } + + private IOUring() { + } +} diff --git a/transport-native-io_uring/src/main/java/io/netty/channel/uring/Native.java b/transport-native-io_uring/src/main/java/io/netty/channel/uring/Native.java index 85aaac9ff4..819319d3bb 100644 --- a/transport-native-io_uring/src/main/java/io/netty/channel/uring/Native.java +++ b/transport-native-io_uring/src/main/java/io/netty/channel/uring/Native.java @@ -15,19 +15,52 @@ */ package io.netty.channel.uring; +import io.netty.channel.DefaultFileRegion; import io.netty.channel.unix.FileDescriptor; +import io.netty.channel.unix.PeerCredentials; 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.util.internal.ThrowableUtil; + +import java.io.IOException; +import java.nio.channels.Selector; import java.util.Locale; -final class Native { +import static io.netty.channel.unix.Socket.isIPv6Preferred; + +final class Native { private static final int DEFAULT_RING_SIZE = SystemPropertyUtil.getInt("io.netty.uring.ringSize", 32); - static { - loadNativeLibrary(); + + static { + Selector selector = null; + try { + // We call Selector.open() as this will under the hood cause IOUtil to be loaded. + // This is a workaround for a possible classloader deadlock that could happen otherwise: + // + // See https://github.com/netty/netty/issues/10187 + selector = Selector.open(); + } catch (IOException ignore) { + // Just ignore + } + try { + // First, try calling a side-effect free JNI method to see if the library was already + // loaded by the application. + Native.createFile(); + } catch (UnsatisfiedLinkError ignore) { + // The library was not previously loaded, load it now. + loadNativeLibrary(); + } finally { + try { + if (selector != null) { + selector.close(); + } + } catch (IOException ignore) { + // Just ignore + } + } Socket.initialize(); } @@ -55,11 +88,17 @@ final class Native { return new FileDescriptor(eventFd()); } + public static native void ioUringExit(RingBuffer ringBuffer); + private static native int eventFd(); // for testing(it is only temporary) public static native int createFile(); + public static Socket newSocketStream() { + return Socket.newSocketStream(); + } + private Native() { // utility } diff --git a/transport-native-io_uring/src/main/java/io/netty/channel/uring/RingBuffer.java b/transport-native-io_uring/src/main/java/io/netty/channel/uring/RingBuffer.java index b92b4b1500..f2bae05425 100644 --- a/transport-native-io_uring/src/main/java/io/netty/channel/uring/RingBuffer.java +++ b/transport-native-io_uring/src/main/java/io/netty/channel/uring/RingBuffer.java @@ -33,4 +33,8 @@ final class RingBuffer { return this.ioUringCompletionQueue; } + public void close() { + Native.ioUringExit(this); + } + } diff --git a/transport-native-io_uring/src/test/java/io/netty/channel/uring/IOUringSocketTest.java b/transport-native-io_uring/src/test/java/io/netty/channel/uring/IOUringSocketTest.java new file mode 100644 index 0000000000..f90c36fe70 --- /dev/null +++ b/transport-native-io_uring/src/test/java/io/netty/channel/uring/IOUringSocketTest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2020 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.uring; + +import io.netty.channel.unix.Socket; +import io.netty.channel.unix.tests.SocketTest; +import org.junit.BeforeClass; + +import static org.junit.Assume.assumeTrue; + +public class IOUringSocketTest extends SocketTest { + + @BeforeClass + public static void loadJNI() { + assumeTrue(IOUring.isAvailable()); + } + + @Override + protected Socket newSocket() { + return Socket.newSocketStream(); + } +}