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 }