View Javadoc
1   /*
2    * Copyright 2016 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.kqueue;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.buffer.ByteBufAllocator;
20  import io.netty.channel.Channel;
21  import io.netty.channel.ChannelConfig;
22  import io.netty.channel.ChannelFuture;
23  import io.netty.channel.ChannelFutureListener;
24  import io.netty.channel.ChannelMetadata;
25  import io.netty.channel.ChannelOutboundBuffer;
26  import io.netty.channel.ChannelPipeline;
27  import io.netty.channel.ChannelPromise;
28  import io.netty.channel.DefaultFileRegion;
29  import io.netty.channel.EventLoop;
30  import io.netty.channel.FileRegion;
31  import io.netty.channel.internal.ChannelUtils;
32  import io.netty.channel.socket.DuplexChannel;
33  import io.netty.channel.unix.IovArray;
34  import io.netty.channel.unix.SocketWritableByteChannel;
35  import io.netty.channel.unix.UnixChannelUtil;
36  import io.netty.util.LeakPresenceDetector;
37  import io.netty.util.internal.StringUtil;
38  import io.netty.util.internal.UnstableApi;
39  import io.netty.util.internal.logging.InternalLogger;
40  import io.netty.util.internal.logging.InternalLoggerFactory;
41  
42  import java.io.IOException;
43  import java.net.SocketAddress;
44  import java.nio.ByteBuffer;
45  import java.nio.channels.WritableByteChannel;
46  import java.util.concurrent.Executor;
47  
48  import static io.netty.channel.internal.ChannelUtils.MAX_BYTES_PER_GATHERING_WRITE_ATTEMPTED_LOW_THRESHOLD;
49  import static io.netty.channel.internal.ChannelUtils.WRITE_STATUS_SNDBUF_FULL;
50  import static io.netty.util.internal.StringUtil.className;
51  
52  public abstract class AbstractKQueueStreamChannel extends AbstractKQueueChannel implements DuplexChannel {
53      private static final InternalLogger logger = InternalLoggerFactory.getInstance(AbstractKQueueStreamChannel.class);
54      private static final ChannelMetadata METADATA = new ChannelMetadata(false, 16);
55      private static final String EXPECTED_TYPES =
56              " (expected: " + StringUtil.simpleClassName(ByteBuf.class) + ", " +
57                      StringUtil.simpleClassName(DefaultFileRegion.class) + ')';
58      private WritableByteChannel byteChannel;
59      private final Runnable flushTask = new Runnable() {
60          @Override
61          public void run() {
62              // Calling flush0 directly to ensure we not try to flush messages that were added via write(...) in the
63              // meantime.
64              ((AbstractKQueueUnsafe) unsafe()).flush0();
65          }
66      };
67  
68      AbstractKQueueStreamChannel(Channel parent, BsdSocket fd, boolean active) {
69          super(parent, fd, active);
70      }
71  
72      AbstractKQueueStreamChannel(Channel parent, BsdSocket fd, SocketAddress remote) {
73          super(parent, fd, remote);
74      }
75  
76      AbstractKQueueStreamChannel(BsdSocket fd) {
77          this(null, fd, isSoErrorZero(fd));
78      }
79  
80      @Override
81      protected AbstractKQueueUnsafe newUnsafe() {
82          return new KQueueStreamUnsafe();
83      }
84  
85      @Override
86      public ChannelMetadata metadata() {
87          return METADATA;
88      }
89  
90      /**
91       * Write bytes form the given {@link ByteBuf} to the underlying {@link java.nio.channels.Channel}.
92       * @param in the collection which contains objects to write.
93       * @param buf the {@link ByteBuf} from which the bytes should be written
94       * @return The value that should be decremented from the write quantum which starts at
95       * {@link ChannelConfig#getWriteSpinCount()}. The typical use cases are as follows:
96       * <ul>
97       *     <li>0 - if no write was attempted. This is appropriate if an empty {@link ByteBuf} (or other empty content)
98       *     is encountered</li>
99       *     <li>1 - if a single call to write data was made to the OS</li>
100      *     <li>{@link ChannelUtils#WRITE_STATUS_SNDBUF_FULL} - if an attempt to write data was made to the OS, but no
101      *     data was accepted</li>
102      * </ul>
103      */
104     private int writeBytes(ChannelOutboundBuffer in, ByteBuf buf) throws Exception {
105         int readableBytes = buf.readableBytes();
106         if (readableBytes == 0) {
107             in.remove();
108             return 0;
109         }
110 
111         if (buf.hasMemoryAddress() || buf.nioBufferCount() == 1) {
112             return doWriteBytes(in, buf);
113         } else {
114             ByteBuffer[] nioBuffers = buf.nioBuffers();
115             return writeBytesMultiple(in, nioBuffers, nioBuffers.length, readableBytes,
116                     config().getMaxBytesPerGatheringWrite());
117         }
118     }
119 
120     private void adjustMaxBytesPerGatheringWrite(long attempted, long written, long oldMaxBytesPerGatheringWrite) {
121         // By default we track the SO_SNDBUF when ever it is explicitly set. However some OSes may dynamically change
122         // SO_SNDBUF (and other characteristics that determine how much data can be written at once) so we should try
123         // make a best effort to adjust as OS behavior changes.
124         if (attempted == written) {
125             if (attempted << 1 > oldMaxBytesPerGatheringWrite) {
126                 config().setMaxBytesPerGatheringWrite(attempted << 1);
127             }
128         } else if (attempted > MAX_BYTES_PER_GATHERING_WRITE_ATTEMPTED_LOW_THRESHOLD && written < attempted >>> 1) {
129             config().setMaxBytesPerGatheringWrite(attempted >>> 1);
130         }
131     }
132 
133     /**
134      * Write multiple bytes via {@link IovArray}.
135      * @param in the collection which contains objects to write.
136      * @param array The array which contains the content to write.
137      * @return The value that should be decremented from the write quantum which starts at
138      * {@link ChannelConfig#getWriteSpinCount()}. The typical use cases are as follows:
139      * <ul>
140      *     <li>0 - if no write was attempted. This is appropriate if an empty {@link ByteBuf} (or other empty content)
141      *     is encountered</li>
142      *     <li>1 - if a single call to write data was made to the OS</li>
143      *     <li>{@link ChannelUtils#WRITE_STATUS_SNDBUF_FULL} - if an attempt to write data was made to the OS, but
144      *     no data was accepted</li>
145      * </ul>
146      * @throws IOException If an I/O exception occurs during write.
147      */
148     private int writeBytesMultiple(ChannelOutboundBuffer in, IovArray array) throws IOException {
149         final long expectedWrittenBytes = array.size();
150         assert expectedWrittenBytes != 0;
151         final int cnt = array.count();
152         assert cnt != 0;
153 
154         final long localWrittenBytes = socket.writevAddresses(array.memoryAddress(0), cnt);
155         if (localWrittenBytes > 0) {
156             adjustMaxBytesPerGatheringWrite(expectedWrittenBytes, localWrittenBytes, array.maxBytes());
157             in.removeBytes(localWrittenBytes);
158             return 1;
159         }
160         return WRITE_STATUS_SNDBUF_FULL;
161     }
162 
163     /**
164      * Write multiple bytes via {@link ByteBuffer} array.
165      * @param in the collection which contains objects to write.
166      * @param nioBuffers The buffers to write.
167      * @param nioBufferCnt The number of buffers to write.
168      * @param expectedWrittenBytes The number of bytes we expect to write.
169      * @param maxBytesPerGatheringWrite The maximum number of bytes we should attempt to write.
170      * @return The value that should be decremented from the write quantum which starts at
171      * {@link ChannelConfig#getWriteSpinCount()}. The typical use cases are as follows:
172      * <ul>
173      *     <li>0 - if no write was attempted. This is appropriate if an empty {@link ByteBuf} (or other empty content)
174      *     is encountered</li>
175      *     <li>1 - if a single call to write data was made to the OS</li>
176      *     <li>{@link ChannelUtils#WRITE_STATUS_SNDBUF_FULL} - if an attempt to write data was made to the OS, but
177      *     no data was accepted</li>
178      * </ul>
179      * @throws IOException If an I/O exception occurs during write.
180      */
181     private int writeBytesMultiple(
182             ChannelOutboundBuffer in, ByteBuffer[] nioBuffers, int nioBufferCnt, long expectedWrittenBytes,
183             long maxBytesPerGatheringWrite) throws IOException {
184         assert expectedWrittenBytes != 0;
185         if (expectedWrittenBytes > maxBytesPerGatheringWrite) {
186             expectedWrittenBytes = maxBytesPerGatheringWrite;
187         }
188 
189         final long localWrittenBytes = socket.writev(nioBuffers, 0, nioBufferCnt, expectedWrittenBytes);
190         if (localWrittenBytes > 0) {
191             adjustMaxBytesPerGatheringWrite(expectedWrittenBytes, localWrittenBytes, maxBytesPerGatheringWrite);
192             in.removeBytes(localWrittenBytes);
193             return 1;
194         }
195         return WRITE_STATUS_SNDBUF_FULL;
196     }
197 
198     /**
199      * Write a {@link DefaultFileRegion}
200      * @param in the collection which contains objects to write.
201      * @param region the {@link DefaultFileRegion} from which the bytes should be written
202      * @return The value that should be decremented from the write quantum which starts at
203      * {@link ChannelConfig#getWriteSpinCount()}. The typical use cases are as follows:
204      * <ul>
205      *     <li>0 - if no write was attempted. This is appropriate if an empty {@link ByteBuf} (or other empty content)
206      *     is encountered</li>
207      *     <li>1 - if a single call to write data was made to the OS</li>
208      *     <li>{@link ChannelUtils#WRITE_STATUS_SNDBUF_FULL} - if an attempt to write data was made to the OS, but
209      *     no data was accepted</li>
210      * </ul>
211      */
212     private int writeDefaultFileRegion(ChannelOutboundBuffer in, DefaultFileRegion region) throws Exception {
213         final long regionCount = region.count();
214         final long offset = region.transferred();
215 
216         if (offset >= regionCount) {
217             in.remove();
218             return 0;
219         }
220 
221         final long flushedAmount = socket.sendFile(region, region.position(), offset, regionCount - offset);
222         if (flushedAmount > 0) {
223             in.progress(flushedAmount);
224             if (region.transferred() >= regionCount) {
225                 in.remove();
226             }
227             return 1;
228         } else if (flushedAmount == 0) {
229             validateFileRegion(region, offset);
230         }
231         return WRITE_STATUS_SNDBUF_FULL;
232     }
233 
234     /**
235      * Write a {@link FileRegion}
236      * @param in the collection which contains objects to write.
237      * @param region the {@link FileRegion} from which the bytes should be written
238      * @return The value that should be decremented from the write quantum which starts at
239      * {@link ChannelConfig#getWriteSpinCount()}. The typical use cases are as follows:
240      * <ul>
241      *     <li>0 - if no write was attempted. This is appropriate if an empty {@link ByteBuf} (or other empty content)
242      *     is encountered</li>
243      *     <li>1 - if a single call to write data was made to the OS</li>
244      *     <li>{@link ChannelUtils#WRITE_STATUS_SNDBUF_FULL} - if an attempt to write data was made to the OS, but no
245      *     data was accepted</li>
246      * </ul>
247      */
248     private int writeFileRegion(ChannelOutboundBuffer in, FileRegion region) throws Exception {
249         if (region.transferred() >= region.count()) {
250             in.remove();
251             return 0;
252         }
253 
254         if (byteChannel == null) {
255             byteChannel = new KQueueSocketWritableByteChannel();
256         }
257         final long flushedAmount = region.transferTo(byteChannel, region.transferred());
258         if (flushedAmount > 0) {
259             in.progress(flushedAmount);
260             if (region.transferred() >= region.count()) {
261                 in.remove();
262             }
263             return 1;
264         }
265         return WRITE_STATUS_SNDBUF_FULL;
266     }
267 
268     @Override
269     protected void doWrite(ChannelOutboundBuffer in) throws Exception {
270         // Don't write the initial data that was sent by TCP_FASTOPEN_CONNECT a second time.
271         removeFastOpenData();
272         int writeSpinCount = config().getWriteSpinCount();
273         do {
274             final int msgCount = in.size();
275             // Do gathering write if the outbound buffer entries start with more than one ByteBuf.
276             if (msgCount > 1 && in.current() instanceof ByteBuf) {
277                 writeSpinCount -= doWriteMultiple(in);
278             } else if (msgCount == 0) {
279                 // Wrote all messages.
280                 writeFilter(false);
281                 // Return here so we don't set the WRITE flag.
282                 return;
283             } else { // msgCount == 1
284                 writeSpinCount -= doWriteSingle(in);
285             }
286 
287             // We do not break the loop here even if the outbound buffer was flushed completely,
288             // because a user might have triggered another write and flush when we notify his or her
289             // listeners.
290         } while (writeSpinCount > 0);
291 
292         if (writeSpinCount == 0) {
293             // It is possible that we have set the write filter, woken up by KQUEUE because the socket is writable, and
294             // then use our write quantum. In this case we no longer want to set the write filter because the socket is
295             // still writable (as far as we know). We will find out next time we attempt to write if the socket is
296             // writable and set the write filter if necessary.
297             writeFilter(false);
298 
299             // We used our writeSpin quantum, and should try to write again later.
300             eventLoop().execute(flushTask);
301         } else {
302             // Underlying descriptor can not accept all data currently, so set the WRITE flag to be woken up
303             // when it can accept more data.
304             writeFilter(true);
305         }
306     }
307 
308     /**
309      * Attempt to write a single object.
310      * @param in the collection which contains objects to write.
311      * @return The value that should be decremented from the write quantum which starts at
312      * {@link ChannelConfig#getWriteSpinCount()}. The typical use cases are as follows:
313      * <ul>
314      *     <li>0 - if no write was attempted. This is appropriate if an empty {@link ByteBuf} (or other empty content)
315      *     is encountered</li>
316      *     <li>1 - if a single call to write data was made to the OS</li>
317      *     <li>{@link ChannelUtils#WRITE_STATUS_SNDBUF_FULL} - if an attempt to write data was made to the OS, but no
318      *     data was accepted</li>
319      * </ul>
320      * @throws Exception If an I/O error occurs.
321      */
322     protected int doWriteSingle(ChannelOutboundBuffer in) throws Exception {
323         // The outbound buffer contains only one message or it contains a file region.
324         Object msg = in.current();
325         if (msg instanceof ByteBuf) {
326             return writeBytes(in, (ByteBuf) msg);
327         } else if (msg instanceof DefaultFileRegion) {
328             return writeDefaultFileRegion(in, (DefaultFileRegion) msg);
329         } else if (msg instanceof FileRegion) {
330             return writeFileRegion(in, (FileRegion) msg);
331         } else {
332             // Should never reach here.
333             throw new Error("Unexpected message type: " + className(msg));
334         }
335     }
336 
337     /**
338      * Attempt to write multiple {@link ByteBuf} objects.
339      * @param in the collection which contains objects to write.
340      * @return The value that should be decremented from the write quantum which starts at
341      * {@link ChannelConfig#getWriteSpinCount()}. The typical use cases are as follows:
342      * <ul>
343      *     <li>0 - if no write was attempted. This is appropriate if an empty {@link ByteBuf} (or other empty content)
344      *     is encountered</li>
345      *     <li>1 - if a single call to write data was made to the OS</li>
346      *     <li>{@link ChannelUtils#WRITE_STATUS_SNDBUF_FULL} - if an attempt to write data was made to the OS, but no
347      *     data was accepted</li>
348      * </ul>
349      * @throws Exception If an I/O error occurs.
350      */
351     private int doWriteMultiple(ChannelOutboundBuffer in) throws Exception {
352         final long maxBytesPerGatheringWrite = config().getMaxBytesPerGatheringWrite();
353         IovArray array = ((NativeArrays) registration().attachment()).cleanIovArray();
354         array.maxBytes(maxBytesPerGatheringWrite);
355         in.forEachFlushedMessage(array);
356 
357         if (array.count() >= 1) {
358             // TODO: Handle the case where cnt == 1 specially.
359             return writeBytesMultiple(in, array);
360         }
361         // cnt == 0, which means the outbound buffer contained empty buffers only.
362         in.removeBytes(0);
363         return 0;
364     }
365 
366     @Override
367     protected Object filterOutboundMessage(Object msg) {
368         if (msg instanceof ByteBuf) {
369             ByteBuf buf = (ByteBuf) msg;
370             return UnixChannelUtil.isBufferCopyNeededForWrite(buf)? newDirectBuffer(buf) : buf;
371         }
372 
373         if (msg instanceof FileRegion) {
374             return msg;
375         }
376 
377         throw new UnsupportedOperationException(
378                 "unsupported message type: " + StringUtil.simpleClassName(msg) + EXPECTED_TYPES);
379     }
380 
381     @UnstableApi
382     @Override
383     protected final void doShutdownOutput() throws Exception {
384         // The initial data that was sent by TCP_FASTOPEN_CONNECT must not be failed when shutting down the output.
385         removeFastOpenData();
386         socket.shutdown(false, true);
387     }
388 
389     @Override
390     public boolean isOutputShutdown() {
391         return socket.isOutputShutdown();
392     }
393 
394     @Override
395     public boolean isInputShutdown() {
396         return socket.isInputShutdown();
397     }
398 
399     @Override
400     public boolean isShutdown() {
401         return socket.isShutdown();
402     }
403 
404     @Override
405     public ChannelFuture shutdownOutput() {
406         return shutdownOutput(newPromise());
407     }
408 
409     @Override
410     public ChannelFuture shutdownOutput(final ChannelPromise promise) {
411         EventLoop loop = eventLoop();
412         if (loop.inEventLoop()) {
413             ((AbstractUnsafe) unsafe()).shutdownOutput(promise);
414         } else {
415             loop.execute(new Runnable() {
416                 @Override
417                 public void run() {
418                     ((AbstractUnsafe) unsafe()).shutdownOutput(promise);
419                 }
420             });
421         }
422         return promise;
423     }
424 
425     @Override
426     public ChannelFuture shutdownInput() {
427         return shutdownInput(newPromise());
428     }
429 
430     @Override
431     public ChannelFuture shutdownInput(final ChannelPromise promise) {
432         EventLoop loop = eventLoop();
433         if (loop.inEventLoop()) {
434             shutdownInput0(promise);
435         } else {
436             loop.execute(new Runnable() {
437                 @Override
438                 public void run() {
439                     shutdownInput0(promise);
440                 }
441             });
442         }
443         return promise;
444     }
445 
446     private void shutdownInput0(ChannelPromise promise) {
447         try {
448             socket.shutdown(true, false);
449         } catch (Throwable cause) {
450             promise.setFailure(cause);
451             return;
452         }
453         promise.setSuccess();
454     }
455 
456     @Override
457     public ChannelFuture shutdown() {
458         return shutdown(newPromise());
459     }
460 
461     @Override
462     public ChannelFuture shutdown(final ChannelPromise promise) {
463         ChannelFuture shutdownOutputFuture = shutdownOutput();
464         if (shutdownOutputFuture.isDone()) {
465             shutdownOutputDone(shutdownOutputFuture, promise);
466         } else {
467             shutdownOutputFuture.addListener(new ChannelFutureListener() {
468                 @Override
469                 public void operationComplete(final ChannelFuture shutdownOutputFuture) throws Exception {
470                     shutdownOutputDone(shutdownOutputFuture, promise);
471                 }
472             });
473         }
474         return promise;
475     }
476 
477     private void shutdownOutputDone(final ChannelFuture shutdownOutputFuture, final ChannelPromise promise) {
478         ChannelFuture shutdownInputFuture = shutdownInput();
479         if (shutdownInputFuture.isDone()) {
480             shutdownDone(shutdownOutputFuture, shutdownInputFuture, promise);
481         } else {
482             shutdownInputFuture.addListener(new ChannelFutureListener() {
483                 @Override
484                 public void operationComplete(ChannelFuture shutdownInputFuture) throws Exception {
485                     shutdownDone(shutdownOutputFuture, shutdownInputFuture, promise);
486                 }
487             });
488         }
489     }
490 
491     private static void shutdownDone(ChannelFuture shutdownOutputFuture,
492                                      ChannelFuture shutdownInputFuture,
493                                      ChannelPromise promise) {
494         Throwable shutdownOutputCause = shutdownOutputFuture.cause();
495         Throwable shutdownInputCause = shutdownInputFuture.cause();
496         if (shutdownOutputCause != null) {
497             if (shutdownInputCause != null) {
498                 logger.debug("Exception suppressed because a previous exception occurred.",
499                         shutdownInputCause);
500             }
501             promise.setFailure(shutdownOutputCause);
502         } else if (shutdownInputCause != null) {
503             promise.setFailure(shutdownInputCause);
504         } else {
505             promise.setSuccess();
506         }
507     }
508 
509     class KQueueStreamUnsafe extends AbstractKQueueUnsafe {
510         @Override
511         protected Executor prepareToClose() {
512             // The initial data that was sent by TCP_FASTOPEN_CONNECT must not be failed when closing.
513             removeFastOpenData();
514             return super.prepareToClose();
515         }
516 
517         @Override
518         void readReady(final KQueueRecvByteAllocatorHandle allocHandle) {
519             final ChannelConfig config = config();
520             if (shouldBreakReadReady(config)) {
521                 clearReadFilter0();
522                 return;
523             }
524             final ChannelPipeline pipeline = pipeline();
525             final ByteBufAllocator allocator = config.getAllocator();
526             allocHandle.reset(config);
527 
528             ByteBuf byteBuf = null;
529             boolean close = false;
530             try {
531                 do {
532                     // we use a direct buffer here as the native implementations only be able
533                     // to handle direct buffers.
534                     byteBuf = allocHandle.allocate(allocator);
535                     allocHandle.lastBytesRead(doReadBytes(byteBuf));
536                     if (allocHandle.lastBytesRead() <= 0) {
537                         // nothing was read, release the buffer.
538                         byteBuf.release();
539                         byteBuf = null;
540                         close = allocHandle.lastBytesRead() < 0;
541                         if (close) {
542                             // There is nothing left to read as we received an EOF.
543                             readPending = false;
544                         }
545                         break;
546                     }
547                     allocHandle.incMessagesRead(1);
548                     readPending = false;
549                     pipeline.fireChannelRead(byteBuf);
550                     byteBuf = null;
551 
552                     if (shouldBreakReadReady(config)) {
553                         // We need to do this for two reasons:
554                         //
555                         // - If the input was shutdown in between (which may be the case when the user did it in the
556                         //   fireChannelRead(...) method we should not try to read again to not produce any
557                         //   miss-leading exceptions.
558                         //
559                         // - If the user closes the channel we need to ensure we not try to read from it again as
560                         //   the filedescriptor may be re-used already by the OS if the system is handling a lot of
561                         //   concurrent connections and so needs a lot of filedescriptors. If not do this we risk
562                         //   reading data from a filedescriptor that belongs to another socket then the socket that
563                         //   was "wrapped" by this Channel implementation.
564                         break;
565                     }
566                 } while (allocHandle.continueReading());
567 
568                 allocHandle.readComplete();
569                 pipeline.fireChannelReadComplete();
570 
571                 if (close || allocHandle.isReadEOF()) {
572                     shutdownInput(false);
573                 }
574             } catch (Throwable t) {
575                 handleReadException(pipeline, byteBuf, t, close, allocHandle);
576             } finally {
577                 if (shouldStopReading(config)) {
578                     clearReadFilter0();
579                 }
580             }
581         }
582 
583         private void handleReadException(ChannelPipeline pipeline, ByteBuf byteBuf, Throwable cause, boolean close,
584                                          KQueueRecvByteAllocatorHandle allocHandle) {
585             if (byteBuf != null) {
586                 if (byteBuf.isReadable()) {
587                     readPending = false;
588                     pipeline.fireChannelRead(byteBuf);
589                 } else {
590                     byteBuf.release();
591                 }
592             }
593             if (!failConnectPromise(cause)) {
594                 allocHandle.readComplete();
595                 pipeline.fireChannelReadComplete();
596                 pipeline.fireExceptionCaught(cause);
597 
598                 // If oom will close the read event, release connection.
599                 // See https://github.com/netty/netty/issues/10434
600                 if (close ||
601                         cause instanceof OutOfMemoryError ||
602                         cause instanceof LeakPresenceDetector.AllocationProhibitedException ||
603                         cause instanceof IOException) {
604                     shutdownInput(false);
605                 }
606             }
607         }
608     }
609 
610     private final class KQueueSocketWritableByteChannel extends SocketWritableByteChannel {
611         KQueueSocketWritableByteChannel() {
612             super(socket);
613         }
614 
615         @Override
616         protected ByteBufAllocator alloc() {
617             return AbstractKQueueStreamChannel.this.alloc();
618         }
619     }
620 }