View Javadoc
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 }