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    *   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  
17  package io.netty.buffer;
18  
19  import java.util.concurrent.locks.ReentrantLock;
20  
21  import static io.netty.buffer.PoolChunk.RUN_OFFSET_SHIFT;
22  import static io.netty.buffer.PoolChunk.SIZE_SHIFT;
23  import static io.netty.buffer.PoolChunk.IS_USED_SHIFT;
24  import static io.netty.buffer.PoolChunk.IS_SUBPAGE_SHIFT;
25  
26  final class PoolSubpage<T> implements PoolSubpageMetric {
27  
28      final PoolChunk<T> chunk;
29      final int elemSize;
30      private final int pageShifts;
31      private final int runOffset;
32      private final int runSize;
33      private final long[] bitmap;
34      private final int bitmapLength;
35      private final int maxNumElems;
36      final int headIndex;
37  
38      PoolSubpage<T> prev;
39      PoolSubpage<T> next;
40  
41      boolean doNotDestroy;
42      private int nextAvail;
43      private int numAvail;
44  
45      final ReentrantLock lock;
46  
47      // TODO: Test if adding padding helps under contention
48      //private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7;
49  
50      /** Special constructor that creates a linked list head */
51      PoolSubpage(int headIndex) {
52          chunk = null;
53          lock = new ReentrantLock();
54          pageShifts = -1;
55          runOffset = -1;
56          elemSize = -1;
57          runSize = -1;
58          bitmap = null;
59          bitmapLength = -1;
60          maxNumElems = 0;
61          this.headIndex = headIndex;
62      }
63  
64      PoolSubpage(PoolSubpage<T> head, PoolChunk<T> chunk, int pageShifts, int runOffset, int runSize, int elemSize) {
65          this.headIndex = head.headIndex;
66          this.chunk = chunk;
67          this.pageShifts = pageShifts;
68          this.runOffset = runOffset;
69          this.runSize = runSize;
70          this.elemSize = elemSize;
71  
72          doNotDestroy = true;
73  
74          maxNumElems = numAvail = runSize / elemSize;
75          int bitmapLength = maxNumElems >>> 6;
76          if ((maxNumElems & 63) != 0) {
77              bitmapLength ++;
78          }
79          this.bitmapLength = bitmapLength;
80          bitmap = new long[bitmapLength];
81          nextAvail = 0;
82  
83          lock = null;
84          addToPool(head);
85      }
86  
87      /**
88       * Returns the bitmap index of the subpage allocation.
89       */
90      long allocate() {
91          if (numAvail == 0 || !doNotDestroy) {
92              return -1;
93          }
94  
95          final int bitmapIdx = getNextAvail();
96          if (bitmapIdx < 0) {
97              removeFromPool(); // Subpage appear to be in an invalid state. Remove to prevent repeated errors.
98              throw new AssertionError("No next available bitmap index found (bitmapIdx = " + bitmapIdx + "), " +
99                      "even though there are supposed to be (numAvail = " + numAvail + ") " +
100                     "out of (maxNumElems = " + maxNumElems + ") available indexes.");
101         }
102         int q = bitmapIdx >>> 6;
103         int r = bitmapIdx & 63;
104         assert (bitmap[q] >>> r & 1) == 0;
105         bitmap[q] |= 1L << r;
106 
107         if (-- numAvail == 0) {
108             removeFromPool();
109         }
110 
111         return toHandle(bitmapIdx);
112     }
113 
114     /**
115      * @return {@code true} if this subpage is in use.
116      *         {@code false} if this subpage is not used by its chunk and thus it's OK to be released.
117      */
118     boolean free(PoolSubpage<T> head, int bitmapIdx) {
119         int q = bitmapIdx >>> 6;
120         int r = bitmapIdx & 63;
121         assert (bitmap[q] >>> r & 1) != 0;
122         bitmap[q] ^= 1L << r;
123 
124         setNextAvail(bitmapIdx);
125 
126         if (numAvail ++ == 0) {
127             addToPool(head);
128             /* When maxNumElems == 1, the maximum numAvail is also 1.
129              * Each of these PoolSubpages will go in here when they do free operation.
130              * If they return true directly from here, then the rest of the code will be unreachable
131              * and they will not actually be recycled. So return true only on maxNumElems > 1. */
132             if (maxNumElems > 1) {
133                 return true;
134             }
135         }
136 
137         if (numAvail != maxNumElems) {
138             return true;
139         } else {
140             // Subpage not in use (numAvail == maxNumElems)
141             if (prev == next) {
142                 // Do not remove if this subpage is the only one left in the pool.
143                 return true;
144             }
145 
146             // Remove this subpage from the pool if there are other subpages left in the pool.
147             doNotDestroy = false;
148             removeFromPool();
149             return false;
150         }
151     }
152 
153     private void addToPool(PoolSubpage<T> head) {
154         assert prev == null && next == null;
155         prev = head;
156         next = head.next;
157         next.prev = this;
158         head.next = this;
159     }
160 
161     private void removeFromPool() {
162         assert prev != null && next != null;
163         prev.next = next;
164         next.prev = prev;
165         next = null;
166         prev = null;
167     }
168 
169     private void setNextAvail(int bitmapIdx) {
170         nextAvail = bitmapIdx;
171     }
172 
173     private int getNextAvail() {
174         int nextAvail = this.nextAvail;
175         if (nextAvail >= 0) {
176             this.nextAvail = -1;
177             return nextAvail;
178         }
179         return findNextAvail();
180     }
181 
182     private int findNextAvail() {
183         for (int i = 0; i < bitmapLength; i ++) {
184             long bits = bitmap[i];
185             if (~bits != 0) {
186                 return findNextAvail0(i, bits);
187             }
188         }
189         return -1;
190     }
191 
192     private int findNextAvail0(int i, long bits) {
193         final int baseVal = i << 6;
194         for (int j = 0; j < 64; j ++) {
195             if ((bits & 1) == 0) {
196                 int val = baseVal | j;
197                 if (val < maxNumElems) {
198                     return val;
199                 } else {
200                     break;
201                 }
202             }
203             bits >>>= 1;
204         }
205         return -1;
206     }
207 
208     private long toHandle(int bitmapIdx) {
209         int pages = runSize >> pageShifts;
210         return (long) runOffset << RUN_OFFSET_SHIFT
211                | (long) pages << SIZE_SHIFT
212                | 1L << IS_USED_SHIFT
213                | 1L << IS_SUBPAGE_SHIFT
214                | bitmapIdx;
215     }
216 
217     @Override
218     public String toString() {
219         final int numAvail;
220         if (chunk == null) {
221             // This is the head so there is no need to synchronize at all as these never change.
222             numAvail = 0;
223         } else {
224             final boolean doNotDestroy;
225             PoolSubpage<T> head = chunk.arena.smallSubpagePools[headIndex];
226             head.lock();
227             try {
228                 doNotDestroy = this.doNotDestroy;
229                 numAvail = this.numAvail;
230             } finally {
231                 head.unlock();
232             }
233             if (!doNotDestroy) {
234                 // Not used for creating the String.
235                 return "(" + runOffset + ": not in use)";
236             }
237         }
238 
239         return "(" + this.runOffset + ": " + (this.maxNumElems - numAvail) + '/' + this.maxNumElems +
240                 ", offset: " + this.runOffset + ", length: " + this.runSize + ", elemSize: " + this.elemSize + ')';
241     }
242 
243     @Override
244     public int maxNumElements() {
245         return maxNumElems;
246     }
247 
248     @Override
249     public int numAvailable() {
250         if (chunk == null) {
251             // It's the head.
252             return 0;
253         }
254         PoolSubpage<T> head = chunk.arena.smallSubpagePools[headIndex];
255         head.lock();
256         try {
257             return numAvail;
258         } finally {
259             head.unlock();
260         }
261     }
262 
263     @Override
264     public int elementSize() {
265         return elemSize;
266     }
267 
268     @Override
269     public int pageSize() {
270         return 1 << pageShifts;
271     }
272 
273     boolean isDoNotDestroy() {
274         if (chunk == null) {
275             // It's the head.
276             return true;
277         }
278         PoolSubpage<T> head = chunk.arena.smallSubpagePools[headIndex];
279         head.lock();
280         try {
281             return doNotDestroy;
282         } finally {
283             head.unlock();
284         }
285     }
286 
287     void destroy() {
288         if (chunk != null) {
289             chunk.destroy();
290         }
291     }
292 
293     void lock() {
294         lock.lock();
295     }
296 
297     void unlock() {
298         lock.unlock();
299     }
300 }