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