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.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 }