View Javadoc
1   /*
2    * Copyright 2024 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.channel.uring;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.channel.ChannelOption;
20  import io.netty.channel.unix.Buffer;
21  import io.netty.channel.unix.Limits;
22  import io.netty.util.internal.PlatformDependent;
23  import io.netty.util.internal.SystemPropertyUtil;
24  import io.netty.util.internal.logging.InternalLogger;
25  import io.netty.util.internal.logging.InternalLoggerFactory;
26  
27  public final class IoUring {
28  
29      private static final Throwable UNAVAILABILITY_CAUSE;
30      private static final boolean IORING_CQE_F_SOCK_NONEMPTY_SUPPORTED;
31      private static final boolean IORING_SPLICE_SUPPORTED;
32      private static final boolean IORING_ACCEPT_NO_WAIT_SUPPORTED;
33      private static final boolean IORING_ACCEPT_MULTISHOT_SUPPORTED;
34      private static final boolean IORING_RECV_MULTISHOT_SUPPORTED;
35      private static final boolean IORING_RECVSEND_BUNDLE_SUPPORTED;
36      private static final boolean IORING_POLL_ADD_MULTISHOT_SUPPORTED;
37      private static final boolean IORING_REGISTER_IOWQ_MAX_WORKERS_SUPPORTED;
38      private static final boolean IORING_SETUP_SUBMIT_ALL_SUPPORTED;
39      private static final boolean IORING_SETUP_CQ_SIZE_SUPPORTED;
40      private static final boolean IORING_SETUP_SINGLE_ISSUER_SUPPORTED;
41      private static final boolean IORING_SETUP_DEFER_TASKRUN_SUPPORTED;
42      private static final boolean IORING_REGISTER_BUFFER_RING_SUPPORTED;
43      private static final boolean IORING_REGISTER_BUFFER_RING_INC_SUPPORTED;
44      private static final boolean IORING_ACCEPT_MULTISHOT_ENABLED;
45      private static final boolean IORING_RECV_MULTISHOT_ENABLED;
46      private static final boolean IORING_RECVSEND_BUNDLE_ENABLED;
47      private static final boolean IORING_POLL_ADD_MULTISHOT_ENABLED;
48  
49      static final int NUM_ELEMENTS_IOVEC;
50  
51      private static final InternalLogger logger;
52  
53      static {
54          logger = InternalLoggerFactory.getInstance(IoUring.class);
55          Throwable cause = null;
56          boolean socketNonEmptySupported = false;
57          boolean spliceSupported = false;
58          boolean acceptSupportNoWait = false;
59          boolean acceptMultishotSupported = false;
60          boolean recvsendBundleSupported = false;
61          boolean recvMultishotSupported = false;
62          boolean pollAddMultishotSupported = false;
63          boolean registerIowqWorkersSupported = false;
64          boolean submitAllSupported = false;
65          boolean setUpCqSizeSupported = false;
66          boolean singleIssuerSupported = false;
67          boolean deferTaskrunSupported = false;
68          boolean registerBufferRingSupported = false;
69          boolean registerBufferRingIncSupported = false;
70          int numElementsIoVec = 10;
71  
72          String kernelVersion = "[unknown]";
73          try {
74              if (SystemPropertyUtil.getBoolean("io.netty.transport.noNative", false)) {
75                  cause = new UnsupportedOperationException(
76                          "Native transport was explicit disabled with -Dio.netty.transport.noNative=true");
77              } else {
78                  kernelVersion = Native.kernelVersion();
79                  Native.checkKernelVersion(kernelVersion);
80                  if (PlatformDependent.javaVersion() >= 9) {
81                      RingBuffer ringBuffer = null;
82                      try {
83                          ringBuffer = Native.createRingBuffer(1, 0);
84                          if ((ringBuffer.features() & Native.IORING_FEAT_SUBMIT_STABLE) == 0) {
85                              // This should only happen on kernels < 5.4 which we don't support anyway.
86                              throw new UnsupportedOperationException("IORING_FEAT_SUBMIT_STABLE not supported!");
87                          }
88                          // IOV_MAX should be 1024 and an IOV is 16 bytes which means that by default we reserve around
89                          // 160kb.
90                          numElementsIoVec = SystemPropertyUtil.getInt(
91                                  "io.netty.iouring.numElementsIoVec", 10 *  Limits.IOV_MAX);
92                          Native.checkAllIOSupported(ringBuffer.fd());
93                          socketNonEmptySupported = Native.isCqeFSockNonEmptySupported(ringBuffer.fd());
94                          spliceSupported = Native.isSpliceSupported(ringBuffer.fd());
95                          recvsendBundleSupported = (ringBuffer.features() & Native.IORING_FEAT_RECVSEND_BUNDLE) != 0;
96                          // IORING_FEAT_RECVSEND_BUNDLE was added in the same release.
97                          acceptSupportNoWait = recvsendBundleSupported;
98                          // Explicit disable recvsend bundles as there seems to be a bug which cause
99                          // and AssertionError which leads to the CI running out of memory.
100                         // We will enable this again once we found the bug and fixed it.
101                         //
102                         // TODO: Remove once fixed.
103                         recvsendBundleSupported = false;
104                         acceptMultishotSupported = Native.isAcceptMultishotSupported(ringBuffer.fd());
105                         recvMultishotSupported = Native.isRecvMultishotSupported();
106                         pollAddMultishotSupported = Native.isPollAddMultiShotSupported(ringBuffer.fd());
107                         registerIowqWorkersSupported = Native.isRegisterIoWqWorkerSupported(ringBuffer.fd());
108                         submitAllSupported = Native.ioUringSetupSupportsFlags(Native.IORING_SETUP_SUBMIT_ALL);
109                         setUpCqSizeSupported = Native.ioUringSetupSupportsFlags(Native.IORING_SETUP_CQSIZE);
110                         singleIssuerSupported = Native.ioUringSetupSupportsFlags(Native.IORING_SETUP_SINGLE_ISSUER);
111                         // IORING_SETUP_DEFER_TASKRUN requires to also set IORING_SETUP_SINGLE_ISSUER.
112                         // See https://manpages.debian.org/unstable/liburing-dev/io_uring_setup.2.en.html
113                         deferTaskrunSupported = Native.ioUringSetupSupportsFlags(
114                                 Native.IORING_SETUP_SINGLE_ISSUER | Native.IORING_SETUP_DEFER_TASKRUN);
115                         registerBufferRingSupported = Native.isRegisterBufferRingSupported(ringBuffer.fd(), 0);
116                         registerBufferRingIncSupported = Native.isRegisterBufferRingSupported(ringBuffer.fd(),
117                                 Native.IOU_PBUF_RING_INC);
118                     } finally {
119                         if (ringBuffer != null) {
120                             try {
121                                 ringBuffer.close();
122                             } catch (Exception ignore) {
123                                 // ignore
124                             }
125                         }
126                     }
127                 } else {
128                     cause = new UnsupportedOperationException("Java 9+ is required");
129                 }
130             }
131         } catch (Throwable t) {
132             cause = t;
133         }
134         if (cause != null) {
135             if (logger.isTraceEnabled()) {
136                 logger.debug("IoUring support is not available using kernel {}", kernelVersion, cause);
137             } else if (logger.isDebugEnabled()) {
138                 logger.debug("IoUring support is not available using kernel {}: {}", kernelVersion, cause.getMessage());
139             }
140         } else {
141             if (logger.isDebugEnabled()) {
142                 logger.debug("IoUring support is available using kernel {} (" +
143                         "CQE_F_SOCK_NONEMPTY_SUPPORTED={}, " +
144                         "SPLICE_SUPPORTED={}, " +
145                         "ACCEPT_NO_WAIT_SUPPORTED={}, " +
146                         "ACCEPT_MULTISHOT_SUPPORTED={}, " +
147                         "POLL_ADD_MULTISHOT_SUPPORTED={} " +
148                         "RECV_MULTISHOT_SUPPORTED={}, " +
149                         "IORING_RECVSEND_BUNDLE_SUPPORTED={}, " +
150                         "REGISTER_IOWQ_MAX_WORKERS_SUPPORTED={}, " +
151                         "SETUP_SUBMIT_ALL_SUPPORTED={}, " +
152                         "SETUP_SINGLE_ISSUER_SUPPORTED={}, " +
153                         "SETUP_DEFER_TASKRUN_SUPPORTED={}, " +
154                         "REGISTER_BUFFER_RING_SUPPORTED={}, " +
155                         "REGISTER_BUFFER_RING_INC_SUPPORTED={}" +
156                         ")", kernelVersion, socketNonEmptySupported, spliceSupported, acceptSupportNoWait,
157                         acceptMultishotSupported, pollAddMultishotSupported, recvMultishotSupported,
158                         recvsendBundleSupported, registerIowqWorkersSupported, submitAllSupported,
159                         singleIssuerSupported, deferTaskrunSupported,
160                         registerBufferRingSupported, registerBufferRingIncSupported);
161             }
162         }
163         UNAVAILABILITY_CAUSE = cause;
164         IORING_CQE_F_SOCK_NONEMPTY_SUPPORTED = socketNonEmptySupported;
165         IORING_SPLICE_SUPPORTED = spliceSupported;
166         IORING_ACCEPT_NO_WAIT_SUPPORTED = acceptSupportNoWait;
167         IORING_ACCEPT_MULTISHOT_SUPPORTED = acceptMultishotSupported;
168         IORING_RECV_MULTISHOT_SUPPORTED = recvMultishotSupported;
169         IORING_RECVSEND_BUNDLE_SUPPORTED = recvsendBundleSupported;
170         IORING_POLL_ADD_MULTISHOT_SUPPORTED = pollAddMultishotSupported;
171         IORING_REGISTER_IOWQ_MAX_WORKERS_SUPPORTED = registerIowqWorkersSupported;
172         IORING_SETUP_SUBMIT_ALL_SUPPORTED = submitAllSupported;
173         IORING_SETUP_CQ_SIZE_SUPPORTED = setUpCqSizeSupported;
174         IORING_SETUP_SINGLE_ISSUER_SUPPORTED = singleIssuerSupported;
175         IORING_SETUP_DEFER_TASKRUN_SUPPORTED = deferTaskrunSupported;
176         IORING_REGISTER_BUFFER_RING_SUPPORTED = registerBufferRingSupported;
177         IORING_REGISTER_BUFFER_RING_INC_SUPPORTED = registerBufferRingIncSupported;
178 
179         IORING_ACCEPT_MULTISHOT_ENABLED = IORING_ACCEPT_MULTISHOT_SUPPORTED && SystemPropertyUtil.getBoolean(
180                 "io.netty.iouring.acceptMultiShotEnabled", true);
181         IORING_RECV_MULTISHOT_ENABLED = IORING_RECV_MULTISHOT_SUPPORTED && SystemPropertyUtil.getBoolean(
182                 "io.netty.iouring.recvMultiShotEnabled", true);
183         IORING_RECVSEND_BUNDLE_ENABLED = IORING_RECVSEND_BUNDLE_SUPPORTED && SystemPropertyUtil.getBoolean(
184                 "io.netty.iouring.recvsendBundleEnabled", true);
185         IORING_POLL_ADD_MULTISHOT_ENABLED = IORING_POLL_ADD_MULTISHOT_SUPPORTED && SystemPropertyUtil.getBoolean(
186                "io.netty.iouring.pollAddMultishotEnabled", true);
187         NUM_ELEMENTS_IOVEC = numElementsIoVec;
188     }
189 
190     public static boolean isAvailable() {
191         return UNAVAILABILITY_CAUSE == null;
192     }
193 
194     /**
195      * Returns {@code true} if the io_uring native transport is both {@linkplain #isAvailable() available} and supports
196      * {@linkplain ChannelOption#TCP_FASTOPEN_CONNECT client-side TCP FastOpen}.
197      *
198      * @return {@code true} if it's possible to use client-side TCP FastOpen via io_uring, otherwise {@code false}.
199      */
200     public static boolean isTcpFastOpenClientSideAvailable() {
201         return isAvailable() && Native.IS_SUPPORTING_TCP_FASTOPEN_CLIENT;
202     }
203 
204     /**
205      * Returns {@code true} if the io_uring native transport is both {@linkplain #isAvailable() available} and supports
206      * {@linkplain ChannelOption#TCP_FASTOPEN server-side TCP FastOpen}.
207      *
208      * @return {@code true} if it's possible to use server-side TCP FastOpen via io_uring, otherwise {@code false}.
209      */
210     public static boolean isTcpFastOpenServerSideAvailable() {
211         return isAvailable() && Native.IS_SUPPORTING_TCP_FASTOPEN_SERVER;
212     }
213 
214     static boolean isCqeFSockNonEmptySupported() {
215         return IORING_CQE_F_SOCK_NONEMPTY_SUPPORTED;
216     }
217 
218     /**
219      * Returns if SPLICE is supported or not.
220      *
221      * @return {@code true} if supported, {@code false} otherwise.
222      */
223     public static boolean isSpliceSupported() {
224         return IORING_SPLICE_SUPPORTED;
225     }
226 
227     static boolean isAcceptNoWaitSupported() {
228         return IORING_ACCEPT_NO_WAIT_SUPPORTED;
229     }
230 
231     static boolean isAcceptMultishotSupported() {
232         return IORING_ACCEPT_MULTISHOT_SUPPORTED;
233     }
234 
235     static boolean isRecvMultishotSupported() {
236         return IORING_RECV_MULTISHOT_SUPPORTED;
237     }
238 
239     static boolean isRecvsendBundleSupported() {
240         return IORING_RECVSEND_BUNDLE_SUPPORTED;
241     }
242 
243     static boolean isPollAddMultishotSupported() {
244         return IORING_POLL_ADD_MULTISHOT_SUPPORTED;
245     }
246 
247     static boolean isRegisterIowqMaxWorkersSupported() {
248         return IORING_REGISTER_IOWQ_MAX_WORKERS_SUPPORTED;
249     }
250 
251     static boolean isSetupCqeSizeSupported() {
252         return IORING_SETUP_CQ_SIZE_SUPPORTED;
253     }
254 
255     static boolean isSetupSubmitAllSupported() {
256         return IORING_SETUP_SUBMIT_ALL_SUPPORTED;
257     }
258 
259     static boolean isSetupSingleIssuerSupported() {
260         return IORING_SETUP_SINGLE_ISSUER_SUPPORTED;
261     }
262 
263     static boolean isSetupDeferTaskrunSupported() {
264         return IORING_SETUP_DEFER_TASKRUN_SUPPORTED;
265     }
266 
267     /**
268      * Returns if it is supported to use a buffer ring.
269      *
270      * @return {@code true} if supported, {@code false} otherwise.
271      */
272     public static boolean isRegisterBufferRingSupported() {
273         return IORING_REGISTER_BUFFER_RING_SUPPORTED;
274     }
275 
276     /**
277      * Returns if it is supported to use an incremental buffer ring.
278      *
279      * @return {@code true} if supported, {@code false} otherwise.
280      */
281     public static boolean isRegisterBufferRingIncSupported() {
282         return IORING_REGISTER_BUFFER_RING_INC_SUPPORTED;
283     }
284 
285     /**
286      * Returns if multi-shot ACCEPT is used or not.
287      *
288      * @return {@code true} if enabled, {@code false} otherwise.
289      */
290     public static boolean isAcceptMultishotEnabled() {
291         return IORING_ACCEPT_MULTISHOT_ENABLED;
292     }
293 
294     /**
295      * Returns if multi-shot RECV is used or not.
296      *
297      * @return {@code true} if enabled, {@code false} otherwise.
298      */
299     public static boolean isRecvMultishotEnabled() {
300         return IORING_RECV_MULTISHOT_ENABLED;
301     }
302 
303     /**
304      * Returns if RECVSEND bundles are used or not.
305      *
306      * @return {@code true} if enabled, {@code false} otherwise.
307      */
308     public static boolean isRecvsendBundleEnabled() {
309         return IORING_RECVSEND_BUNDLE_ENABLED;
310     }
311 
312     /**
313      * Returns if multi-shot POLL_ADD is used or not.
314      *
315      * @return {@code true} if enabled, {@code false} otherwise.
316      */
317     public static boolean isPollAddMultishotEnabled() {
318         return IORING_POLL_ADD_MULTISHOT_ENABLED;
319     }
320 
321     public static void ensureAvailability() {
322         if (UNAVAILABILITY_CAUSE != null) {
323             throw (Error) new UnsatisfiedLinkError(
324                     "failed to load the required native library").initCause(UNAVAILABILITY_CAUSE);
325         }
326     }
327 
328     static long memoryAddress(ByteBuf buffer) {
329         if (buffer.hasMemoryAddress()) {
330             return buffer.memoryAddress();
331         }
332         return Buffer.memoryAddress(buffer.internalNioBuffer(0, buffer.capacity()));
333     }
334 
335     public static Throwable unavailabilityCause() {
336         return UNAVAILABILITY_CAUSE;
337     }
338 
339     private IoUring() {
340     }
341 }