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.netty.channel.unix;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.buffer.Unpooled;
20  import io.netty.channel.ChannelOutboundBuffer.MessageProcessor;
21  import io.netty.util.internal.CleanableDirectBuffer;
22  import io.netty.util.internal.PlatformDependent;
23  
24  import java.nio.ByteBuffer;
25  import java.nio.ByteOrder;
26  
27  import static io.netty.channel.unix.Limits.IOV_MAX;
28  import static io.netty.channel.unix.Limits.SSIZE_MAX;
29  import static io.netty.util.internal.ObjectUtil.checkPositive;
30  import static java.lang.Math.min;
31  
32  /**
33   * Represent an array of struct array and so can be passed directly over via JNI without the need to do any more
34   * array copies.
35   *
36   * The buffers are written out directly into direct memory to match the struct iov. See also {@code man writev}.
37   *
38   * <pre>
39   * struct iovec {
40   *   void  *iov_base;
41   *   size_t iov_len;
42   * };
43   * </pre>
44   *
45   * See also
46   * <a href="https://rkennke.wordpress.com/2007/07/30/efficient-jni-programming-iv-wrapping-native-data-objects/"
47   * >Efficient JNI programming IV: Wrapping native data objects</a>.
48   */
49  public final class IovArray implements MessageProcessor {
50  
51      /** The size of an address which should be 8 for 64 bits and 4 for 32 bits. */
52      private static final int ADDRESS_SIZE = Buffer.addressSize();
53  
54      /**
55       * The size of an {@code iovec} struct in bytes. This is calculated as we have 2 entries each of the size of the
56       * address.
57       */
58      public static final int IOV_SIZE = 2 * ADDRESS_SIZE;
59  
60      /**
61       * The needed memory to hold up to {@code IOV_MAX} iov entries, where {@code IOV_MAX} signified
62       * the maximum number of {@code iovec} structs that can be passed to {@code writev(...)}.
63       */
64      private static final int MAX_CAPACITY = IOV_MAX * IOV_SIZE;
65  
66      private final long memoryAddress;
67      private final ByteBuf memory;
68      private final CleanableDirectBuffer cleanable;
69      private int count;
70      private long size;
71      private long maxBytes = SSIZE_MAX;
72      private int maxCount;
73  
74      /**
75       * @deprecated Use {@link #IovArray(int)} instead.
76       */
77      @Deprecated
78      public IovArray() {
79          this(IOV_MAX);
80      }
81  
82      /**
83       * Allocate an IovArray with enough room for the given number of <strong>entries</strong> (not bytes).
84       * @param numEntries The desired number of entries in the IovArray.
85       */
86      @SuppressWarnings("deprecation")
87      public IovArray(int numEntries) {
88          int sizeBytes = Math.multiplyExact(checkPositive(numEntries, "numEntries"), IOV_SIZE);
89          cleanable = Buffer.allocateDirectBufferWithNativeOrder(sizeBytes);
90          ByteBuf bbuf = Unpooled.wrappedBuffer(cleanable.buffer()).setIndex(0, 0);
91          memory = PlatformDependent.hasUnsafe() ? bbuf : bbuf.order(
92                  PlatformDependent.BIG_ENDIAN_NATIVE_ORDER ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
93          if (memory.hasMemoryAddress()) {
94              memoryAddress = memory.memoryAddress();
95          } else {
96              // Fallback to using JNI as we were not be able to access the address otherwise.
97  
98              // Use internalNioBuffer to reduce object creation.
99              // It is important to add the position as the returned ByteBuffer might be shared by multiple ByteBuf
100             // instances and so has an address that starts before the start of the ByteBuf itself.
101             ByteBuffer byteBuffer = memory.internalNioBuffer(0, memory.capacity());
102             memoryAddress = Buffer.memoryAddress(byteBuffer) + byteBuffer.position();
103         }
104         maxCount = IOV_MAX;
105     }
106 
107     /**
108      * @param memory The underlying memory.
109      * @deprecated Use {@link #IovArray(int)} instead.
110      */
111     @Deprecated
112     public IovArray(ByteBuf memory) {
113         assert memory.writerIndex() == 0;
114         assert memory.readerIndex() == 0;
115         this.memory = PlatformDependent.hasUnsafe() ? memory : memory.order(
116                 PlatformDependent.BIG_ENDIAN_NATIVE_ORDER ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
117         if (memory.hasMemoryAddress()) {
118             memoryAddress = memory.memoryAddress();
119         } else {
120             // Fallback to using JNI as we were not be able to access the address otherwise.
121 
122             // Use internalNioBuffer to reduce object creation.
123             // It is important to add the position as the returned ByteBuffer might be shared by multiple ByteBuf
124             // instances and so has an address that starts before the start of the ByteBuf itself.
125             ByteBuffer byteBuffer = memory.internalNioBuffer(0, memory.capacity());
126             memoryAddress = Buffer.memoryAddress(byteBuffer) + byteBuffer.position();
127         }
128         cleanable = null;
129         maxCount = IOV_MAX;
130     }
131 
132     public void clear() {
133         count = 0;
134         size = 0;
135         maxCount = IOV_MAX;
136     }
137 
138     /**
139      * @deprecated Use {@link #add(ByteBuf, int, int)}
140      */
141     @Deprecated
142     public boolean add(ByteBuf buf) {
143         return add(buf, buf.readerIndex(), buf.readableBytes());
144     }
145 
146     public boolean add(ByteBuf buf, int offset, int len) {
147         if (count == maxCount) {
148             // No more room!
149             return false;
150         }
151         if (buf.nioBufferCount() == 1) {
152             if (len == 0) {
153                 return true;
154             }
155             if (buf.hasMemoryAddress()) {
156                 return add(memoryAddress, buf.memoryAddress() + offset, len);
157             } else {
158                 ByteBuffer nioBuffer = buf.internalNioBuffer(offset, len);
159                 return add(memoryAddress, Buffer.memoryAddress(nioBuffer) + nioBuffer.position(), len);
160             }
161         } else {
162             ByteBuffer[] buffers = buf.nioBuffers(offset, len);
163             for (ByteBuffer nioBuffer : buffers) {
164                 final int remaining = nioBuffer.remaining();
165                 if (remaining != 0 &&
166                         (!add(memoryAddress, Buffer.memoryAddress(nioBuffer) + nioBuffer.position(), remaining)
167                                 || count == IOV_MAX)) {
168                     return false;
169                 }
170             }
171             return true;
172         }
173     }
174 
175     /**
176      * Return {@code true} if there is no more space left in the {@link IovArray}.
177      *
178      * @return full or not.
179      */
180     public boolean isFull() {
181         return memory.capacity() < (count + 1) * IOV_SIZE || size >= maxBytes;
182     }
183 
184     private boolean add(long memoryAddress, long addr, int len) {
185         assert addr != 0;
186 
187         // If there is at least 1 entry then we enforce the maximum bytes. We want to accept at least one entry so we
188         // will attempt to write some data and make progress.
189         if ((maxBytes - len < size && count > 0) ||
190                 // Check if we have enough space left
191                 memory.capacity() < (count + 1) * IOV_SIZE) {
192             // If the size + len will overflow SSIZE_MAX we stop populate the IovArray. This is done as linux
193             //  not allow to write more bytes then SSIZE_MAX with one writev(...) call and so will
194             // return 'EINVAL', which will raise an IOException.
195             //
196             // See also:
197             // - https://linux.die.net//man/2/writev
198             return false;
199         }
200         final int baseOffset = idx(count);
201         final int lengthOffset = baseOffset + ADDRESS_SIZE;
202 
203         size += len;
204         ++count;
205 
206         if (ADDRESS_SIZE == 8) {
207             // 64bit
208             if (PlatformDependent.hasUnsafe()) {
209                 PlatformDependent.putLong(baseOffset + memoryAddress, addr);
210                 PlatformDependent.putLong(lengthOffset + memoryAddress, len);
211             } else {
212                 memory.setLong(baseOffset, addr);
213                 memory.setLong(lengthOffset, len);
214             }
215         } else {
216             assert ADDRESS_SIZE == 4;
217             if (PlatformDependent.hasUnsafe()) {
218                 PlatformDependent.putInt(baseOffset + memoryAddress, (int) addr);
219                 PlatformDependent.putInt(lengthOffset + memoryAddress, len);
220             } else {
221                 memory.setInt(baseOffset, (int) addr);
222                 memory.setInt(lengthOffset, len);
223             }
224         }
225         return true;
226     }
227 
228     /**
229      * Returns the number if iov entries.
230      */
231     public int count() {
232         return count;
233     }
234 
235     /**
236      * Returns the size in bytes
237      */
238     public long size() {
239         return size;
240     }
241 
242     /**
243      * Set the maximum amount of bytes that can be added to this {@link IovArray} via {@link #add(ByteBuf, int, int)}
244      * <p>
245      * This will not impact the existing state of the {@link IovArray}, and only applies to subsequent calls to
246      * {@link #add(ByteBuf)}.
247      * <p>
248      * In order to ensure some progress is made at least one {@link ByteBuf} will be accepted even if it's size exceeds
249      * this value.
250      * @param maxBytes the maximum amount of bytes that can be added to this {@link IovArray}.
251      */
252     public void maxBytes(long maxBytes) {
253         this.maxBytes = min(SSIZE_MAX, checkPositive(maxBytes, "maxBytes"));
254     }
255 
256     /**
257      * Set the maximum amount of buffers that can be added to this {@link IovArray} via {@link #add(ByteBuf, int, int)}
258      * <p>
259      * This will not impact the existing state of the {@link IovArray}, and only applies to subsequent calls to
260      * {@link #add(ByteBuf)}.
261      * <p>
262      * @param maxCount the maximum amount of bytes that can be added to this {@link IovArray}.
263      */
264     public void maxCount(int maxCount) {
265         this.maxCount = min(IOV_MAX, checkPositive(maxCount, "maxCount"));
266     }
267 
268     /**
269      * Get the maximum amount of bytes that can be added to this {@link IovArray}.
270      * @return the maximum amount of bytes that can be added to this {@link IovArray}.
271      */
272     public long maxBytes() {
273         return maxBytes;
274     }
275 
276     /**
277      * Get the maximum amount of buffers that can be added to this {@link IovArray}.
278      * @return the maximum amount of buffers that can be added to this {@link IovArray}.
279      */
280     public int maxCount() {
281         return maxCount;
282     }
283 
284     /**
285      * Returns the {@code memoryAddress} for the given {@code offset}.
286      */
287     public long memoryAddress(int offset) {
288         return memoryAddress + idx(offset);
289     }
290 
291     /**
292      * Release the {@link IovArray}. Once release further using of it may crash the JVM!
293      */
294     public void release() {
295         memory.release();
296         if (cleanable != null) {
297             // The 'cleanable' will be 'null' if the 'IovArray(ByteBuf)' constructor was used.
298             cleanable.clean();
299         }
300     }
301 
302     @Override
303     public boolean processMessage(Object msg) throws Exception {
304         if (msg instanceof ByteBuf) {
305             ByteBuf buffer = (ByteBuf) msg;
306             return add(buffer, buffer.readerIndex(), buffer.readableBytes());
307         }
308         return false;
309     }
310 
311     private static int idx(int index) {
312         return IOV_SIZE * index;
313     }
314 }