View Javadoc
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 }