1 /*
2 * Copyright 2012 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 org.jboss.netty.handler.ssl;
17
18 import javax.net.ssl.SSLEngine;
19 import java.nio.ByteBuffer;
20 import java.util.concurrent.ArrayBlockingQueue;
21 import java.util.concurrent.BlockingQueue;
22 import java.util.concurrent.atomic.AtomicInteger;
23
24 /**
25 * A {@link ByteBuffer} pool dedicated for {@link SslHandler} performance improvement.
26 * <p>
27 * In most cases, you won't need to create a new pool instance because {@link SslHandler} has a default pool
28 * instance internally.
29 * <p>
30 * The reason why {@link SslHandler} requires a buffer pool is because the current {@link SSLEngine} implementation
31 * always requires a 17KiB buffer for every 'wrap' and 'unwrap' operation. In most cases, the actual size of the
32 * required buffer is much smaller than that, and therefore allocating a 17KiB buffer for every 'wrap' and 'unwrap'
33 * operation wastes a lot of memory bandwidth, resulting in the application performance degradation.
34 */
35 public class SslBufferPool {
36
37 // Add 1024 as a room for compressed data and another 1024 for Apache Harmony compatibility.
38 private static final int MAX_PACKET_SIZE_ALIGNED = (OpenSslEngine.MAX_ENCRYPTED_PACKET_LENGTH / 128 + 1) * 128;
39
40 private static final int DEFAULT_POOL_SIZE = MAX_PACKET_SIZE_ALIGNED * 1024;
41
42 private final ByteBuffer preallocated;
43 private final BlockingQueue<ByteBuffer> pool;
44 private final int maxBufferCount;
45 private final boolean allocateDirect;
46
47 /**
48 * The number of buffers allocated so far. Used only when {@link #preallocated} is null.
49 */
50 private final AtomicInteger numAllocations;
51
52 /**
53 * Creates a new buffer pool whose size is {@code 19267584}, which can hold {@code 1024} buffers.
54 */
55 public SslBufferPool() {
56 this(DEFAULT_POOL_SIZE);
57 }
58
59 /**
60 * Creates a new buffer pool whose size is {@code 19267584}, which can hold {@code 1024} buffers.
61 *
62 * @param preallocate {@code true} if and only if the buffers in this pool has to be pre-allocated
63 * at construction time
64 * @param allocateDirect {@code true} if and only if this pool has to allocate direct buffers.
65 */
66 public SslBufferPool(boolean preallocate, boolean allocateDirect) {
67 this(DEFAULT_POOL_SIZE, preallocate, allocateDirect);
68 }
69
70 /**
71 * Creates a new buffer pool.
72 *
73 * @param maxPoolSize the maximum number of bytes that this pool can hold
74 */
75 public SslBufferPool(int maxPoolSize) {
76 this(maxPoolSize, false, false);
77 }
78
79 /**
80 * Creates a new buffer pool.
81 *
82 * @param maxPoolSize the maximum number of bytes that this pool can hold
83 * @param preallocate {@code true} if and only if the buffers in this pool has to be pre-allocated
84 * at construction time
85 * @param allocateDirect {@code true} if and only if this pool has to allocate direct buffers.
86 */
87 public SslBufferPool(int maxPoolSize, boolean preallocate, boolean allocateDirect) {
88 if (maxPoolSize <= 0) {
89 throw new IllegalArgumentException("maxPoolSize: " + maxPoolSize);
90 }
91
92 int maxBufferCount = maxPoolSize / MAX_PACKET_SIZE_ALIGNED;
93 if (maxPoolSize % MAX_PACKET_SIZE_ALIGNED != 0) {
94 maxBufferCount ++;
95 }
96
97 this.maxBufferCount = maxBufferCount;
98 this.allocateDirect = allocateDirect;
99
100 pool = new ArrayBlockingQueue<ByteBuffer>(maxBufferCount);
101
102 if (preallocate) {
103 preallocated = allocate(maxBufferCount * MAX_PACKET_SIZE_ALIGNED);
104 numAllocations = null;
105 for (int i = 0; i < maxBufferCount; i ++) {
106 int pos = i * MAX_PACKET_SIZE_ALIGNED;
107 preallocated.clear().position(pos).limit(pos + MAX_PACKET_SIZE_ALIGNED);
108 pool.add(preallocated.slice());
109 }
110 } else {
111 preallocated = null;
112 numAllocations = new AtomicInteger();
113 }
114 }
115
116 /**
117 * Returns the maximum size of this pool in byte unit. The returned value
118 * can be somewhat different from what was specified in the constructor.
119 */
120 public int getMaxPoolSize() {
121 return maxBufferCount * MAX_PACKET_SIZE_ALIGNED;
122 }
123
124 /**
125 * Returns the number of bytes which were allocated but have not been
126 * acquired yet. You can estimate how optimal the specified maximum pool
127 * size is from this value. If it keeps returning {@code 0}, it means the
128 * pool is getting exhausted. If it keeps returns a unnecessarily big
129 * value, it means the pool is wasting the heap space.
130 */
131 public int getUnacquiredPoolSize() {
132 return pool.size() * MAX_PACKET_SIZE_ALIGNED;
133 }
134
135 /**
136 * Acquire a new {@link ByteBuffer} out of the {@link SslBufferPool}
137 *
138 */
139 public ByteBuffer acquireBuffer() {
140 ByteBuffer buf;
141 if (preallocated != null || numAllocations.get() >= maxBufferCount) {
142 boolean interrupted = false;
143 for (;;) {
144 try {
145 buf = pool.take();
146 break;
147 } catch (InterruptedException ignore) {
148 interrupted = true;
149 }
150 }
151 if (interrupted) {
152 Thread.currentThread().interrupt();
153 }
154 } else {
155 buf = pool.poll();
156 if (buf == null) {
157 // Note that we can allocate more buffers than maxBufferCount.
158 // We will discard the buffers allocated after numAllocations reached maxBufferCount in releaseBuffer().
159 numAllocations.incrementAndGet();
160 buf = allocate(OpenSslEngine.MAX_ENCRYPTED_PACKET_LENGTH);
161 }
162 }
163
164 buf.clear();
165 return buf;
166 }
167
168 /**
169 * Release a previous acquired {@link ByteBuffer}
170 */
171 public void releaseBuffer(ByteBuffer buffer) {
172 pool.offer(buffer);
173 }
174
175 private ByteBuffer allocate(int capacity) {
176 if (allocateDirect) {
177 return ByteBuffer.allocateDirect(capacity);
178 } else {
179 return ByteBuffer.allocate(capacity);
180 }
181 }
182 }