View Javadoc
1   /*
2    * Copyright 2026 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.buffer.ByteBuf;
19  import io.netty.channel.ChannelOutboundBuffer;
20  import io.netty.channel.unix.IovArray;
21  import io.netty.util.ReferenceCounted;
22  
23  import java.util.Arrays;
24  
25  /**
26   * Fills the {@link IoUringIoHandler}'s {@link IovArray} from flushed outbound messages and records the
27   * {@link ByteBuf} behind each entry it added, so the caller can copy those references into a {@link WriteOperation}
28   * slot once the SQE is built.
29   *
30   * <p>One instance per {@link IoUringIoHandler}, matching the {@link IovArray} it wraps: the handler hands out the
31   * same {@link IovArray} instance to every channel it services, so a per-channel collector would be scoped smaller
32   * than the array it fills. This collector is only ever valid between a {@link #reset()} and the {@link WriteOperation}
33   * record call that copies its references out -- the caller then resets it from a {@code finally} that also covers
34   * the submit, so every exit from the write path, including the ones that throw, leaves it empty. Without that reset
35   * this instance, being permanently owned by the event loop rather than any one channel, would keep the previous
36   * write's buffers reachable for as long as this event loop went without servicing another write.
37   */
38  final class IovArrayReferenceCollector implements ChannelOutboundBuffer.MessageProcessor {
39      private final IovArray iovArray;
40      private ReferenceCounted[] references = new ReferenceCounted[4];
41      private int count;
42  
43      IovArrayReferenceCollector(IovArray iovArray) {
44          this.iovArray = iovArray;
45      }
46  
47      /**
48       * Drops the previous references, keeping the array for reuse. Nulls out the dropped entries too: otherwise a
49       * smaller write reusing the collector after a larger one would leave stale {@code ByteBuf} references reachable
50       * through the backing array until the next reset, which risks promoting them into an old generation.
51       */
52      void reset() {
53          Arrays.fill(references, 0, count, null);
54          count = 0;
55      }
56  
57      @Override
58      public boolean processMessage(Object msg) throws Exception {
59          int previousCount = iovArray.count();
60          boolean processed = iovArray.processMessage(msg);
61          recordIfAdded(msg, previousCount);
62          return processed;
63      }
64  
65      /**
66       * Records the buffer behind {@code msg} once {@link IovArray} actually gained an entry for it. Split out of
67       * {@link #processMessage(Object)} so that method stays under HotSpot's default inline size threshold (35
68       * bytes), which it exceeded with the {@code if} check below inlined.
69       */
70      private void recordIfAdded(Object msg, int previousCount) {
71          // A 0-byte readable buffer makes IovArray.add(...) return true without adding an entry (see
72          // IovArray.add(ByteBuf, int, int)), so it never needs a slot here either -- comparing count before and
73          // after is what tells such a buffer apart from one that was actually added.
74          if (iovArray.count() != previousCount) {
75              add((ByteBuf) msg);
76          }
77      }
78  
79      ReferenceCounted[] referencesArray() {
80          return references;
81      }
82  
83      int referencesCount() {
84          return count;
85      }
86  
87      private void add(ByteBuf buffer) {
88          if (count == references.length) {
89              grow();
90          }
91          references[count++] = buffer;
92      }
93  
94      /**
95       * Doubles the backing array once it fills up. Split out of {@link #add(ByteBuf)} so that method stays under
96       * HotSpot's default inline size threshold (35 bytes); growing is genuinely the rare branch here, since
97       * {@link #reset()} clears the array in place for reuse instead of shrinking it back down.
98       */
99      private void grow() {
100         references = Arrays.copyOf(references, count << 1);
101     }
102 }