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