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