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 }