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    *   http://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.epoll;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.channel.ChannelOutboundBuffer.MessageProcessor;
20  import io.netty.util.internal.PlatformDependent;
21  
22  import java.nio.ByteBuffer;
23  
24  /**
25   * Represent an array of struct array and so can be passed directly over via JNI without the need to do any more
26   * array copies.
27   *
28   * The buffers are written out directly into direct memory to match the struct iov. See also {@code man writev}.
29   *
30   * <pre>
31   * struct iovec {
32   *   void  *iov_base;
33   *   size_t iov_len;
34   * };
35   * </pre>
36   *
37   * See also
38   * <a href="http://rkennke.wordpress.com/2007/07/30/efficient-jni-programming-iv-wrapping-native-data-objects/"
39   * >Efficient JNI programming IV: Wrapping native data objects</a>.
40   */
41  final class IovArray implements MessageProcessor {
42  
43      /** The size of an address which should be 8 for 64 bits and 4 for 32 bits. */
44      private static final int ADDRESS_SIZE = PlatformDependent.addressSize();
45  
46      /**
47       * The size of an {@code iovec} struct in bytes. This is calculated as we have 2 entries each of the size of the
48       * address.
49       */
50      private static final int IOV_SIZE = 2 * ADDRESS_SIZE;
51  
52      /**
53       * The needed memory to hold up to {@link Native#IOV_MAX} iov entries, where {@link Native#IOV_MAX} signified
54       * the maximum number of {@code iovec} structs that can be passed to {@code writev(...)}.
55       */
56      private static final int CAPACITY = Native.IOV_MAX * IOV_SIZE;
57  
58      private final long memoryAddress;
59      private int count;
60      private long size;
61  
62      IovArray() {
63          memoryAddress = PlatformDependent.allocateMemory(CAPACITY);
64      }
65  
66      void clear() {
67          count = 0;
68          size = 0;
69      }
70  
71      /**
72       * Try to add the given {@link ByteBuf}. Returns {@code true} on success,
73       * {@code false} otherwise.
74       */
75      boolean add(ByteBuf buf) {
76          int nioBufferCount = buf.nioBufferCount();
77          if (count + nioBufferCount > Native.IOV_MAX) {
78              // No more room!
79              return false;
80          }
81  
82          if (nioBufferCount == 1) {
83              final int len = buf.readableBytes();
84              if (len == 0) {
85                  // No need to add an empty buffer.
86                  // We return true here because we want ChannelOutboundBuffer.forEachFlushedMessage() to continue
87                  // fetching the next buffers.
88                  return true;
89              }
90  
91              final long addr = buf.memoryAddress();
92              final int offset = buf.readerIndex();
93              return add(addr, offset, len);
94          } else {
95              ByteBuffer[] buffers = buf.nioBuffers();
96              for (ByteBuffer nioBuffer : buffers) {
97                  int len = nioBuffer.remaining();
98                  if (len == 0) {
99                      // No need to add an empty buffer so just continue
100                     continue;
101                 }
102                 int offset = nioBuffer.position();
103                 long addr = PlatformDependent.directBufferAddress(nioBuffer);
104 
105                 if (!add(addr, offset, len)) {
106                     return false;
107                 }
108             }
109             return true;
110         }
111     }
112 
113     private boolean add(long addr, int offset, int len) {
114         if (len == 0) {
115             // No need to add an empty buffer.
116             return true;
117         }
118 
119         final long baseOffset = memoryAddress(count++);
120         final long lengthOffset = baseOffset + ADDRESS_SIZE;
121 
122         if (Native.SSIZE_MAX - len < size) {
123             // If the size + len will overflow an SSIZE_MAX we stop populate the IovArray. This is done as linux
124             //  not allow to write more bytes then SSIZE_MAX with one writev(...) call and so will
125             // return 'EINVAL', which will raise an IOException.
126             //
127             // See also:
128             // - http://linux.die.net/man/2/writev
129             return false;
130         }
131         size += len;
132 
133         if (ADDRESS_SIZE == 8) {
134             // 64bit
135             PlatformDependent.putLong(baseOffset, addr + offset);
136             PlatformDependent.putLong(lengthOffset, len);
137         } else {
138             assert ADDRESS_SIZE == 4;
139             PlatformDependent.putInt(baseOffset, (int) addr + offset);
140             PlatformDependent.putInt(lengthOffset, len);
141         }
142         return true;
143     }
144 
145     /**
146      * Process the written iov entries. This will return the length of the iov entry on the given index if it is
147      * smaller then the given {@code written} value. Otherwise it returns {@code -1}.
148      */
149     long processWritten(int index, long written) {
150         long baseOffset = memoryAddress(index);
151         long lengthOffset = baseOffset + ADDRESS_SIZE;
152         if (ADDRESS_SIZE == 8) {
153             // 64bit
154             long len = PlatformDependent.getLong(lengthOffset);
155             if (len > written) {
156                 long offset = PlatformDependent.getLong(baseOffset);
157                 PlatformDependent.putLong(baseOffset, offset + written);
158                 PlatformDependent.putLong(lengthOffset, len - written);
159                 return -1;
160             }
161             return len;
162         } else {
163             assert ADDRESS_SIZE == 4;
164             long len = PlatformDependent.getInt(lengthOffset);
165             if (len > written) {
166                 int offset = PlatformDependent.getInt(baseOffset);
167                 PlatformDependent.putInt(baseOffset, (int) (offset + written));
168                 PlatformDependent.putInt(lengthOffset, (int) (len - written));
169                 return -1;
170             }
171             return len;
172         }
173     }
174 
175     /**
176      * Returns the number if iov entries.
177      */
178     int count() {
179         return count;
180     }
181 
182     /**
183      * Returns the size in bytes
184      */
185     long size() {
186         return size;
187     }
188 
189     /**
190      * Returns the {@code memoryAddress} for the given {@code offset}.
191      */
192     long memoryAddress(int offset) {
193         return memoryAddress + IOV_SIZE * offset;
194     }
195 
196     /**
197      * Release the {@link IovArray}. Once release further using of it may crash the JVM!
198      */
199     void release() {
200         PlatformDependent.freeMemory(memoryAddress);
201     }
202 
203     @Override
204     public boolean processMessage(Object msg) throws Exception {
205         if (msg instanceof ByteBuf) {
206             return add((ByteBuf) msg);
207         }
208         return false;
209     }
210 }