View Javadoc
1   /*
2    * Copyright 2013 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.netty.channel;
17  
18  import io.netty.buffer.AbstractReferenceCountedByteBuf;
19  import io.netty.buffer.ByteBuf;
20  import io.netty.buffer.ByteBufHolder;
21  import io.netty.buffer.Unpooled;
22  import io.netty.channel.socket.nio.NioSocketChannel;
23  import io.netty.util.Recycler;
24  import io.netty.util.Recycler.EnhancedHandle;
25  import io.netty.util.ReferenceCountUtil;
26  import io.netty.util.concurrent.FastThreadLocal;
27  import io.netty.util.internal.InternalThreadLocalMap;
28  import io.netty.util.internal.ObjectPool.Handle;
29  import io.netty.util.internal.ObjectUtil;
30  import io.netty.util.internal.PromiseNotificationUtil;
31  import io.netty.util.internal.SystemPropertyUtil;
32  import io.netty.util.internal.logging.InternalLogger;
33  import io.netty.util.internal.logging.InternalLoggerFactory;
34  
35  import java.nio.ByteBuffer;
36  import java.nio.channels.ClosedChannelException;
37  import java.util.ArrayList;
38  import java.util.Arrays;
39  import java.util.List;
40  import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
41  import java.util.concurrent.atomic.AtomicLongFieldUpdater;
42  
43  import static java.lang.Math.min;
44  
45  /**
46   * (Transport implementors only) an internal data structure used by {@link AbstractChannel} to store its pending
47   * outbound write requests.
48   * <p>
49   * All methods must be called by a transport implementation from an I/O thread, except the following ones:
50   * <ul>
51   * <li>{@link #isWritable()}</li>
52   * <li>{@link #getUserDefinedWritability(int)} and {@link #setUserDefinedWritability(int, boolean)}</li>
53   * </ul>
54   * </p>
55   */
56  public final class ChannelOutboundBuffer {
57      // Assuming a 64-bit JVM:
58      //  - 16 bytes object header
59      //  - 6 reference fields
60      //  - 2 long fields
61      //  - 2 int fields
62      //  - 1 boolean field
63      //  - padding
64      static final int CHANNEL_OUTBOUND_BUFFER_ENTRY_OVERHEAD =
65              SystemPropertyUtil.getInt("io.netty.transport.outboundBufferEntrySizeOverhead", 96);
66  
67      private static final InternalLogger logger = InternalLoggerFactory.getInstance(ChannelOutboundBuffer.class);
68  
69      private static final FastThreadLocal<ByteBuffer[]> NIO_BUFFERS = new FastThreadLocal<ByteBuffer[]>() {
70          @Override
71          protected ByteBuffer[] initialValue() throws Exception {
72              return new ByteBuffer[1024];
73          }
74      };
75  
76      private final Channel channel;
77  
78      // Entry(flushedEntry) --> ... Entry(unflushedEntry) --> ... Entry(tailEntry)
79      //
80      // The Entry that is the first in the linked-list structure that was flushed
81      private Entry flushedEntry;
82      // The Entry which is the first unflushed in the linked-list structure
83      private Entry unflushedEntry;
84      // The Entry which represents the tail of the buffer
85      private Entry tailEntry;
86      // The number of flushed entries that are not written yet
87      private int flushed;
88  
89      private int nioBufferCount;
90      private long nioBufferSize;
91  
92      private boolean inFail;
93  
94      private static final AtomicLongFieldUpdater<ChannelOutboundBuffer> TOTAL_PENDING_SIZE_UPDATER =
95              AtomicLongFieldUpdater.newUpdater(ChannelOutboundBuffer.class, "totalPendingSize");
96  
97      @SuppressWarnings("UnusedDeclaration")
98      private volatile long totalPendingSize;
99  
100     private static final AtomicIntegerFieldUpdater<ChannelOutboundBuffer> UNWRITABLE_UPDATER =
101             AtomicIntegerFieldUpdater.newUpdater(ChannelOutboundBuffer.class, "unwritable");
102 
103     @SuppressWarnings("UnusedDeclaration")
104     private volatile int unwritable;
105 
106     private volatile Runnable fireChannelWritabilityChangedTask;
107 
108     ChannelOutboundBuffer(AbstractChannel channel) {
109         this.channel = channel;
110     }
111 
112     /**
113      * Add given message to this {@link ChannelOutboundBuffer}. The given {@link ChannelPromise} will be notified once
114      * the message was written.
115      */
116     public void addMessage(Object msg, int size, ChannelPromise promise) {
117         Entry entry = Entry.newInstance(msg, size, total(msg), promise);
118         if (tailEntry == null) {
119             flushedEntry = null;
120         } else {
121             Entry tail = tailEntry;
122             tail.next = entry;
123         }
124         tailEntry = entry;
125         if (unflushedEntry == null) {
126             unflushedEntry = entry;
127         }
128 
129         // Touch the message to make it easier to debug buffer leaks.
130 
131         // this save both checking against the ReferenceCounted interface
132         // and makes better use of virtual calls vs interface ones
133         if (msg instanceof AbstractReferenceCountedByteBuf) {
134             ((AbstractReferenceCountedByteBuf) msg).touch();
135         } else {
136             ReferenceCountUtil.touch(msg);
137         }
138 
139         // increment pending bytes after adding message to the unflushed arrays.
140         // See https://github.com/netty/netty/issues/1619
141         incrementPendingOutboundBytes(entry.pendingSize, false);
142     }
143 
144     /**
145      * Add a flush to this {@link ChannelOutboundBuffer}. This means all previous added messages are marked as flushed
146      * and so you will be able to handle them.
147      */
148     public void addFlush() {
149         // There is no need to process all entries if there was already a flush before and no new messages
150         // where added in the meantime.
151         //
152         // See https://github.com/netty/netty/issues/2577
153         Entry entry = unflushedEntry;
154         if (entry != null) {
155             if (flushedEntry == null) {
156                 // there is no flushedEntry yet, so start with the entry
157                 flushedEntry = entry;
158             }
159             long cancelledBytes = 0;
160             List<Object> cancelledMsgs = null;
161             do {
162                 flushed ++;
163                 if (!entry.promise.setUncancellable()) {
164                     // Was cancelled so make sure we free up memory. The message is released only once this buffer is
165                     // consistent again, as releasing it may run arbitrary user code.
166                     if (cancelledMsgs == null) {
167                         cancelledMsgs = new ArrayList<Object>(2);
168                     }
169                     cancelledMsgs.add(entry.msg);
170                     cancelledBytes += entry.cancel();
171                 }
172                 entry = entry.next;
173             } while (entry != null);
174 
175             // All flushed so reset unflushedEntry
176             unflushedEntry = null;
177 
178             if (cancelledMsgs != null) {
179                 for (int i = 0; i < cancelledMsgs.size(); i++) {
180                     ReferenceCountUtil.safeRelease(cancelledMsgs.get(i));
181                 }
182                 // Notify about the freed bytes only now. This may fire channelWritabilityChanged(...) synchronously,
183                 // and a handler may write, flush or close the channel from there, so this buffer must be consistent
184                 // first.
185                 decrementPendingOutboundBytes(cancelledBytes, false, true);
186             }
187         }
188     }
189 
190     /**
191      * Increment the pending bytes which will be written at some point.
192      * This method is thread-safe!
193      */
194     void incrementPendingOutboundBytes(long size) {
195         incrementPendingOutboundBytes(size, true);
196     }
197 
198     private void incrementPendingOutboundBytes(long size, boolean invokeLater) {
199         if (size == 0) {
200             return;
201         }
202 
203         long newWriteBufferSize = TOTAL_PENDING_SIZE_UPDATER.addAndGet(this, size);
204         if (newWriteBufferSize > channel.config().getWriteBufferHighWaterMark()) {
205             setUnwritable(invokeLater);
206         }
207     }
208 
209     /**
210      * Decrement the pending bytes which will be written at some point.
211      * This method is thread-safe!
212      */
213     void decrementPendingOutboundBytes(long size) {
214         decrementPendingOutboundBytes(size, true, true);
215     }
216 
217     private void decrementPendingOutboundBytes(long size, boolean invokeLater, boolean notifyWritability) {
218         if (size == 0) {
219             return;
220         }
221 
222         long newWriteBufferSize = TOTAL_PENDING_SIZE_UPDATER.addAndGet(this, -size);
223         if (notifyWritability && newWriteBufferSize < channel.config().getWriteBufferLowWaterMark()) {
224             setWritable(invokeLater);
225         }
226     }
227 
228     private static long total(Object msg) {
229         if (msg instanceof ByteBuf) {
230             return ((ByteBuf) msg).readableBytes();
231         }
232         if (msg instanceof FileRegion) {
233             return ((FileRegion) msg).count();
234         }
235         if (msg instanceof ByteBufHolder) {
236             return ((ByteBufHolder) msg).content().readableBytes();
237         }
238         return -1;
239     }
240 
241     /**
242      * Return the current message to write or {@code null} if nothing was flushed before and so is ready to be written.
243      */
244     public Object current() {
245         Entry entry = flushedEntry;
246         if (entry == null) {
247             return null;
248         }
249 
250         return entry.msg;
251     }
252 
253     /**
254      * Return the current message flush progress.
255      * @return {@code 0} if nothing was flushed before for the current message or there is no current message
256      */
257     public long currentProgress() {
258         Entry entry = flushedEntry;
259         if (entry == null) {
260             return 0;
261         }
262         return entry.progress;
263     }
264 
265     /**
266      * Notify the {@link ChannelPromise} of the current message about writing progress.
267      */
268     public void progress(long amount) {
269         Entry e = flushedEntry;
270         assert e != null;
271         ChannelPromise p = e.promise;
272         long progress = e.progress + amount;
273         e.progress = progress;
274         assert p != null;
275         final Class<?> promiseClass = p.getClass();
276         // fast-path to save O(n) ChannelProgressivePromise's type check on OpenJDK
277         if (promiseClass == VoidChannelPromise.class || promiseClass == DefaultChannelPromise.class) {
278             return;
279         }
280         // this is going to save from type pollution due to https://bugs.openjdk.org/browse/JDK-8180450
281         if (p instanceof DefaultChannelProgressivePromise) {
282             ((DefaultChannelProgressivePromise) p).tryProgress(progress, e.total);
283         } else if (p instanceof ChannelProgressivePromise) {
284             ((ChannelProgressivePromise) p).tryProgress(progress, e.total);
285         }
286     }
287 
288     /**
289      * Will remove the current message, mark its {@link ChannelPromise} as success and return {@code true}. If no
290      * flushed message exists at the time this method is called it will return {@code false} to signal that no more
291      * messages are ready to be handled.
292      */
293     public boolean remove() {
294         Entry e = flushedEntry;
295         if (e == null) {
296             clearNioBuffers();
297             return false;
298         }
299         Object msg = e.msg;
300 
301         ChannelPromise promise = e.promise;
302         int size = e.pendingSize;
303 
304         removeEntry(e);
305 
306         // only release message, notify and decrement if it was not canceled before.
307         if (!e.cancelled) {
308             // this save both checking against the ReferenceCounted interface
309             // and makes better use of virtual calls vs interface ones
310             if (msg instanceof AbstractReferenceCountedByteBuf) {
311                 try {
312                     // release now as it is flushed.
313                     ((AbstractReferenceCountedByteBuf) msg).release();
314                 } catch (Throwable t) {
315                     logger.warn("Failed to release a ByteBuf: {}", msg, t);
316                 }
317             } else {
318                 ReferenceCountUtil.safeRelease(msg);
319             }
320             safeSuccess(promise);
321             decrementPendingOutboundBytes(size, false, true);
322         }
323 
324         // recycle the entry
325         e.unguardedRecycle();
326 
327         return true;
328     }
329 
330     /**
331      * Will remove the current message, mark its {@link ChannelPromise} as failure using the given {@link Throwable}
332      * and return {@code true}. If no   flushed message exists at the time this method is called it will return
333      * {@code false} to signal that no more messages are ready to be handled.
334      */
335     public boolean remove(Throwable cause) {
336         return remove0(cause, true);
337     }
338 
339     private boolean remove0(Throwable cause, boolean notifyWritability) {
340         Entry e = flushedEntry;
341         if (e == null) {
342             clearNioBuffers();
343             return false;
344         }
345         Object msg = e.msg;
346 
347         ChannelPromise promise = e.promise;
348         int size = e.pendingSize;
349 
350         removeEntry(e);
351 
352         if (!e.cancelled) {
353             // only release message, fail and decrement if it was not canceled before.
354             ReferenceCountUtil.safeRelease(msg);
355 
356             safeFail(promise, cause);
357             decrementPendingOutboundBytes(size, false, notifyWritability);
358         }
359 
360         // recycle the entry
361         e.unguardedRecycle();
362 
363         return true;
364     }
365 
366     private void removeEntry(Entry e) {
367         if (-- flushed == 0) {
368             // processed everything
369             flushedEntry = null;
370             if (e == tailEntry) {
371                 tailEntry = null;
372                 unflushedEntry = null;
373             }
374         } else {
375             flushedEntry = e.next;
376         }
377     }
378 
379     /**
380      * Removes the fully written entries and update the reader index of the partially written entry.
381      * This operation assumes all messages in this buffer is {@link ByteBuf}.
382      */
383     public void removeBytes(long writtenBytes) {
384         for (;;) {
385             Object msg = current();
386             if (!(msg instanceof ByteBuf)) {
387                 assert writtenBytes == 0;
388                 break;
389             }
390 
391             final ByteBuf buf = (ByteBuf) msg;
392             final int readerIndex = buf.readerIndex();
393             final int readableBytes = buf.writerIndex() - readerIndex;
394 
395             if (readableBytes <= writtenBytes) {
396                 if (writtenBytes != 0) {
397                     progress(readableBytes);
398                     writtenBytes -= readableBytes;
399                 }
400                 remove();
401             } else { // readableBytes > writtenBytes
402                 if (writtenBytes != 0) {
403                     buf.readerIndex(readerIndex + (int) writtenBytes);
404                     progress(writtenBytes);
405                 }
406                 break;
407             }
408         }
409         clearNioBuffers();
410     }
411 
412     // Clear all ByteBuffer from the array so these can be GC'ed.
413     // See https://github.com/netty/netty/issues/3837
414     private void clearNioBuffers() {
415         int count = nioBufferCount;
416         if (count > 0) {
417             nioBufferCount = 0;
418             Arrays.fill(NIO_BUFFERS.get(), 0, count, null);
419         }
420     }
421 
422     /**
423      * Returns an array of direct NIO buffers if the currently pending messages are made of {@link ByteBuf} only.
424      * {@link #nioBufferCount()} and {@link #nioBufferSize()} will return the number of NIO buffers in the returned
425      * array and the total number of readable bytes of the NIO buffers respectively.
426      * <p>
427      * Note that the returned array is reused and thus should not escape
428      * {@link AbstractChannel#doWrite(ChannelOutboundBuffer)}.
429      * Refer to {@link NioSocketChannel#doWrite(ChannelOutboundBuffer)} for an example.
430      * </p>
431      */
432     public ByteBuffer[] nioBuffers() {
433         return nioBuffers(Integer.MAX_VALUE, Integer.MAX_VALUE);
434     }
435 
436     /**
437      * Returns an array of direct NIO buffers if the currently pending messages are made of {@link ByteBuf} only.
438      * {@link #nioBufferCount()} and {@link #nioBufferSize()} will return the number of NIO buffers in the returned
439      * array and the total number of readable bytes of the NIO buffers respectively.
440      * <p>
441      * Note that the returned array is reused and thus should not escape
442      * {@link AbstractChannel#doWrite(ChannelOutboundBuffer)}.
443      * Refer to {@link NioSocketChannel#doWrite(ChannelOutboundBuffer)} for an example.
444      * </p>
445      * @param maxCount The maximum amount of buffers that will be added to the return value.
446      * @param maxBytes A hint toward the maximum number of bytes to include as part of the return value. Note that this
447      *                 value maybe exceeded because we make a best effort to include at least 1 {@link ByteBuffer}
448      *                 in the return value to ensure write progress is made.
449      */
450     public ByteBuffer[] nioBuffers(int maxCount, long maxBytes) {
451         assert maxCount > 0;
452         assert maxBytes > 0;
453         long nioBufferSize = 0;
454         int nioBufferCount = 0;
455         final InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
456         ByteBuffer[] nioBuffers = NIO_BUFFERS.get(threadLocalMap);
457         Entry entry = flushedEntry;
458         while (isFlushedEntry(entry) && entry.msg instanceof ByteBuf) {
459             if (!entry.cancelled) {
460                 ByteBuf buf = (ByteBuf) entry.msg;
461                 final int readerIndex = buf.readerIndex();
462                 final int readableBytes = buf.writerIndex() - readerIndex;
463 
464                 if (readableBytes > 0) {
465                     if (maxBytes - readableBytes < nioBufferSize && nioBufferCount != 0) {
466                         // If the nioBufferSize + readableBytes will overflow maxBytes, and there is at least one entry
467                         // we stop populate the ByteBuffer array. This is done for 2 reasons:
468                         // 1. bsd/osx don't allow to write more bytes then Integer.MAX_VALUE with one writev(...) call
469                         // and so will return 'EINVAL', which will raise an IOException. On Linux it may work depending
470                         // on the architecture and kernel but to be safe we also enforce the limit here.
471                         // 2. There is no sense in putting more data in the array than is likely to be accepted by the
472                         // OS.
473                         //
474                         // See also:
475                         // - https://www.freebsd.org/cgi/man.cgi?query=write&sektion=2
476                         // - https://linux.die.net//man/2/writev
477                         break;
478                     }
479                     nioBufferSize += readableBytes;
480                     int count = entry.count;
481                     if (count == -1) {
482                         //noinspection ConstantValueVariableUse
483                         entry.count = count = buf.nioBufferCount();
484                     }
485                     int neededSpace = min(maxCount, nioBufferCount + count);
486                     if (neededSpace > nioBuffers.length) {
487                         nioBuffers = expandNioBufferArray(nioBuffers, neededSpace, nioBufferCount);
488                         NIO_BUFFERS.set(threadLocalMap, nioBuffers);
489                     }
490                     if (count == 1) {
491                         ByteBuffer nioBuf = entry.buf;
492                         if (nioBuf == null) {
493                             // cache ByteBuffer as it may need to create a new ByteBuffer instance if its a
494                             // derived buffer
495                             entry.buf = nioBuf = buf.internalNioBuffer(readerIndex, readableBytes);
496                         }
497                         nioBuffers[nioBufferCount++] = nioBuf;
498                     } else {
499                         // The code exists in an extra method to ensure the method is not too big to inline as this
500                         // branch is not very likely to get hit very frequently.
501                         nioBufferCount = nioBuffers(entry, buf, nioBuffers, nioBufferCount, maxCount);
502                     }
503                     if (nioBufferCount >= maxCount) {
504                         break;
505                     }
506                 }
507             }
508             entry = entry.next;
509         }
510         this.nioBufferCount = nioBufferCount;
511         this.nioBufferSize = nioBufferSize;
512 
513         return nioBuffers;
514     }
515 
516     private static int nioBuffers(Entry entry, ByteBuf buf, ByteBuffer[] nioBuffers, int nioBufferCount, int maxCount) {
517         ByteBuffer[] nioBufs = entry.bufs;
518         if (nioBufs == null) {
519             // cached ByteBuffers as they may be expensive to create in terms
520             // of Object allocation
521             entry.bufs = nioBufs = buf.nioBuffers();
522         }
523         for (int i = 0; i < nioBufs.length && nioBufferCount < maxCount; ++i) {
524             ByteBuffer nioBuf = nioBufs[i];
525             if (nioBuf == null) {
526                 break;
527             } else if (!nioBuf.hasRemaining()) {
528                 continue;
529             }
530             nioBuffers[nioBufferCount++] = nioBuf;
531         }
532         return nioBufferCount;
533     }
534 
535     private static ByteBuffer[] expandNioBufferArray(ByteBuffer[] array, int neededSpace, int size) {
536         int newCapacity = array.length;
537         do {
538             // double capacity until it is big enough
539             // See https://github.com/netty/netty/issues/1890
540             newCapacity <<= 1;
541 
542             if (newCapacity < 0) {
543                 throw new IllegalStateException();
544             }
545 
546         } while (neededSpace > newCapacity);
547 
548         ByteBuffer[] newArray = new ByteBuffer[newCapacity];
549         System.arraycopy(array, 0, newArray, 0, size);
550 
551         return newArray;
552     }
553 
554     /**
555      * Returns the number of {@link ByteBuffer} that can be written out of the {@link ByteBuffer} array that was
556      * obtained via {@link #nioBuffers()}. This method <strong>MUST</strong> be called after {@link #nioBuffers()}
557      * was called.
558      */
559     public int nioBufferCount() {
560         return nioBufferCount;
561     }
562 
563     /**
564      * Returns the number of bytes that can be written out of the {@link ByteBuffer} array that was
565      * obtained via {@link #nioBuffers()}. This method <strong>MUST</strong> be called after {@link #nioBuffers()}
566      * was called.
567      */
568     public long nioBufferSize() {
569         return nioBufferSize;
570     }
571 
572     /**
573      * Returns {@code true} if and only if {@linkplain #totalPendingWriteBytes() the total number of pending bytes} did
574      * not exceed the write watermark of the {@link Channel} and
575      * no {@linkplain #setUserDefinedWritability(int, boolean) user-defined writability flag} has been set to
576      * {@code false}.
577      */
578     public boolean isWritable() {
579         return unwritable == 0;
580     }
581 
582     /**
583      * Returns {@code true} if and only if the user-defined writability flag at the specified index is set to
584      * {@code true}.
585      */
586     public boolean getUserDefinedWritability(int index) {
587         return (unwritable & writabilityMask(index)) == 0;
588     }
589 
590     /**
591      * Sets a user-defined writability flag at the specified index.
592      */
593     public void setUserDefinedWritability(int index, boolean writable) {
594         if (writable) {
595             setUserDefinedWritability(index);
596         } else {
597             clearUserDefinedWritability(index);
598         }
599     }
600 
601     private void setUserDefinedWritability(int index) {
602         final int mask = ~writabilityMask(index);
603         for (;;) {
604             final int oldValue = unwritable;
605             final int newValue = oldValue & mask;
606             if (UNWRITABLE_UPDATER.compareAndSet(this, oldValue, newValue)) {
607                 if (oldValue != 0 && newValue == 0) {
608                     fireChannelWritabilityChanged(true);
609                 }
610                 break;
611             }
612         }
613     }
614 
615     private void clearUserDefinedWritability(int index) {
616         final int mask = writabilityMask(index);
617         for (;;) {
618             final int oldValue = unwritable;
619             final int newValue = oldValue | mask;
620             if (UNWRITABLE_UPDATER.compareAndSet(this, oldValue, newValue)) {
621                 if (oldValue == 0 && newValue != 0) {
622                     fireChannelWritabilityChanged(true);
623                 }
624                 break;
625             }
626         }
627     }
628 
629     private static int writabilityMask(int index) {
630         if (index < 1 || index > 31) {
631             throw new IllegalArgumentException("index: " + index + " (expected: 1~31)");
632         }
633         return 1 << index;
634     }
635 
636     private void setWritable(boolean invokeLater) {
637         for (;;) {
638             final int oldValue = unwritable;
639             final int newValue = oldValue & ~1;
640             if (UNWRITABLE_UPDATER.compareAndSet(this, oldValue, newValue)) {
641                 if (oldValue != 0 && newValue == 0) {
642                     fireChannelWritabilityChanged(invokeLater);
643                 }
644                 break;
645             }
646         }
647     }
648 
649     private void setUnwritable(boolean invokeLater) {
650         for (;;) {
651             final int oldValue = unwritable;
652             final int newValue = oldValue | 1;
653             if (UNWRITABLE_UPDATER.compareAndSet(this, oldValue, newValue)) {
654                 if (oldValue == 0) {
655                     fireChannelWritabilityChanged(invokeLater);
656                 }
657                 break;
658             }
659         }
660     }
661 
662     private void fireChannelWritabilityChanged(boolean invokeLater) {
663         final ChannelPipeline pipeline = channel.pipeline();
664         if (invokeLater) {
665             Runnable task = fireChannelWritabilityChangedTask;
666             if (task == null) {
667                 fireChannelWritabilityChangedTask = task = new Runnable() {
668                     @Override
669                     public void run() {
670                         pipeline.fireChannelWritabilityChanged();
671                     }
672                 };
673             }
674             channel.eventLoop().execute(task);
675         } else {
676             pipeline.fireChannelWritabilityChanged();
677         }
678     }
679 
680     /**
681      * Returns the number of flushed messages in this {@link ChannelOutboundBuffer}.
682      */
683     public int size() {
684         return flushed;
685     }
686 
687     /**
688      * Returns {@code true} if there are flushed messages in this {@link ChannelOutboundBuffer} or {@code false}
689      * otherwise.
690      */
691     public boolean isEmpty() {
692         return flushed == 0;
693     }
694 
695     void failFlushed(Throwable cause, boolean notify) {
696         // Make sure that this method does not reenter.  A listener added to the current promise can be notified by the
697         // current thread in the tryFailure() call of the loop below, and the listener can trigger another fail() call
698         // indirectly (usually by closing the channel.)
699         //
700         // See https://github.com/netty/netty/issues/1501
701         if (inFail) {
702             return;
703         }
704 
705         try {
706             inFail = true;
707             for (;;) {
708                 if (!remove0(cause, notify)) {
709                     break;
710                 }
711             }
712         } finally {
713             inFail = false;
714         }
715     }
716 
717     void close(final Throwable cause, final boolean allowChannelOpen) {
718         if (inFail) {
719             channel.eventLoop().execute(new Runnable() {
720                 @Override
721                 public void run() {
722                     close(cause, allowChannelOpen);
723                 }
724             });
725             return;
726         }
727 
728         inFail = true;
729 
730         if (!allowChannelOpen && channel.isOpen()) {
731             throw new IllegalStateException("close() must be invoked after the channel is closed.");
732         }
733 
734         if (!isEmpty()) {
735             throw new IllegalStateException("close() must be invoked after all flushed writes are handled.");
736         }
737 
738         // Release all unflushed messages.
739         try {
740             Entry e = unflushedEntry;
741             while (e != null) {
742                 // Just decrease; do not trigger any events via decrementPendingOutboundBytes()
743                 int size = e.pendingSize;
744                 TOTAL_PENDING_SIZE_UPDATER.addAndGet(this, -size);
745 
746                 if (!e.cancelled) {
747                     ReferenceCountUtil.safeRelease(e.msg);
748                     safeFail(e.promise, cause);
749                 }
750                 e = e.unguardedRecycleAndGetNext();
751             }
752         } finally {
753             inFail = false;
754         }
755         clearNioBuffers();
756     }
757 
758     void close(ClosedChannelException cause) {
759         close(cause, false);
760     }
761 
762     private static void safeSuccess(ChannelPromise promise) {
763         // Only log if the given promise is not of type VoidChannelPromise as trySuccess(...) is expected to return
764         // false.
765         PromiseNotificationUtil.trySuccess(promise, null, promise instanceof VoidChannelPromise ? null : logger);
766     }
767 
768     private static void safeFail(ChannelPromise promise, Throwable cause) {
769         // Only log if the given promise is not of type VoidChannelPromise as tryFailure(...) is expected to return
770         // false.
771         PromiseNotificationUtil.tryFailure(promise, cause, promise instanceof VoidChannelPromise ? null : logger);
772     }
773 
774     @Deprecated
775     public void recycle() {
776         // NOOP
777     }
778 
779     public long totalPendingWriteBytes() {
780         return totalPendingSize;
781     }
782 
783     /**
784      * Get how many bytes can be written until {@link #isWritable()} returns {@code false}.
785      * This quantity will always be non-negative. If {@link #isWritable()} is {@code false} then 0.
786      */
787     public long bytesBeforeUnwritable() {
788         // +1 because writability doesn't change until the threshold is crossed (not equal to).
789         long bytes = channel.config().getWriteBufferHighWaterMark() - totalPendingSize + 1;
790         // If bytes is negative we know we are not writable, but if bytes is non-negative we have to check writability.
791         // Note that totalPendingSize and isWritable() use different volatile variables that are not synchronized
792         // together. totalPendingSize will be updated before isWritable().
793         return bytes > 0 && isWritable() ? bytes : 0;
794     }
795 
796     /**
797      * Get how many bytes must be drained from the underlying buffer until {@link #isWritable()} returns {@code true}.
798      * This quantity will always be non-negative. If {@link #isWritable()} is {@code true} then 0.
799      */
800     public long bytesBeforeWritable() {
801         // +1 because writability doesn't change until the threshold is crossed (not equal to).
802         long bytes = totalPendingSize - channel.config().getWriteBufferLowWaterMark() + 1;
803         // If bytes is negative we know we are writable, but if bytes is non-negative we have to check writability.
804         // Note that totalPendingSize and isWritable() use different volatile variables that are not synchronized
805         // together. totalPendingSize will be updated before isWritable().
806         return bytes <= 0 || isWritable() ? 0 : bytes;
807     }
808 
809     /**
810      * Call {@link MessageProcessor#processMessage(Object)} for each flushed message
811      * in this {@link ChannelOutboundBuffer} until {@link MessageProcessor#processMessage(Object)}
812      * returns {@code false} or there are no more flushed messages to process.
813      */
814     public void forEachFlushedMessage(MessageProcessor processor) throws Exception {
815         ObjectUtil.checkNotNull(processor, "processor");
816 
817         Entry entry = flushedEntry;
818         if (entry == null) {
819             return;
820         }
821 
822         do {
823             if (!entry.cancelled) {
824                 if (!processor.processMessage(entry.msg)) {
825                     return;
826                 }
827             }
828             entry = entry.next;
829         } while (isFlushedEntry(entry));
830     }
831 
832     private boolean isFlushedEntry(Entry e) {
833         return e != null && e != unflushedEntry;
834     }
835 
836     public interface MessageProcessor {
837         /**
838          * Will be called for each flushed message until it either there are no more flushed messages or this
839          * method returns {@code false}.
840          */
841         boolean processMessage(Object msg) throws Exception;
842     }
843 
844     static final class Entry {
845         private static final Recycler<Entry> RECYCLER = new Recycler<Entry>() {
846             @Override
847             protected Entry newObject(Handle<Entry> handle) {
848                 return new Entry(handle);
849             }
850         };
851 
852         private final EnhancedHandle<Entry> handle;
853         Entry next;
854         Object msg;
855         ByteBuffer[] bufs;
856         ByteBuffer buf;
857         ChannelPromise promise;
858         long progress;
859         long total;
860         int pendingSize;
861         int count = -1;
862         boolean cancelled;
863 
864         private Entry(Handle<Entry> handle) {
865             this.handle = (EnhancedHandle<Entry>) handle;
866         }
867 
868         static Entry newInstance(Object msg, int size, long total, ChannelPromise promise) {
869             Entry entry = RECYCLER.get();
870             entry.msg = msg;
871             entry.pendingSize = size + CHANNEL_OUTBOUND_BUFFER_ENTRY_OVERHEAD;
872             entry.total = total;
873             entry.promise = promise;
874             return entry;
875         }
876 
877         int cancel() {
878             if (!cancelled) {
879                 cancelled = true;
880                 int pSize = pendingSize;
881 
882                 // replace the message with an empty buffer, the caller is responsible for releasing it
883                 msg = Unpooled.EMPTY_BUFFER;
884 
885                 pendingSize = 0;
886                 total = 0;
887                 progress = 0;
888                 bufs = null;
889                 buf = null;
890                 return pSize;
891             }
892             return 0;
893         }
894 
895         void unguardedRecycle() {
896             next = null;
897             bufs = null;
898             buf = null;
899             msg = null;
900             promise = null;
901             progress = 0;
902             total = 0;
903             pendingSize = 0;
904             count = -1;
905             cancelled = false;
906             handle.unguardedRecycle(this);
907         }
908 
909         Entry unguardedRecycleAndGetNext() {
910             Entry next = this.next;
911             unguardedRecycle();
912             return next;
913         }
914     }
915 }