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.channel.IoHandle;
19  import io.netty.channel.IoHandler;
20  import io.netty.channel.IoHandlerContext;
21  import io.netty.channel.IoHandlerFactory;
22  import io.netty.channel.IoOps;
23  import io.netty.channel.IoRegistration;
24  import io.netty.channel.unix.Buffer;
25  import io.netty.channel.unix.Errors;
26  import io.netty.channel.unix.FileDescriptor;
27  import io.netty.channel.unix.IovArray;
28  import io.netty.util.collection.IntObjectHashMap;
29  import io.netty.util.collection.IntObjectMap;
30  import io.netty.util.concurrent.ThreadAwareExecutor;
31  import io.netty.util.internal.CleanableDirectBuffer;
32  import io.netty.util.internal.ObjectUtil;
33  import io.netty.util.internal.StringUtil;
34  import io.netty.util.internal.logging.InternalLogger;
35  import io.netty.util.internal.logging.InternalLoggerFactory;
36  
37  import java.io.IOException;
38  import java.io.UncheckedIOException;
39  import java.nio.ByteBuffer;
40  import java.util.ArrayList;
41  import java.util.Collection;
42  import java.util.List;
43  import java.util.concurrent.TimeUnit;
44  import java.util.concurrent.atomic.AtomicBoolean;
45  import java.util.concurrent.atomic.AtomicInteger;
46  
47  import static java.lang.Math.max;
48  import static java.lang.Math.min;
49  import static java.util.Objects.requireNonNull;
50  
51  /**
52   * {@link IoHandler} which is implemented in terms of the Linux-specific {@code io_uring} API.
53   */
54  public final class IoUringIoHandler implements IoHandler {
55      private static final InternalLogger logger = InternalLoggerFactory.getInstance(IoUringIoHandler.class);
56      private static final int WAKEUP_CLOSED = 1 << 30;
57  
58      private final RingBuffer ringBuffer;
59      private final IntObjectMap<IoUringBufferRing> registeredIoUringBufferRing;
60      private final IntObjectMap<DefaultIoUringIoRegistration> registrations;
61      // The maximum number of bytes for an InetAddress / Inet6Address
62      private final byte[] inet4AddressArray = new byte[SockaddrIn.IPV4_ADDRESS_LENGTH];
63      private final byte[] inet6AddressArray = new byte[SockaddrIn.IPV6_ADDRESS_LENGTH];
64  
65      private final AtomicBoolean eventfdAsyncNotify = new AtomicBoolean();
66      private final AtomicInteger wakeupWriters = new AtomicInteger();
67      private final FileDescriptor eventfd;
68      private final CleanableDirectBuffer eventfdReadBufCleanable;
69      private final ByteBuffer eventfdReadBuf;
70      private final long eventfdReadBufAddress;
71      private final CleanableDirectBuffer timeoutMemoryCleanable;
72      private final ByteBuffer timeoutMemory;
73      private final long timeoutMemoryAddress;
74      private final IovArray iovArray;
75      private final IovArrayReferenceCollector iovArrayReferenceCollector;
76      private final MsgHdrMemoryArray msgHdrMemoryArray;
77      private long eventfdReadSubmitted;
78      private boolean eventFdClosing;
79      private volatile boolean shuttingDown;
80      private boolean closeCompleted;
81      private final PendingOpMap pendingOps;
82      private int nextRegistrationId = 1;
83  
84      private static final long INVALID_ID = 0;
85      private static final long EVENTFD_TOKEN = PendingOpMap.token(1);
86      private static final long RINGFD_TOKEN = PendingOpMap.token(2);
87      private static final int KERNEL_TIMESPEC_SIZE = 16; //__kernel_timespec
88  
89      private static final int KERNEL_TIMESPEC_TV_SEC_FIELD = 0;
90      private static final int KERNEL_TIMESPEC_TV_NSEC_FIELD = 8;
91  
92      private final ThreadAwareExecutor executor;
93  
94      // Caching completion callback to avoid creating a new instance for each poll.
95      private final CompletionCallback completionCallback = this::handle;
96  
97      IoUringIoHandler(ThreadAwareExecutor executor, IoUringIoHandlerConfig config) {
98          // Ensure that we load all native bits as otherwise it may fail when try to use native methods in IovArray
99          IoUring.ensureAvailability();
100         this.executor = requireNonNull(executor, "executor");
101         requireNonNull(config, "config");
102         int setupFlags = Native.setupFlags(config.singleIssuer());
103 
104         //The default cq size is always twice the ringSize.
105         // It only makes sense when the user actually specifies the cq ring size.
106         int cqSize = 2 * config.getRingSize();
107         if (config.needSetupCqeSize()) {
108             assert IoUring.isSetupCqeSizeSupported();
109             setupFlags |= Native.IORING_SETUP_CQSIZE;
110             cqSize = config.getCqSize();
111         }
112         this.ringBuffer = Native.createRingBuffer(config.getRingSize(), cqSize, setupFlags);
113         if (IoUring.isRegisterIowqMaxWorkersSupported() && config.needRegisterIowqMaxWorker()) {
114             int maxBoundedWorker = Math.max(config.getMaxBoundedWorker(), 0);
115             int maxUnboundedWorker = Math.max(config.getMaxUnboundedWorker(), 0);
116             int result = Native.ioUringRegisterIoWqMaxWorkers(ringBuffer.fd(), maxBoundedWorker, maxUnboundedWorker);
117             if (result < 0) {
118                 // Close ringBuffer before throwing to ensure we release all memory on failure.
119                 ringBuffer.close();
120                 throw new UncheckedIOException(Errors.newIOException("io_uring_register", result));
121             }
122         }
123 
124         registeredIoUringBufferRing = new IntObjectHashMap<>();
125         Collection<IoUringBufferRingConfig> bufferRingConfigs = config.getInternBufferRingConfigs();
126         if (bufferRingConfigs != null && !bufferRingConfigs.isEmpty()) {
127             for (IoUringBufferRingConfig bufferRingConfig : bufferRingConfigs) {
128                 try {
129                     IoUringBufferRing ring = newBufferRing(ringBuffer.fd(), bufferRingConfig);
130                     registeredIoUringBufferRing.put(bufferRingConfig.bufferGroupId(), ring);
131                 } catch (Errors.NativeIoException e) {
132                     for (IoUringBufferRing bufferRing : registeredIoUringBufferRing.values()) {
133                         bufferRing.close();
134                     }
135                     // Close ringBuffer before throwing to ensure we release all memory on failure.
136                     ringBuffer.close();
137                     throw new UncheckedIOException(e);
138                 }
139             }
140         }
141 
142         registrations = new IntObjectHashMap<>();
143         pendingOps = new PendingOpMap(IoUring.DEFAULT_PENDING_OPS_INITIAL_CAPACITY);
144         eventfd = Native.newBlockingEventFd();
145         eventfdReadBufCleanable = Buffer.allocateDirectBufferWithNativeOrder(Long.BYTES);
146         eventfdReadBuf = eventfdReadBufCleanable.buffer();
147         eventfdReadBufAddress = Buffer.memoryAddress(eventfdReadBuf);
148         timeoutMemoryCleanable = Buffer.allocateDirectBufferWithNativeOrder(KERNEL_TIMESPEC_SIZE);
149         timeoutMemory = timeoutMemoryCleanable.buffer();
150         timeoutMemoryAddress = Buffer.memoryAddress(timeoutMemory);
151         iovArray = new IovArray(IoUring.NUM_ELEMENTS_IOVEC);
152         iovArrayReferenceCollector = new IovArrayReferenceCollector(iovArray);
153         msgHdrMemoryArray = new MsgHdrMemoryArray((short) 1024);
154     }
155 
156     @Override
157     public void initialize() {
158         ringBuffer.enable();
159         // Fill all buffer rings now.
160         for (IoUringBufferRing bufferRing : registeredIoUringBufferRing.values()) {
161             bufferRing.initialize();
162         }
163     }
164 
165     @Override
166     public int run(IoHandlerContext context) {
167         if (closeCompleted) {
168             if (context.shouldReportActiveIoTime()) {
169                 context.reportActiveIoTime(0);
170             }
171             return 0;
172         }
173         SubmissionQueue submissionQueue = ringBuffer.ioUringSubmissionQueue();
174         CompletionQueue completionQueue = ringBuffer.ioUringCompletionQueue();
175         if (!completionQueue.hasCompletions() && context.canBlock()) {
176             if (eventfdReadSubmitted == 0) {
177                 submitEventFdRead();
178             }
179             long timeoutNanos = context.deadlineNanos() == -1 ? -1 : context.delayNanos(System.nanoTime());
180             submitAndWaitWithTimeout(submissionQueue, false, timeoutNanos);
181         } else {
182             // Even if we have some completions already pending we can still try to even fetch more.
183             submitAndClearNow(submissionQueue);
184         }
185 
186         int ioCompletions;
187         if (context.shouldReportActiveIoTime()) {
188             long activeIoStartTimeNanos = System.nanoTime();
189             ioCompletions = processCompletionsAndHandleOverflow(
190                     submissionQueue, completionQueue, completionCallback);
191             long activeIoEndTimeNanos = System.nanoTime();
192             context.reportActiveIoTime(activeIoEndTimeNanos - activeIoStartTimeNanos);
193         } else {
194             ioCompletions = processCompletionsAndHandleOverflow(
195                     submissionQueue, completionQueue, completionCallback);
196         }
197         return ioCompletions;
198     }
199 
200     int submitIfFullAndGetRemaining() {
201         SubmissionQueue submissionQueue = ringBuffer.ioUringSubmissionQueue();
202         if (submissionQueue.remaining() == 0) {
203             if (submitAndClearNow(submissionQueue) == 0) {
204                 throw new IllegalStateException("Submission queue is full and no submissions were accepted");
205             }
206         }
207         return submissionQueue.remaining();
208     }
209 
210     private boolean needSubmit(int sqFlags) {
211         SubmissionQueue submissionQueue = ringBuffer.ioUringSubmissionQueue();
212         return submissionQueue.count() > 0
213                 || (sqFlags & (Native.IORING_SQ_CQ_OVERFLOW | Native.IORING_SQ_TASKRUN)) != 0;
214     }
215 
216     private int processCompletionsAndHandleOverflow(SubmissionQueue submissionQueue, CompletionQueue completionQueue,
217                                          CompletionCallback callback) {
218         int ioCompletions = 0;
219         for (int i = 0; i < 128; i++) {
220             long packed = completionQueue.process(callback);
221             int total = (int) (packed >>> 32);
222             ioCompletions += (int) packed;
223             int sqFlags = submissionQueue.flags();
224             if ((sqFlags & Native.IORING_SQ_CQ_OVERFLOW) != 0) {
225                 logger.warn("CompletionQueue overflow detected, consider increasing size: {} ",
226                         completionQueue.ringEntries);
227             }
228             if (total == 0) {
229                 if (!needSubmit(sqFlags)) {
230                     break;
231                 }
232                 submitAndClearNow0(submissionQueue);
233             }
234         }
235         return ioCompletions;
236     }
237 
238     private int submitAndClearNow(SubmissionQueue submissionQueue) {
239         if (needSubmit(submissionQueue.flags())) {
240             return submitAndClearNow0(submissionQueue);
241         }
242         return 0;
243     }
244 
245     private int submitAndClearNow0(SubmissionQueue submissionQueue) {
246 
247         int submitted = submissionQueue.submitAndGetNow();
248 
249         // Clear the iovArray as we can re-use it now as things are considered stable after submission:
250         // See https://man7.org/linux/man-pages/man3/io_uring_prep_sendmsg.3.html
251         iovArray.clear();
252         msgHdrMemoryArray.clear();
253         return submitted;
254     }
255 
256     private static IoUringBufferRing newBufferRing(int ringFd, IoUringBufferRingConfig bufferRingConfig)
257             throws Errors.NativeIoException {
258         short bufferRingSize = bufferRingConfig.bufferRingSize();
259         short bufferGroupId = bufferRingConfig.bufferGroupId();
260         int flags = bufferRingConfig.isIncremental() ? Native.IOU_PBUF_RING_INC : 0;
261         long ioUringBufRingAddr = Native.ioUringRegisterBufRing(ringFd, bufferRingSize, bufferGroupId, flags);
262         if (ioUringBufRingAddr < 0) {
263             throw Errors.newIOException("ioUringRegisterBufRing", (int) ioUringBufRingAddr);
264         }
265         return new IoUringBufferRing(ringFd,
266                 Buffer.wrapMemoryAddressWithNativeOrder(ioUringBufRingAddr, Native.ioUringBufRingSize(bufferRingSize)),
267                 bufferRingSize, bufferRingConfig.batchSize(),
268                 bufferGroupId, bufferRingConfig.isIncremental(), bufferRingConfig.allocator(),
269                 bufferRingConfig.isBatchAllocation()
270         );
271     }
272 
273     IoUringBufferRing findBufferRing(short bgId) {
274         IoUringBufferRing cached = registeredIoUringBufferRing.get(bgId);
275         if (cached != null) {
276             return cached;
277         }
278         throw new IllegalArgumentException(
279                 String.format("Cant find bgId:%d, please register it in ioUringIoHandler", bgId)
280         );
281     }
282 
283     private static void handleLoopException(Throwable throwable) {
284         logger.warn("Unexpected exception in the IO event loop.", throwable);
285 
286         // Prevent possible consecutive immediate failures that lead to
287         // excessive CPU consumption.
288         try {
289             Thread.sleep(100);
290         } catch (InterruptedException ignore) {
291             // ignore
292         }
293     }
294 
295     private boolean handle(int res, int flags, long udata, ByteBuffer extraCqeData) {
296         try {
297             if (udata == EVENTFD_TOKEN) {
298                 handleEventFdRead();
299                 return false;
300             }
301             if (udata == RINGFD_TOKEN) {
302                 return false;
303             }
304             if (udata >= 0) {
305                 handleFastPath(res, flags, udata, extraCqeData);
306                 return true;
307             }
308             handleSlowPath(res, flags, udata, extraCqeData);
309             return true;
310         } catch (Error e) {
311             throw e;
312         } catch (Throwable throwable) {
313             handleLoopException(throwable);
314             return true;
315         }
316     }
317 
318     private void handleFastPath(int res, int flags, long udata, ByteBuffer extraCqeData) {
319         int id = UserData.decodeId(udata);
320         byte op = UserData.decodeOp(udata);
321         long userData = UserData.decodeData(udata);
322         DefaultIoUringIoRegistration registration = registrations.get(id);
323         if (registration != null) {
324             traceCompletion(registration, id, op, res);
325             registration.handle(res, flags, op, userData, extraCqeData);
326             return;
327         }
328         if (logger.isDebugEnabled()) {
329             logger.debug("ignoring packed completion for unknown registration (registrationId={}, op={}, userData={},"
330                             + " res={})",
331                     id, Native.opToStr(op), userData, res);
332         }
333     }
334 
335     private void handleSlowPath(int res, int flags, long udata, ByteBuffer extraCqeData) {
336         long sequence = PendingOpMap.tokenSequence(udata);
337         int slot = pendingOps.findSlot(udata);
338         if (slot != -1) {
339             int registrationId = pendingOps.registrationId(slot);
340             DefaultIoUringIoRegistration registration = registrations.get(registrationId);
341             byte op = pendingOps.op(slot);
342             long userData = pendingOps.userData(slot);
343 
344             // Recycle if this completion is terminal (no more CQEs expected for this SQE).
345             if ((flags & Native.IORING_CQE_F_MORE) == 0) {
346                 pendingOps.release(slot);
347             }
348 
349             // Resolve slow-path completions through the live registration table to align with the fast path.
350             if (registration != null) {
351                 traceCompletion(registration, registrationId, op, res);
352                 registration.handle(res, flags, op, userData, extraCqeData);
353                 return;
354             }
355             if (logger.isDebugEnabled()) {
356                 logger.debug("ignoring slow-path completion for missing registration (registrationId={}, seq={}, "
357                                 + "op={}, userData={}, res={})",
358                         registrationId, sequence, Native.opToStr(op), userData, res);
359             }
360             return;
361         }
362         if (logger.isDebugEnabled()) {
363             logger.debug("ignoring slow-path completion for unknown sequence (seq={}, res={})", sequence, res);
364         }
365     }
366 
367     private void traceCompletion(DefaultIoUringIoRegistration registration, int registrationId, byte op, int res) {
368         if (!logger.isTraceEnabled()) {
369             return;
370         }
371         int fd = registration.fd();
372         if (fd != -1) {
373             logger.trace("completed(ring {}): {}(fd={}, res={})",
374                     ringBuffer.fd(), Native.opToStr(op), fd, res);
375         } else {
376             logger.trace("completed(ring {}): {}(registrationId={}, res={})",
377                     ringBuffer.fd(), Native.opToStr(op), registrationId, res);
378         }
379     }
380 
381     private void handleEventFdRead() {
382         eventfdReadSubmitted = 0;
383         if (!eventFdClosing) {
384             eventfdAsyncNotify.set(false);
385             submitEventFdRead();
386         }
387     }
388 
389     private void submitEventFdRead() {
390         SubmissionQueue submissionQueue = ringBuffer.ioUringSubmissionQueue();
391         eventfdReadSubmitted = submissionQueue.addEventFdRead(
392                 eventfd.intValue(), eventfdReadBufAddress, 0, 8, EVENTFD_TOKEN);
393     }
394 
395     private int submitAndWaitWithTimeout(SubmissionQueue submissionQueue,
396                                          boolean linkTimeout, long timeoutNanoSeconds) {
397         if (timeoutNanoSeconds != -1) {
398             // We use the same timespec pointer for all add*Timeout operations. This only works because we call
399             // submit directly after it. This ensures the submitted timeout is considered "stable" and so can be reused.
400             long seconds, nanoSeconds;
401             if (timeoutNanoSeconds == 0) {
402                 seconds = 0;
403                 nanoSeconds = 0;
404             } else {
405                 seconds = (int) min(timeoutNanoSeconds / 1000000000L, Integer.MAX_VALUE);
406                 nanoSeconds = (int) max(timeoutNanoSeconds - seconds * 1000000000L, 0);
407             }
408 
409             timeoutMemory.putLong(KERNEL_TIMESPEC_TV_SEC_FIELD, seconds);
410             timeoutMemory.putLong(KERNEL_TIMESPEC_TV_NSEC_FIELD, nanoSeconds);
411             if (linkTimeout) {
412                 submissionQueue.addLinkTimeout(timeoutMemoryAddress, RINGFD_TOKEN);
413             } else {
414                 submissionQueue.addTimeout(timeoutMemoryAddress, RINGFD_TOKEN);
415             }
416         }
417         int submitted = submissionQueue.submitAndGet();
418         // Clear the iovArray as we can re-use it now as things are considered stable after submission:
419         // See https://man7.org/linux/man-pages/man3/io_uring_prep_sendmsg.3.html
420         iovArray.clear();
421         msgHdrMemoryArray.clear();
422         return submitted;
423     }
424 
425     @Override
426     public void prepareToDestroy() {
427         shuttingDown = true;
428         CompletionQueue completionQueue = ringBuffer.ioUringCompletionQueue();
429         SubmissionQueue submissionQueue = ringBuffer.ioUringSubmissionQueue();
430 
431         List<DefaultIoUringIoRegistration> copy = new ArrayList<>(registrations.values());
432 
433         for (DefaultIoUringIoRegistration registration: copy) {
434             registration.close();
435         }
436 
437         // Write to the eventfd to ensure that if we submitted a read for the eventfd we will see the completion event.
438         Native.eventFdWrite(eventfd.intValue(), 1L);
439 
440         // Ensure all previously submitted IOs get to complete before tearing down everything.
441         submissionQueue.addNop((byte) Native.IOSQE_IO_DRAIN, RINGFD_TOKEN);
442 
443         // Submit everything and wait until we could drain i.
444         submissionQueue.submitAndGet();
445 
446         while (completionQueue.hasCompletions()) {
447             processCompletionsAndHandleOverflow(submissionQueue, completionQueue, completionCallback);
448             if (submissionQueue.count() > 0) {
449                 submissionQueue.submitAndGetNow();
450             }
451         }
452     }
453 
454     @Override
455     public void destroy() {
456         SubmissionQueue submissionQueue = ringBuffer.ioUringSubmissionQueue();
457         CompletionQueue completionQueue = ringBuffer.ioUringCompletionQueue();
458         drainEventFd();
459         if (submissionQueue.remaining() < 2) {
460             // We need to submit 2 linked operations. Since they are linked, we cannot allow a submit-call to
461             // separate them. We don't have enough room (< 2) in the queue, so we submit now to make more room.
462             submissionQueue.submit();
463         }
464         // Try to drain all the IO from the queue first...
465         // We need to also specify the Native.IOSQE_LINK flag for it to work as otherwise it is not correctly linked
466         // with the timeout.
467         // See:
468         // - https://man7.org/linux/man-pages/man2/io_uring_enter.2.html
469         // - https://git.kernel.dk/cgit/liburing/commit/?h=link-timeout&id=bc1bd5e97e2c758d6fd975bd35843b9b2c770c5a
470         submissionQueue.addNop((byte) (Native.IOSQE_IO_DRAIN | Native.IOSQE_LINK), RINGFD_TOKEN);
471         // ... but only wait for 200 milliseconds on this
472         submitAndWaitWithTimeout(submissionQueue, true, TimeUnit.MILLISECONDS.toNanos(200));
473         completionQueue.process(completionCallback);
474         for (IoUringBufferRing ioUringBufferRing : registeredIoUringBufferRing.values()) {
475             ioUringBufferRing.close();
476         }
477         completeRingClose();
478     }
479 
480     // We need to prevent the race condition where a wakeup event is submitted to a file descriptor that has
481     // already been freed (and potentially reallocated by the OS). Because submitted events is gated on the
482     // `eventfdAsyncNotify` flag we can close the gate but may need to read any outstanding events that have
483     // (or will) be written.
484     private void drainEventFd() {
485         CompletionQueue completionQueue = ringBuffer.ioUringCompletionQueue();
486         SubmissionQueue submissionQueue = ringBuffer.ioUringSubmissionQueue();
487         assert !eventFdClosing;
488         eventFdClosing = true;
489         boolean eventPending = eventfdAsyncNotify.getAndSet(true);
490         if (eventPending) {
491             // There is an event that has been or will be written by another thread, so we must wait for the event.
492             // Make sure we're actually listening for writes to the event fd.
493             while (eventfdReadSubmitted == 0) {
494                 submitEventFdRead();
495                 submissionQueue.submit();
496             }
497             // Drain the eventfd of the pending wakup.
498             class DrainFdEventCallback implements CompletionCallback {
499                 boolean eventFdDrained;
500 
501                 @Override
502                 public boolean handle(int res, int flags, long udata, ByteBuffer extraCqeData) {
503                     if (udata == EVENTFD_TOKEN) {
504                         eventFdDrained = true;
505                     }
506                     return IoUringIoHandler.this.handle(res, flags, udata, extraCqeData);
507                 }
508             }
509             final DrainFdEventCallback handler = new DrainFdEventCallback();
510             completionQueue.process(handler);
511             while (!handler.eventFdDrained) {
512                 submissionQueue.submitAndGet();
513                 processCompletionsAndHandleOverflow(submissionQueue, completionQueue, handler);
514             }
515         }
516         // We've consumed any pending eventfd read and `eventfdAsyncNotify` should never
517         // transition back to false, thus we should never have any more events written.
518         // So, if we have a read event pending, we can cancel it.
519         if (eventfdReadSubmitted != 0) {
520             submissionQueue.addCancel(eventfdReadSubmitted, EVENTFD_TOKEN);
521             eventfdReadSubmitted = 0;
522             submissionQueue.submit();
523         }
524     }
525 
526     private void completeRingClose() {
527         if (closeCompleted) {
528             // already done.
529             return;
530         }
531         closeCompleted = true;
532         ringBuffer.close();
533         closeWakeupGate();
534         try {
535             eventfd.close();
536         } catch (IOException e) {
537             logger.warn("Failed to close eventfd", e);
538         }
539         eventfdReadBufCleanable.clean();
540         timeoutMemoryCleanable.clean();
541         iovArray.release();
542         msgHdrMemoryArray.release();
543     }
544 
545     @Override
546     public IoRegistration register(IoHandle handle) throws Exception {
547         IoUringIoHandle ioHandle = cast(handle);
548         if (shuttingDown) {
549             throw new IllegalStateException("IoUringIoHandler is shutting down");
550         }
551         int startId = nextRegistrationId;
552         DefaultIoUringIoRegistration registration = new DefaultIoUringIoRegistration(executor, ioHandle);
553         for (;;) {
554             int id = nextRegistrationId();
555             DefaultIoUringIoRegistration old = registrations.put(id, registration);
556             if (old != null) {
557                 assert old.handle != registration.handle;
558                 registrations.put(id, old);
559                 if (nextRegistrationId == startId) {
560                     throw new IllegalStateException("registration id space exhausted");
561                 }
562             } else {
563                 registration.setId(id);
564                 ioHandle.registered();
565                 break;
566             }
567         }
568 
569         return registration;
570     }
571 
572     private int nextRegistrationId() {
573         //registrationId must stay positive because id > 0
574         //it is used to distinguish normal fast-path completions from non-registration tokens.
575         int id = nextRegistrationId;
576         nextRegistrationId = id == Integer.MAX_VALUE ? 1 : id + 1;
577         return id;
578     }
579 
580     private final class DefaultIoUringIoRegistration implements IoRegistration {
581         private final AtomicBoolean canceled = new AtomicBoolean();
582         private final ThreadAwareExecutor executor;
583         private final IoUringIoEvent event = new IoUringIoEvent(0, 0, (byte) 0, 0L);
584         final IoUringIoHandle handle;
585 
586         private boolean removeLater;
587         private int outstandingCompletions;
588         private int id;
589 
590         DefaultIoUringIoRegistration(ThreadAwareExecutor executor, IoUringIoHandle handle) {
591             this.executor = executor;
592             this.handle = handle;
593         }
594 
595         void setId(int id) {
596             this.id = id;
597         }
598 
599         @Override
600         public long submit(IoOps ops) {
601             IoUringIoOps ioOps = (IoUringIoOps) ops;
602             if (!isValid()) {
603                 return INVALID_ID;
604             }
605             if ((ioOps.flags() & Native.IOSQE_CQE_SKIP_SUCCESS) != 0) {
606                 // Because we expect at least 1 completion per submission we can't support IOSQE_CQE_SKIP_SUCCESS
607                 // as it will only produce a completion on failure.
608                 throw new IllegalArgumentException("IOSQE_CQE_SKIP_SUCCESS not supported");
609             }
610             long userData = ioOps.userData();
611             // Use the fast path when the full submission can still be encoded into packed UserData.
612             if (canUseFastPath(userData)) {
613                 long packedSeq = UserData.encode(id, ioOps.opcode(), (short) userData);
614                 if (executor.isExecutorThread(Thread.currentThread())) {
615                     submitFastPath0(ioOps, packedSeq);
616                 } else {
617                     executor.execute(() -> submitFastPath0(ioOps, packedSeq));
618                 }
619                 return packedSeq;
620             }
621             long token = pendingOps.nextToken();
622             if (executor.isExecutorThread(Thread.currentThread())) {
623                 submitSlowPath0(ioOps, token, userData);
624             } else {
625                 executor.execute(() -> submitSlowPath0(ioOps, token, userData));
626             }
627             return token;
628         }
629 
630         private void submitFastPath0(IoUringIoOps ioOps, long seq) {
631             ringBuffer.ioUringSubmissionQueue().enqueueSqe(ioOps.opcode(), ioOps.flags(), ioOps.ioPrio(),
632                     ioOps.fd(), ioOps.union1(), ioOps.union2(), ioOps.len(), ioOps.union3(), seq,
633                     ioOps.union4(), ioOps.personality(), ioOps.union5(), ioOps.union6()
634             );
635             outstandingCompletions++;
636         }
637 
638         private void submitSlowPath0(IoUringIoOps ioOps, long token, long userData) {
639             pendingOps.registerNormal(token, id, ioOps.opcode(), userData);
640             ringBuffer.ioUringSubmissionQueue().enqueueSqe(ioOps.opcode(), ioOps.flags(), ioOps.ioPrio(),
641                     ioOps.fd(), ioOps.union1(), ioOps.union2(), ioOps.len(), ioOps.union3(), token,
642                     ioOps.union4(), ioOps.personality(), ioOps.union5(), ioOps.union6()
643             );
644             outstandingCompletions++;
645         }
646 
647         private boolean canUseFastPath(long userData) {
648             return ((short) userData) == userData;
649         }
650 
651         private int fd() {
652             if (handle instanceof AbstractIoUringChannel) {
653                 return ((AbstractIoUringChannel) handle).fd().intValue();
654             }
655             return -1;
656         }
657 
658         @SuppressWarnings("unchecked")
659         @Override
660         public <T> T attachment() {
661             return (T) IoUringIoHandler.this;
662         }
663 
664         @Override
665         public boolean isValid() {
666             return !canceled.get();
667         }
668 
669         @Override
670         public boolean cancel() {
671             if (!canceled.compareAndSet(false, true)) {
672                 // Already cancelled.
673                 return false;
674             }
675             if (executor.isExecutorThread(Thread.currentThread())) {
676                 tryRemove();
677             } else {
678                 executor.execute(this::tryRemove);
679             }
680             return true;
681         }
682 
683         private void tryRemove() {
684             if (outstandingCompletions > 0) {
685                 // We have some completions outstanding, we will remove the id <-> registration mapping
686                 // once these are done.
687                 removeLater = true;
688                 return;
689             }
690             remove();
691         }
692 
693         private void remove() {
694             DefaultIoUringIoRegistration old = registrations.remove(id);
695             assert old == this;
696             handle.unregistered();
697         }
698 
699         void close() {
700             // Closing the handle will also cancel the registration.
701             // It's important that we not manually cancel as close() might need to submit some work to the ring.
702             assert executor.isExecutorThread(Thread.currentThread());
703             try {
704                 handle.close();
705             } catch (Exception e) {
706                 logger.debug("Exception during closing " + handle, e);
707             }
708         }
709 
710         void handle(int res, int flags, byte op, long userData, ByteBuffer extraCqeData) {
711             event.update(res, flags, op, userData, extraCqeData);
712             handle.handle(this, event);
713             // Only decrement outstandingCompletions if IORING_CQE_F_MORE is not set as otherwise we know that we will
714             // receive more completions for the intial request.
715             if ((flags & Native.IORING_CQE_F_MORE) == 0 && --outstandingCompletions == 0 && removeLater) {
716                 // No more outstanding completions, remove the registration now.
717                 removeLater = false;
718                 remove();
719             }
720         }
721     }
722 
723     private static IoUringIoHandle cast(IoHandle handle) {
724         if (handle instanceof IoUringIoHandle) {
725             return (IoUringIoHandle) handle;
726         }
727         throw new IllegalArgumentException("IoHandle of type " + StringUtil.simpleClassName(handle) + " not supported");
728     }
729 
730     @Override
731     public void wakeup() {
732         if (!executor.isExecutorThread(Thread.currentThread()) &&
733             !eventfdAsyncNotify.getAndSet(true)) {
734             // Reserve a writer slot so the event-loop thread cannot close the eventfd while we are in the
735             // middle of eventFdWrite(). If the gate has already been closed (loop is being destroyed),
736             // simply drop the wakeup: there is no loop left to wake up, and writing to a closed (and
737             // possibly recycled) fd would either throw EBADF or, worse, hit an unrelated fd.
738             int s;
739             do {
740                 s = wakeupWriters.get();
741                 if ((s & WAKEUP_CLOSED) != 0) {
742                     return;
743                 }
744             } while (!wakeupWriters.compareAndSet(s, s + 1));
745             try {
746                 // write to the eventfd which will then trigger an eventfd read completion.
747                 Native.eventFdWrite(eventfd.intValue(), 1L);
748             } finally {
749                 wakeupWriters.decrementAndGet();
750             }
751         }
752     }
753 
754     private void closeWakeupGate() {
755         int s;
756         do {
757             s = wakeupWriters.get();
758         } while (!wakeupWriters.compareAndSet(s, s | WAKEUP_CLOSED));
759         // Wait for any thread still inside eventFdWrite() to leave. eventFdWrite is a single write(2)
760         // syscall on an eventfd, so this spin is bounded to a few microseconds in practice.
761         while ((wakeupWriters.get() & ~WAKEUP_CLOSED) != 0) {
762             Thread.onSpinWait();
763         }
764     }
765 
766     @Override
767     public boolean isCompatible(Class<? extends IoHandle> handleType) {
768         return IoUringIoHandle.class.isAssignableFrom(handleType);
769     }
770 
771     IovArray iovArray() {
772         if (iovArray.isFull()) {
773             // Submit so we can reuse the iovArray.
774             submitAndClearNow(ringBuffer.ioUringSubmissionQueue());
775             assert iovArray.count() == 0;
776         }
777         return iovArray;
778     }
779 
780     /**
781      * Returns the {@link IovArrayReferenceCollector} paired with {@link #iovArray()}. A plain getter: callers are
782      * expected to have already called {@link #iovArray()} to make room, so this must not itself submit-and-clear.
783      */
784     IovArrayReferenceCollector iovArrayReferenceCollector() {
785         return iovArrayReferenceCollector;
786     }
787 
788     MsgHdrMemoryArray msgHdrMemoryArray() {
789         if (msgHdrMemoryArray.isFull()) {
790             // Submit so we can reuse the msgHdrArray.
791             submitAndClearNow(ringBuffer.ioUringSubmissionQueue());
792         }
793         return msgHdrMemoryArray;
794     }
795 
796     /**
797      * {@code byte[]} that can be used as temporary storage to encode the ipv4 address
798      */
799     byte[] inet4AddressArray() {
800         return inet4AddressArray;
801     }
802 
803     /**
804      * {@code byte[]} that can be used as temporary storage to encode the ipv6 address
805      */
806     byte[] inet6AddressArray() {
807         return inet6AddressArray;
808     }
809 
810     /**
811      * Create a new {@link IoHandlerFactory} that can be used to create {@link IoUringIoHandler}s.
812      *
813      * @return factory
814      */
815     public static IoHandlerFactory newFactory() {
816         return newFactory(new IoUringIoHandlerConfig());
817     }
818 
819     /**
820      * Create a new {@link IoHandlerFactory} that can be used to create {@link IoUringIoHandler}s.
821      * Each {@link IoUringIoHandler} will use a ring of size {@code ringSize}.
822      *
823      * @param  ringSize     the size of the ring.
824      * @return              factory
825      */
826     public static IoHandlerFactory newFactory(int ringSize) {
827         IoUringIoHandlerConfig configuration = new IoUringIoHandlerConfig();
828         configuration.setRingSize(ringSize);
829         return eventLoop -> new IoUringIoHandler(eventLoop, configuration);
830     }
831 
832     /**
833      * Create a new {@link IoHandlerFactory} that can be used to create {@link IoUringIoHandler}s.
834      * Each {@link IoUringIoHandler} will use same option
835      * @param config the io_uring configuration
836      * @return factory
837      */
838     public static IoHandlerFactory newFactory(IoUringIoHandlerConfig config) {
839         IoUring.ensureAvailability();
840         final IoUringIoHandlerConfig copy = ObjectUtil.checkNotNull(config, "config").verifyAndClone();
841         return new IoHandlerFactory() {
842             @Override
843             public IoHandler newHandler(ThreadAwareExecutor eventLoop) {
844                 return new IoUringIoHandler(eventLoop, copy);
845             }
846 
847             @Override
848             public boolean isChangingThreadSupported() {
849                 return !copy.singleIssuer();
850             }
851         };
852     }
853 }