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