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