View Javadoc
1   /*
2    * Copyright 2014 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.netty5.channel.unix;
17  
18  import io.netty5.buffer.api.Buffer;
19  import io.netty5.buffer.api.ReadableComponent;
20  import io.netty5.buffer.api.ReadableComponentProcessor;
21  import io.netty5.buffer.api.WritableComponent;
22  import io.netty5.buffer.api.WritableComponentProcessor;
23  import io.netty5.channel.ChannelOutboundBuffer.MessageProcessor;
24  import io.netty5.util.internal.PlatformDependent;
25  
26  import java.nio.ByteBuffer;
27  import java.nio.ByteOrder;
28  
29  import static io.netty5.channel.unix.Buffer.addressSize;
30  import static io.netty5.channel.unix.Buffer.allocateDirectWithNativeOrder;
31  import static io.netty5.channel.unix.Buffer.free;
32  import static io.netty5.channel.unix.Buffer.nativeAddressOf;
33  import static io.netty5.channel.unix.Limits.IOV_MAX;
34  import static io.netty5.channel.unix.Limits.SSIZE_MAX;
35  import static io.netty5.util.internal.ObjectUtil.checkPositive;
36  import static java.lang.Math.min;
37  
38  /**
39   * Represent an array of struct array and so can be passed directly over via JNI without the need to do any more
40   * array copies.
41   *
42   * The buffers are written out directly into direct memory to match the struct iov. See also {@code man writev}.
43   *
44   * <pre>
45   * struct iovec {
46   *   void  *iov_base;
47   *   size_t iov_len;
48   * };
49   * </pre>
50   *
51   * See also
52   * <a href="https://rkennke.wordpress.com/2007/07/30/efficient-jni-programming-iv-wrapping-native-data-objects/"
53   * >Efficient JNI programming IV: Wrapping native data objects</a>.
54   */
55  public final class IovArray implements MessageProcessor, ReadableComponentProcessor<RuntimeException>,
56                                         WritableComponentProcessor<RuntimeException> {
57  
58      /** The size of an address which should be 8 for 64 bits and 4 for 32 bits. */
59      private static final int ADDRESS_SIZE = addressSize();
60  
61      /**
62       * The size of an {@code iovec} struct in bytes. This is calculated as we have 2 entries each of the size of the
63       * address.
64       */
65      public static final int IOV_SIZE = 2 * ADDRESS_SIZE;
66  
67      /**
68       * The needed memory to hold up to {@code IOV_MAX} iov entries, where {@code IOV_MAX} signified
69       * the maximum number of {@code iovec} structs that can be passed to {@code writev(...)}.
70       */
71      private static final int MAX_CAPACITY = IOV_MAX * IOV_SIZE;
72  
73      private final long memoryAddress;
74      private final ByteBuffer memory;
75      private int count;
76      private long size;
77      private long maxBytes = SSIZE_MAX;
78  
79      public IovArray() {
80          this(allocateDirectWithNativeOrder(MAX_CAPACITY));
81      }
82  
83      public IovArray(ByteBuffer memory) {
84          assert memory.position() == 0;
85          if (!memory.isDirect()) {
86              memory = ByteBuffer.allocateDirect(memory.capacity());
87          }
88          if (memory.order() != ByteOrder.nativeOrder()) {
89              memory = memory.order(ByteOrder.nativeOrder());
90          }
91          this.memory = memory;
92          memoryAddress = nativeAddressOf(memory);
93      }
94  
95      public void clear() {
96          count = 0;
97          size = 0;
98      }
99  
100     private boolean add(long memoryAddress, long addr, int len) {
101         assert addr != 0;
102 
103         // If there is at least 1 entry then we enforce the maximum bytes. We want to accept at least one entry so we
104         // will attempt to write some data and make progress.
105         if (maxBytes - len < size && count > 0 ||
106             // Check if we have enough space left
107                 memory.capacity() < (count + 1) * IOV_SIZE) {
108             // If the size + len will overflow SSIZE_MAX we stop populate the IovArray. This is done as linux
109             //  not allow to write more bytes then SSIZE_MAX with one writev(...) call and so will
110             // return 'EINVAL', which will raise an IOException.
111             //
112             // See also:
113             // - https://linux.die.net//man/2/writev
114             return false;
115         }
116         final int baseOffset = idx(count);
117         final int lengthOffset = baseOffset + ADDRESS_SIZE;
118 
119         size += len;
120         ++count;
121 
122         if (ADDRESS_SIZE == 8) {
123             // 64bit
124             if (PlatformDependent.hasUnsafe()) {
125                 PlatformDependent.putLong(baseOffset + memoryAddress, addr);
126                 PlatformDependent.putLong(lengthOffset + memoryAddress, len);
127             } else {
128                 memory.putLong(baseOffset, addr);
129                 memory.putLong(lengthOffset, len);
130             }
131         } else {
132             assert ADDRESS_SIZE == 4;
133             if (PlatformDependent.hasUnsafe()) {
134                 PlatformDependent.putInt(baseOffset + memoryAddress, (int) addr);
135                 PlatformDependent.putInt(lengthOffset + memoryAddress, len);
136             } else {
137                 memory.putInt(baseOffset, (int) addr);
138                 memory.putInt(lengthOffset, len);
139             }
140         }
141         return true;
142     }
143 
144     /**
145      * Returns the number if iov entries.
146      */
147     public int count() {
148         return count;
149     }
150 
151     /**
152      * Returns the size in bytes
153      */
154     public long size() {
155         return size;
156     }
157 
158     /**
159      * Set the maximum amount of bytes that can be added to this {@link IovArray} via {@link #add(long, long, int)} or
160      * {@link #processMessage(Object)}.
161      * <p>
162      * This will not impact the existing state of the {@link IovArray}, and only applies to subsequent calls to
163      * {@link #add(long, long, int)} or {@link #processMessage(Object)}.
164      * <p>
165      * In order to ensure some progress is made at least one {@link Buffer} will be accepted even if it's size exceeds
166      * this value.
167      * @param maxBytes the maximum amount of bytes that can be added to this {@link IovArray}.
168      */
169     public void maxBytes(long maxBytes) {
170         this.maxBytes = min(SSIZE_MAX, checkPositive(maxBytes, "maxBytes"));
171     }
172 
173     /**
174      * Get the maximum amount of bytes that can be added to this {@link IovArray}.
175      * @return the maximum amount of bytes that can be added to this {@link IovArray}.
176      */
177     public long maxBytes() {
178         return maxBytes;
179     }
180 
181     /**
182      * Returns the {@code memoryAddress} for the given {@code index}.
183      */
184     public long memoryAddress(int index) {
185         return memoryAddress + idx(index);
186     }
187 
188     /**
189      * Release the {@link IovArray}. Once release further using of it may crash the JVM!
190      */
191     public void release() {
192         free(memory);
193     }
194 
195     @Override
196     public boolean processMessage(Object msg) {
197         if (msg instanceof io.netty5.buffer.api.Buffer) {
198             var buffer = (io.netty5.buffer.api.Buffer) msg;
199             if (buffer.readableBytes() == 0) {
200                 return true;
201             }
202             return buffer.forEachReadable(0, this) >= 0;
203         }
204         return false;
205     }
206 
207     private static int idx(int index) {
208         return IOV_SIZE * index;
209     }
210 
211     @Override
212     public boolean process(int index, ReadableComponent component) {
213         return process(component, component.readableBytes());
214     }
215 
216     public boolean process(ReadableComponent component, int byteCount) {
217         if (count == IOV_MAX) {
218             // No more room!
219             return false;
220         }
221         long nativeAddress = component.readableNativeAddress();
222         assert nativeAddress != 0;
223         return add(memoryAddress, nativeAddress, byteCount);
224     }
225 
226     @Override
227     public boolean process(int index, WritableComponent component) {
228         return process(component, component.writableBytes());
229     }
230 
231     public boolean process(WritableComponent component, int byteCount) {
232         if (count == IOV_MAX) {
233             // No more room!
234             return false;
235         }
236         long nativeAddress = component.writableNativeAddress();
237         assert nativeAddress != 0;
238         return add(memoryAddress, nativeAddress, byteCount);
239     }
240 }