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 private boolean add(long memoryAddress, long addr, int len) {
137 assert addr != 0;
138
139 // If there is at least 1 entry then we enforce the maximum bytes. We want to accept at least one entry so we
140 // will attempt to write some data and make progress.
141 if ((maxBytes - len < size && count > 0) ||
142 // Check if we have enough space left
143 memory.capacity() < (count + 1) * IOV_SIZE) {
144 // If the size + len will overflow SSIZE_MAX we stop populate the IovArray. This is done as linux
145 // not allow to write more bytes then SSIZE_MAX with one writev(...) call and so will
146 // return 'EINVAL', which will raise an IOException.
147 //
148 // See also:
149 // - https://linux.die.net//man/2/writev
150 return false;
151 }
152 final int baseOffset = idx(count);
153 final int lengthOffset = baseOffset + ADDRESS_SIZE;
154
155 size += len;
156 ++count;
157
158 if (ADDRESS_SIZE == 8) {
159 // 64bit
160 if (PlatformDependent.hasUnsafe()) {
161 PlatformDependent.putLong(baseOffset + memoryAddress, addr);
162 PlatformDependent.putLong(lengthOffset + memoryAddress, len);
163 } else {
164 memory.setLong(baseOffset, addr);
165 memory.setLong(lengthOffset, len);
166 }
167 } else {
168 assert ADDRESS_SIZE == 4;
169 if (PlatformDependent.hasUnsafe()) {
170 PlatformDependent.putInt(baseOffset + memoryAddress, (int) addr);
171 PlatformDependent.putInt(lengthOffset + memoryAddress, len);
172 } else {
173 memory.setInt(baseOffset, (int) addr);
174 memory.setInt(lengthOffset, len);
175 }
176 }
177 return true;
178 }
179
180 /**
181 * Returns the number if iov entries.
182 */
183 public int count() {
184 return count;
185 }
186
187 /**
188 * Returns the size in bytes
189 */
190 public long size() {
191 return size;
192 }
193
194 /**
195 * Set the maximum amount of bytes that can be added to this {@link IovArray} via {@link #add(ByteBuf, int, int)}
196 * <p>
197 * This will not impact the existing state of the {@link IovArray}, and only applies to subsequent calls to
198 * {@link #add(ByteBuf)}.
199 * <p>
200 * In order to ensure some progress is made at least one {@link ByteBuf} will be accepted even if it's size exceeds
201 * this value.
202 * @param maxBytes the maximum amount of bytes that can be added to this {@link IovArray}.
203 */
204 public void maxBytes(long maxBytes) {
205 this.maxBytes = min(SSIZE_MAX, checkPositive(maxBytes, "maxBytes"));
206 }
207
208 /**
209 * Get the maximum amount of bytes that can be added to this {@link IovArray}.
210 * @return the maximum amount of bytes that can be added to this {@link IovArray}.
211 */
212 public long maxBytes() {
213 return maxBytes;
214 }
215
216 /**
217 * Returns the {@code memoryAddress} for the given {@code offset}.
218 */
219 public long memoryAddress(int offset) {
220 return memoryAddress + idx(offset);
221 }
222
223 /**
224 * Release the {@link IovArray}. Once release further using of it may crash the JVM!
225 */
226 public void release() {
227 memory.release();
228 }
229
230 @Override
231 public boolean processMessage(Object msg) throws Exception {
232 if (msg instanceof ByteBuf) {
233 ByteBuf buffer = (ByteBuf) msg;
234 return add(buffer, buffer.readerIndex(), buffer.readableBytes());
235 }
236 return false;
237 }
238
239 private static int idx(int index) {
240 return IOV_SIZE * index;
241 }
242 }