View Javadoc
1   /*
2    * Copyright 2017 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
5    * "License"); you may not use this file except in compliance with the License. You may obtain a
6    * 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 distributed under the License
11   * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12   * or implied. See the License for the specific language governing permissions and limitations under
13   * the License.
14   */
15  package io.netty.channel;
16  
17  import io.netty.buffer.ByteBuf;
18  import io.netty.buffer.ByteBufAllocator;
19  import io.netty.buffer.CompositeByteBuf;
20  import io.netty.util.internal.UnstableApi;
21  import io.netty.util.internal.logging.InternalLogger;
22  import io.netty.util.internal.logging.InternalLoggerFactory;
23  
24  import java.util.ArrayDeque;
25  
26  import static io.netty.util.ReferenceCountUtil.safeRelease;
27  import static io.netty.util.internal.ObjectUtil.checkNotNull;
28  import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
29  import static io.netty.util.internal.PlatformDependent.throwException;
30  
31  @UnstableApi
32  public abstract class AbstractCoalescingBufferQueue {
33      private static final InternalLogger logger = InternalLoggerFactory.getInstance(AbstractCoalescingBufferQueue.class);
34      private final ArrayDeque<Object> bufAndListenerPairs;
35      private final PendingBytesTracker tracker;
36      private int readableBytes;
37  
38      /**
39       * Create a new instance.
40       *
41       * @param channel the {@link Channel} which will have the {@link Channel#isWritable()} reflect the amount of queued
42       *                buffers or {@code null} if there is no writability state updated.
43       * @param initSize the initial size of the underlying queue.
44       */
45      protected AbstractCoalescingBufferQueue(Channel channel, int initSize) {
46          bufAndListenerPairs = new ArrayDeque<Object>(initSize);
47          tracker = channel == null ? null : PendingBytesTracker.newTracker(channel);
48      }
49  
50      /**
51       * Add a buffer to the front of the queue and associate a promise with it that should be completed when
52       * all the buffer's bytes have been consumed from the queue and written.
53       * @param buf to add to the head of the queue
54       * @param promise to complete when all the bytes have been consumed and written, can be void.
55       */
56      public final void addFirst(ByteBuf buf, ChannelPromise promise) {
57          addFirst(buf, toChannelFutureListener(promise));
58      }
59  
60      private void addFirst(ByteBuf buf, ChannelFutureListener listener) {
61          // Touch the message to make it easier to debug buffer leaks.
62          buf.touch();
63  
64          if (listener != null) {
65              bufAndListenerPairs.addFirst(listener);
66          }
67          bufAndListenerPairs.addFirst(buf);
68          incrementReadableBytes(buf.readableBytes());
69      }
70  
71      /**
72       * Add a buffer to the end of the queue.
73       */
74      public final void add(ByteBuf buf) {
75          add(buf, (ChannelFutureListener) null);
76      }
77  
78      /**
79       * Add a buffer to the end of the queue and associate a promise with it that should be completed when
80       * all the buffer's bytes have been consumed from the queue and written.
81       * @param buf to add to the tail of the queue
82       * @param promise to complete when all the bytes have been consumed and written, can be void.
83       */
84      public final void add(ByteBuf buf, ChannelPromise promise) {
85          // buffers are added before promises so that we naturally 'consume' the entire buffer during removal
86          // before we complete it's promise.
87          add(buf, toChannelFutureListener(promise));
88      }
89  
90      /**
91       * Add a buffer to the end of the queue and associate a listener with it that should be completed when
92       * all the buffers  bytes have been consumed from the queue and written.
93       * @param buf to add to the tail of the queue
94       * @param listener to notify when all the bytes have been consumed and written, can be {@code null}.
95       */
96      public final void add(ByteBuf buf, ChannelFutureListener listener) {
97          // Touch the message to make it easier to debug buffer leaks.
98          buf.touch();
99  
100         // buffers are added before promises so that we naturally 'consume' the entire buffer during removal
101         // before we complete it's promise.
102         bufAndListenerPairs.add(buf);
103         if (listener != null) {
104             bufAndListenerPairs.add(listener);
105         }
106         incrementReadableBytes(buf.readableBytes());
107     }
108 
109     /**
110      * Remove the first {@link ByteBuf} from the queue.
111      * @param aggregatePromise used to aggregate the promises and listeners for the returned buffer.
112      * @return the first {@link ByteBuf} from the queue.
113      */
114     public final ByteBuf removeFirst(ChannelPromise aggregatePromise) {
115         Object entry = bufAndListenerPairs.poll();
116         if (entry == null) {
117             return null;
118         }
119         assert entry instanceof ByteBuf;
120         ByteBuf result = (ByteBuf) entry;
121 
122         decrementReadableBytes(result.readableBytes());
123 
124         entry = bufAndListenerPairs.peek();
125         if (entry instanceof ChannelFutureListener) {
126             aggregatePromise.addListener((ChannelFutureListener) entry);
127             bufAndListenerPairs.poll();
128         }
129         reconcileReadableBytes();
130         return result;
131     }
132 
133     /**
134      * Remove a {@link ByteBuf} from the queue with the specified number of bytes. Any added buffer who's bytes are
135      * fully consumed during removal will have it's promise completed when the passed aggregate {@link ChannelPromise}
136      * completes.
137      *
138      * @param alloc The allocator used if a new {@link ByteBuf} is generated during the aggregation process.
139      * @param bytes the maximum number of readable bytes in the returned {@link ByteBuf}, if {@code bytes} is greater
140      *              than {@link #readableBytes} then a buffer of length {@link #readableBytes} is returned.
141      * @param aggregatePromise used to aggregate the promises and listeners for the constituent buffers.
142      * @return a {@link ByteBuf} composed of the enqueued buffers.
143      */
144     public final ByteBuf remove(ByteBufAllocator alloc, int bytes, ChannelPromise aggregatePromise) {
145         checkPositiveOrZero(bytes, "bytes");
146         checkNotNull(aggregatePromise, "aggregatePromise");
147 
148         // Use isEmpty rather than readableBytes==0 as we may have a promise associated with an empty buffer.
149         if (bufAndListenerPairs.isEmpty()) {
150             reconcileReadableBytes();
151             return removeEmptyValue();
152         }
153         bytes = Math.min(bytes, readableBytes);
154 
155         ByteBuf toReturn = null;
156         ByteBuf entryBuffer = null;
157         int originalBytes = bytes;
158         Object entry = null;
159         try {
160             for (;;) {
161                 entry = bufAndListenerPairs.poll();
162                 if (entry == null) {
163                     break;
164                 }
165                 // fast-path vs abstract type
166                 if (entry instanceof ByteBuf) {
167                     entryBuffer = (ByteBuf) entry;
168                     int bufferBytes = entryBuffer.readableBytes();
169 
170                     if (bufferBytes > bytes) {
171                         // Add the buffer back to the queue as we can't consume all of it.
172                         bufAndListenerPairs.addFirst(entryBuffer);
173                         if (bytes > 0) {
174                             // Take a slice of what we can consume and retain it.
175                             entryBuffer = entryBuffer.readRetainedSlice(bytes);
176                             // we end here, so if this is the only buffer to return, skip composing
177                             toReturn = toReturn == null ? entryBuffer
178                                     : compose(alloc, toReturn, entryBuffer);
179                             bytes = 0;
180                         }
181                         break;
182                     }
183 
184                     bytes -= bufferBytes;
185                     if (toReturn == null) {
186                         // if there are no more bytes to read, there's no reason to compose
187                         toReturn = bytes == 0
188                                 ? entryBuffer
189                                 : composeFirst(alloc, entryBuffer, bufferBytes + bytes);
190                     } else {
191                         toReturn = compose(alloc, toReturn, entryBuffer);
192                     }
193                     entryBuffer = null;
194                 } else if (entry instanceof DelegatingChannelPromiseNotifier) {
195                     aggregatePromise.addListener((DelegatingChannelPromiseNotifier) entry);
196                 } else if (entry instanceof ChannelFutureListener) {
197                     aggregatePromise.addListener((ChannelFutureListener) entry);
198                 }
199             }
200         } catch (Throwable cause) {
201             // Always decrement to keep things consistent. We decrement directly here and not in a finally-block
202             // to ensure that the state is consistent even if it would be accessed via a listener that is
203             // attached to the promise that we fail below.
204             decrementReadableBytes(originalBytes - bytes);
205 
206             // Poll the next element if it's a listener that belongs to the ByteBuf.
207             entry = bufAndListenerPairs.peek();
208             if (entry instanceof ChannelFutureListener) {
209                 aggregatePromise.addListener((ChannelFutureListener) entry);
210                 bufAndListenerPairs.poll();
211             }
212 
213             safeRelease(entryBuffer);
214             safeRelease(toReturn);
215             aggregatePromise.setFailure(cause);
216             throwException(cause);
217         }
218         decrementReadableBytes(originalBytes - bytes);
219         reconcileReadableBytes();
220         return toReturn;
221     }
222 
223     /**
224      * The number of readable bytes.
225      */
226     public final int readableBytes() {
227         return readableBytes;
228     }
229 
230     /**
231      * Are there pending buffers in the queue.
232      */
233     public final boolean isEmpty() {
234         return bufAndListenerPairs.isEmpty();
235     }
236 
237     /**
238      *  Release all buffers in the queue and complete all listeners and promises.
239      */
240     public final void releaseAndFailAll(ChannelOutboundInvoker invoker, Throwable cause) {
241         releaseAndCompleteAll(invoker.newFailedFuture(cause));
242     }
243 
244     /**
245      * Copy all pending entries in this queue into the destination queue.
246      * @param dest to copy pending buffers to.
247      */
248     public final void copyTo(AbstractCoalescingBufferQueue dest) {
249         dest.bufAndListenerPairs.addAll(bufAndListenerPairs);
250         dest.incrementReadableBytes(readableBytes);
251     }
252 
253     /**
254      * Writes all remaining elements in this queue.
255      * @param ctx The context to write all elements to.
256      */
257     public final void writeAndRemoveAll(ChannelHandlerContext ctx) {
258         Throwable pending = null;
259         ByteBuf previousBuf = null;
260         for (;;) {
261             Object entry = bufAndListenerPairs.poll();
262             try {
263                 if (entry == null) {
264                     if (previousBuf != null) {
265                         decrementReadableBytes(previousBuf.readableBytes());
266                         ctx.write(previousBuf, ctx.voidPromise());
267                     }
268                     break;
269                 }
270 
271                 if (entry instanceof ByteBuf) {
272                     if (previousBuf != null) {
273                         decrementReadableBytes(previousBuf.readableBytes());
274                         ctx.write(previousBuf, ctx.voidPromise());
275                     }
276                     previousBuf = (ByteBuf) entry;
277                 } else if (entry instanceof ChannelPromise) {
278                     decrementReadableBytes(previousBuf.readableBytes());
279                     ctx.write(previousBuf, (ChannelPromise) entry);
280                     previousBuf = null;
281                 } else {
282                     decrementReadableBytes(previousBuf.readableBytes());
283                     ctx.write(previousBuf).addListener((ChannelFutureListener) entry);
284                     previousBuf = null;
285                 }
286             } catch (Throwable t) {
287                 if (pending == null) {
288                     pending = t;
289                 } else {
290                     logger.info("Throwable being suppressed because Throwable {} is already pending", pending, t);
291                 }
292             }
293         }
294         reconcileReadableBytes();
295         if (pending != null) {
296             throw new IllegalStateException(pending);
297         }
298     }
299 
300     @Override
301     public String toString() {
302         return "bytes: " + readableBytes + " buffers: " + (size() >> 1);
303     }
304 
305     /**
306      * Calculate the result of {@code current + next}.
307      */
308     protected abstract ByteBuf compose(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf next);
309 
310     /**
311      * Compose {@code cumulation} and {@code next} into a new {@link CompositeByteBuf}.
312      */
313     protected final ByteBuf composeIntoComposite(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf next) {
314         // Create a composite buffer to accumulate this pair and potentially all the buffers
315         // in the queue. Using +2 as we have already dequeued current and next.
316         CompositeByteBuf composite = alloc.compositeBuffer(size() + 2);
317         try {
318             composite.addComponent(true, cumulation);
319             composite.addComponent(true, next);
320         } catch (Throwable cause) {
321             composite.release();
322             safeRelease(next);
323             throwException(cause);
324         }
325         return composite;
326     }
327 
328     /**
329      * Compose {@code cumulation} and {@code next} into a new {@link ByteBufAllocator#ioBuffer()}.
330      * @param alloc The allocator to use to allocate the new buffer.
331      * @param cumulation The current cumulation.
332      * @param next The next buffer.
333      * @return The result of {@code cumulation + next}.
334      */
335     protected final ByteBuf copyAndCompose(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf next) {
336         ByteBuf newCumulation = alloc.ioBuffer(cumulation.readableBytes() + next.readableBytes());
337         try {
338             newCumulation.writeBytes(cumulation).writeBytes(next);
339         } catch (Throwable cause) {
340             newCumulation.release();
341             safeRelease(next);
342             throwException(cause);
343         }
344         cumulation.release();
345         next.release();
346         return newCumulation;
347     }
348 
349     /**
350      * Calculate the first {@link ByteBuf} which will be used in subsequent calls to
351      * {@link #compose(ByteBufAllocator, ByteBuf, ByteBuf)}.
352      * @param bufferSize the optimal size of the buffer needed for cumulation
353      * @return the first buffer
354      */
355     protected ByteBuf composeFirst(ByteBufAllocator allocator, ByteBuf first, int bufferSize) {
356         return composeFirst(allocator, first);
357     }
358 
359     /**
360      * Calculate the first {@link ByteBuf} which will be used in subsequent calls to
361      * {@link #compose(ByteBufAllocator, ByteBuf, ByteBuf)}.
362      * This method is deprecated and will be removed in the future. Implementing classes should
363      * override {@link #composeFirst(ByteBufAllocator, ByteBuf, int)} instead.
364      * @deprecated Use {AbstractCoalescingBufferQueue#composeFirst(ByteBufAllocator, ByteBuf, int)}
365      */
366     @Deprecated
367     protected ByteBuf composeFirst(ByteBufAllocator allocator, ByteBuf first) {
368         return first;
369     }
370 
371     /**
372      * The value to return when {@link #remove(ByteBufAllocator, int, ChannelPromise)} is called but the queue is empty.
373      * @return the {@link ByteBuf} which represents an empty queue.
374      */
375     protected abstract ByteBuf removeEmptyValue();
376 
377     /**
378      * Get the number of elements in this queue added via one of the {@link #add(ByteBuf)} methods.
379      * @return the number of elements in this queue.
380      */
381     protected final int size() {
382         return bufAndListenerPairs.size();
383     }
384 
385     private void releaseAndCompleteAll(ChannelFuture future) {
386         Throwable pending = null;
387         for (;;) {
388             Object entry = bufAndListenerPairs.poll();
389             if (entry == null) {
390                 break;
391             }
392             try {
393                 if (entry instanceof ByteBuf) {
394                     ByteBuf buffer = (ByteBuf) entry;
395                     decrementReadableBytes(buffer.readableBytes());
396                     safeRelease(buffer);
397                 } else {
398                     ((ChannelFutureListener) entry).operationComplete(future);
399                 }
400             } catch (Throwable t) {
401                 if (pending == null) {
402                     pending = t;
403                 } else {
404                     logger.info("Throwable being suppressed because Throwable {} is already pending", pending, t);
405                 }
406             }
407         }
408         reconcileReadableBytes();
409         if (pending != null) {
410             throw new IllegalStateException(pending);
411         }
412     }
413 
414     private void incrementReadableBytes(int increment) {
415         int nextReadableBytes = readableBytes + increment;
416         if (nextReadableBytes < readableBytes) {
417             throw new IllegalStateException("buffer queue length overflow: " + readableBytes + " + " + increment);
418         }
419         readableBytes = nextReadableBytes;
420         if (tracker != null) {
421             tracker.incrementPendingOutboundBytes(increment);
422         }
423     }
424 
425     private void decrementReadableBytes(int decrement) {
426         readableBytes -= decrement;
427         assert readableBytes >= 0;
428         if (tracker != null) {
429             tracker.decrementPendingOutboundBytes(decrement);
430         }
431     }
432 
433     /**
434      * Resets readableBytes to 0 when the queue is empty. They can only diverge if a queued buffer was released
435      * or consumed while still referenced by the queue (similar to a reference-counting bug) after it was added,
436      * which would otherwise make remove(...) return empty buffers forever. Logged at error level because it
437      * always indicates a bug that needs to be found.
438      * See https://github.com/netty/netty/issues/16946
439      */
440     private void reconcileReadableBytes() {
441         if (readableBytes != 0 && bufAndListenerPairs.isEmpty()) {
442             logger.error("readableBytes is {} but the queue is empty: a queued buffer was released or consumed " +
443                     "while still referenced by the queue. This indicates a bug in the code that produced the " +
444                     "buffer. Resetting readableBytes to 0.", readableBytes);
445             decrementReadableBytes(readableBytes);
446         }
447     }
448 
449     private static ChannelFutureListener toChannelFutureListener(ChannelPromise promise) {
450         return promise.isVoid() ? null : new DelegatingChannelPromiseNotifier(promise);
451     }
452 }