1 /* 2 * Copyright 2025 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.util.internal.ObjectUtil; 19 20 import java.util.ArrayList; 21 import java.util.HashSet; 22 import java.util.List; 23 import java.util.Objects; 24 import java.util.Set; 25 26 /** 27 * Configuration class for an {@link IoUringIoHandler}, 28 * managing the settings for a {@link RingBuffer} and its io_uring file descriptor. 29 * 30 * <h3>Option Map</h3> 31 * These options are used exclusively during the initialization of the {@link IoUringIoHandler} 32 * to configure the associated io_uring instance. 33 * 34 * <p> 35 * The {@link IoUringIoHandlerConfig} class provides the following configurable options: 36 * </p> 37 * 38 * <table border="1" cellspacing="0" cellpadding="6"> 39 * <caption>Available Configuration Options</caption> 40 * <thead> 41 * <tr> 42 * <th>Setter Method</th> 43 * <th>Description</th> 44 * </tr> 45 * </thead> 46 * <tbody> 47 * <tr> 48 * <td>{@link IoUringIoHandlerConfig#setRingSize}</td> 49 * <td>Sets the size of the submission queue for the io_uring instance. 50 * <br> 51 * If you want to submit a large number of io_uring requests at once, 52 * it is recommended to properly configure this option. 53 * The default value is 4096, which is sufficient for most scenarios. 54 * </td> 55 * </tr> 56 * <tr> 57 * <td>{@link IoUringIoHandlerConfig#setMaxBoundedWorker}</td> 58 * <td>Defines the maximum number of bounded io_uring worker threads. 59 * <br> 60 * If you extend io_uring-related file operations based on Netty, 61 * it is recommended to properly configure this option. 62 * For more details, refer to the 63 * <a href="https://man7.org/linux/man-pages/man3/io_uring_register_iowq_max_workers.3.html> 64 * manual. 65 * </a> 66 * </td> 67 * </tr> 68 * <tr> 69 * <td>{@link IoUringIoHandlerConfig#setMaxUnboundedWorker}</td> 70 * <td>Defines the maximum number of unbounded io_uring worker threads. 71 * <br> 72 * If you use FileRegion to perform `sendfile` operations in io_uring, 73 * it is recommended to properly configure this option as otherwise you might 74 * end up with an <a href="https://github.com/netty/netty/issues/15125>unexpected number of kernel threads</a>. 75 * </td> 76 * </tr> 77 * <tr> 78 * <td>{@link IoUringIoHandlerConfig#setCqSize}</td> 79 * <td>Sets the size of the completionQueue queue for the io_uring instance. 80 * <br> 81 * If your current kernel supports some multishot variants 82 * (such as IORING_RECV_MULTISHOT, IORING_ACCEPT_MULTISHOT) or IORING_RECVSEND_BUNDLE, 83 * and you want to handle more CQEs in a single syscall 84 * it is recommended to properly configure this option. 85 * The default value is twice the ring size, which is sufficient for most scenarios. 86 * </td> 87 * </tr> 88 * <tr> 89 * <td>{@link IoUringIoHandlerConfig#setBufferRingConfig}</td> 90 * <td> 91 * Adds a buffer ring configuration to the list of buffer ring configurations. 92 * It will be used to register the buffer ring for the io_uring instance. 93 * </td> 94 * </tr> 95 * <tr> 96 * <td>{@link IoUringIoHandlerConfig#setSingleIssuer}</td> 97 * <td> 98 * Enable or disable the use of {@code IORING_SETUP_SINGLE_ISSUER}. 99 * </td> 100 * </tr> 101 * </tbody> 102 * </table> 103 */ 104 105 public final class IoUringIoHandlerConfig { 106 107 private int ringSize = IoUring.DEFAULT_RING_SIZE; 108 private int cqSize = IoUring.DEFAULT_CQ_SIZE; 109 private int maxBoundedWorker; 110 private int maxUnboundedWorker; 111 private Set<IoUringBufferRingConfig> bufferRingConfigs; 112 private boolean singleIssuer = true; 113 114 /** 115 * Return the ring size of the io_uring instance. 116 * @return the ring size of the io_uring instance. 117 */ 118 public int getRingSize() { 119 return ringSize; 120 } 121 122 /** 123 * Return the size of the io_uring cqe. 124 * @return the cq size of the io_uring. 125 */ 126 public int getCqSize() { 127 return cqSize; 128 } 129 130 /** 131 * Return the maximum number of bounded iowq worker threads. 132 * @return the maximum number of bounded iowq worker threads. 133 */ 134 public int getMaxBoundedWorker() { 135 return maxBoundedWorker; 136 } 137 138 /** 139 * Return the maximum number of unbounded iowq worker threads. 140 * @return the maximum number of unbounded iowq worker threads. 141 */ 142 public int getMaxUnboundedWorker() { 143 return maxUnboundedWorker; 144 } 145 146 /** 147 * Set the ring size of the io_uring instance. 148 * @param ringSize the ring size of the io_uring instance. 149 * @return reference to this, so the API can be used fluently 150 */ 151 public IoUringIoHandlerConfig setRingSize(int ringSize) { 152 this.ringSize = ObjectUtil.checkPositive(ringSize, "ringSize"); 153 return this; 154 } 155 156 /** 157 * Set the size of the io_uring cqe. 158 * @param cqSize the size of the io_uring cqe. 159 * @throws IllegalArgumentException if cqSize is less than ringSize, or not a power of 2 160 * @return reference to this, so the API can be used fluently 161 */ 162 public IoUringIoHandlerConfig setCqSize(int cqSize) { 163 ObjectUtil.checkPositive(cqSize, "cqSize"); 164 this.cqSize = checkCqSize(cqSize); 165 return this; 166 } 167 168 int checkCqSize(int cqSize) { 169 if (cqSize < ringSize) { 170 throw new IllegalArgumentException("cqSize must be greater than or equal to ringSize"); 171 } 172 173 boolean isPowerOfTwo = Integer.bitCount(cqSize) == 1; 174 if (!isPowerOfTwo) { 175 throw new IllegalArgumentException("cqSize: " + cqSize + " (expected: power of 2)"); 176 } 177 return cqSize; 178 } 179 180 /** 181 * Set the maximum number of bounded iowq worker threads. 182 * @param maxBoundedWorker the maximum number of bounded iowq worker threads, 183 * or 0 for the Linux kernel default 184 * @return reference to this, so the API can be used fluently 185 */ 186 public IoUringIoHandlerConfig setMaxBoundedWorker(int maxBoundedWorker) { 187 this.maxBoundedWorker = ObjectUtil.checkPositiveOrZero(maxBoundedWorker, "maxBoundedWorker"); 188 return this; 189 } 190 191 /** 192 * Set the maximum number of unbounded iowq worker threads. 193 * @param maxUnboundedWorker the maximum number of unbounded iowq worker threads, 194 * of 0 for the Linux kernel default 195 * @return reference to this, so the API can be used fluently 196 */ 197 public IoUringIoHandlerConfig setMaxUnboundedWorker(int maxUnboundedWorker) { 198 this.maxUnboundedWorker = ObjectUtil.checkPositiveOrZero(maxUnboundedWorker, "maxUnboundedWorker"); 199 return this; 200 } 201 202 /** 203 * Add a buffer ring configuration to the list of buffer ring configurations. 204 * Each {@link IoUringBufferRingConfig} must have a different {@link IoUringBufferRingConfig#bufferGroupId()}. 205 * 206 * @param ringConfig the buffer ring configuration to append. 207 * @return reference to this, so the API can be used fluently 208 */ 209 public IoUringIoHandlerConfig setBufferRingConfig(IoUringBufferRingConfig... ringConfig) { 210 Set<IoUringBufferRingConfig> configSet = new HashSet<>(ringConfig.length); 211 for (IoUringBufferRingConfig bufferRingConfig : ringConfig) { 212 if (!configSet.add(bufferRingConfig)) { 213 throw new IllegalArgumentException("Duplicated buffer group id: " + bufferRingConfig.bufferGroupId()); 214 } 215 } 216 bufferRingConfigs = configSet; 217 return this; 218 } 219 220 /** 221 * Set if {@code IORING_SETUP_SINGLE_ISSUER} should be used when setup the ring. This is {@code true} by default 222 * for performance reasons but also means that the {@link Thread} that is used to drive the 223 * {@link io.netty.channel.IoHandler} can never change. If you want the flexibility to change the {@link Thread}, 224 * to for example make use of the {@link io.netty.util.concurrent.AutoScalingEventExecutorChooserFactory} it's 225 * possible to not use {@code IORING_SETUP_SINGLE_ISSUER}. This will trade scalibility / flexibility 226 * and performance. 227 * 228 * @param singleIssuer {@code true} if {@code IORING_SETUP_SINGLE_ISSUER} should be used, {@code false} otherwise 229 * @return reference to this, so the API can be used fluently 230 */ 231 public IoUringIoHandlerConfig setSingleIssuer(boolean singleIssuer) { 232 this.singleIssuer = singleIssuer; 233 return this; 234 } 235 236 /** 237 * Get the list of buffer ring configurations. 238 * @return the copy of buffer ring configurations. 239 */ 240 public List<IoUringBufferRingConfig> getBufferRingConfigs() { 241 return new ArrayList<>(bufferRingConfigs); 242 } 243 244 boolean needRegisterIowqMaxWorker() { 245 return maxBoundedWorker > 0 || maxUnboundedWorker > 0; 246 } 247 248 boolean needSetupCqeSize() { 249 return cqSize != IoUring.DISABLE_SETUP_CQ_SIZE; 250 } 251 252 Set<IoUringBufferRingConfig> getInternBufferRingConfigs() { 253 return bufferRingConfigs; 254 } 255 256 boolean singleIssuer() { 257 return singleIssuer; 258 } 259 }