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 }