View Javadoc
1   /*
2    * Copyright 2012 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.channel.nio;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.buffer.ByteBufAllocator;
20  import io.netty.buffer.ByteBufUtil;
21  import io.netty.buffer.Unpooled;
22  import io.netty.channel.AbstractChannel;
23  import io.netty.channel.Channel;
24  import io.netty.channel.ChannelException;
25  import io.netty.channel.ChannelFuture;
26  import io.netty.channel.ChannelFutureListener;
27  import io.netty.channel.ChannelPromise;
28  import io.netty.channel.ConnectTimeoutException;
29  import io.netty.channel.EventLoop;
30  import io.netty.channel.IoEvent;
31  import io.netty.channel.IoEventLoop;
32  import io.netty.channel.IoEventLoopGroup;
33  import io.netty.channel.IoRegistration;
34  import io.netty.util.ReferenceCountUtil;
35  import io.netty.util.ReferenceCounted;
36  import io.netty.util.concurrent.Future;
37  import io.netty.util.internal.ObjectUtil;
38  import io.netty.util.internal.logging.InternalLogger;
39  import io.netty.util.internal.logging.InternalLoggerFactory;
40  
41  import java.io.IOException;
42  import java.net.SocketAddress;
43  import java.nio.channels.CancelledKeyException;
44  import java.nio.channels.ClosedChannelException;
45  import java.nio.channels.ConnectionPendingException;
46  import java.nio.channels.SelectableChannel;
47  import java.nio.channels.SelectionKey;
48  import java.util.concurrent.TimeUnit;
49  
50  /**
51   * Abstract base class for {@link Channel} implementations which use a Selector based approach.
52   */
53  public abstract class AbstractNioChannel extends AbstractChannel {
54  
55      private static final InternalLogger logger =
56              InternalLoggerFactory.getInstance(AbstractNioChannel.class);
57  
58      private final SelectableChannel ch;
59      protected final int readInterestOp;
60      protected final NioIoOps readOps;
61      volatile IoRegistration registration;
62      boolean readPending;
63      private final Runnable clearReadPendingRunnable = new Runnable() {
64          @Override
65          public void run() {
66              clearReadPending0();
67          }
68      };
69  
70      /**
71       * The future of the current connection attempt.  If not null, subsequent
72       * connection attempts will fail.
73       */
74      private ChannelPromise connectPromise;
75      private Future<?> connectTimeoutFuture;
76      private SocketAddress requestedRemoteAddress;
77  
78      /**
79       * Create a new instance
80       *
81       * @param parent            the parent {@link Channel} by which this instance was created. May be {@code null}
82       * @param ch                the underlying {@link SelectableChannel} on which it operates
83       * @param readOps           the ops to set to receive data from the {@link SelectableChannel}
84       */
85      protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readOps) {
86          this(parent, ch, NioIoOps.valueOf(readOps));
87      }
88  
89      protected AbstractNioChannel(Channel parent, SelectableChannel ch, NioIoOps readOps) {
90          super(parent);
91          this.ch = ch;
92          this.readInterestOp = ObjectUtil.checkNotNull(readOps, "readOps").value;
93          this.readOps = readOps;
94          try {
95              ch.configureBlocking(false);
96          } catch (IOException e) {
97              try {
98                  ch.close();
99              } catch (IOException e2) {
100                 logger.warn(
101                             "Failed to close a partially initialized socket.", e2);
102             }
103 
104             throw new ChannelException("Failed to enter non-blocking mode.", e);
105         }
106     }
107 
108     protected void addAndSubmit(NioIoOps addOps) {
109         int interestOps = selectionKey().interestOps();
110         if (!addOps.isIncludedIn(interestOps)) {
111             try {
112                 registration().submit(NioIoOps.valueOf(interestOps).with(addOps));
113             } catch (Exception e) {
114                 throw new ChannelException(e);
115             }
116         }
117     }
118 
119     protected void removeAndSubmit(NioIoOps removeOps) {
120         int interestOps = selectionKey().interestOps();
121         if (removeOps.isIncludedIn(interestOps)) {
122             try {
123                 registration().submit(NioIoOps.valueOf(interestOps).without(removeOps));
124             } catch (Exception e) {
125                 throw new ChannelException(e);
126             }
127         }
128     }
129 
130     @Override
131     public boolean isOpen() {
132         return ch.isOpen();
133     }
134 
135     @Override
136     public NioUnsafe unsafe() {
137         return (NioUnsafe) super.unsafe();
138     }
139 
140     protected SelectableChannel javaChannel() {
141         return ch;
142     }
143 
144     /**
145      * Return the current {@link SelectionKey}
146      *
147      * @deprecated use {@link #registration}.
148      */
149     @Deprecated
150     protected SelectionKey selectionKey() {
151         return registration().attachment();
152     }
153 
154     @SuppressWarnings("unchecked")
155     protected IoRegistration registration() {
156         assert registration != null;
157         return registration;
158     }
159 
160     /**
161      * @deprecated No longer supported.
162      * No longer supported.
163      */
164     @Deprecated
165     protected boolean isReadPending() {
166         return readPending;
167     }
168 
169     /**
170      * @deprecated Use {@link #clearReadPending()} if appropriate instead.
171      * No longer supported.
172      */
173     @Deprecated
174     protected void setReadPending(final boolean readPending) {
175         if (isRegistered()) {
176             EventLoop eventLoop = eventLoop();
177             if (eventLoop.inEventLoop()) {
178                 setReadPending0(readPending);
179             } else {
180                 eventLoop.execute(new Runnable() {
181                     @Override
182                     public void run() {
183                         setReadPending0(readPending);
184                     }
185                 });
186             }
187         } else {
188             // Best effort if we are not registered yet clear readPending.
189             // NB: We only set the boolean field instead of calling clearReadPending0(), because the SelectionKey is
190             // not set yet so it would produce an assertion failure.
191             this.readPending = readPending;
192         }
193     }
194 
195     /**
196      * Set read pending to {@code false}.
197      */
198     protected final void clearReadPending() {
199         if (isRegistered()) {
200             EventLoop eventLoop = eventLoop();
201             if (eventLoop.inEventLoop()) {
202                 clearReadPending0();
203             } else {
204                 eventLoop.execute(clearReadPendingRunnable);
205             }
206         } else {
207             // Best effort if we are not registered yet clear readPending. This happens during channel initialization.
208             // NB: We only set the boolean field instead of calling clearReadPending0(), because the SelectionKey is
209             // not set yet so it would produce an assertion failure.
210             readPending = false;
211         }
212     }
213 
214     private void setReadPending0(boolean readPending) {
215         this.readPending = readPending;
216         if (!readPending) {
217             ((AbstractNioUnsafe) unsafe()).removeReadOp();
218         }
219     }
220 
221     private void clearReadPending0() {
222         readPending = false;
223         ((AbstractNioUnsafe) unsafe()).removeReadOp();
224     }
225 
226     /**
227      * Special {@link Unsafe} sub-type which allows to access the underlying {@link SelectableChannel}
228      */
229     public interface NioUnsafe extends Unsafe {
230         /**
231          * Return underlying {@link SelectableChannel}
232          */
233         SelectableChannel ch();
234 
235         /**
236          * Finish connect
237          */
238         void finishConnect();
239 
240         /**
241          * Read from underlying {@link SelectableChannel}
242          */
243         void read();
244 
245         void forceFlush();
246     }
247 
248     protected abstract class AbstractNioUnsafe extends AbstractUnsafe implements NioUnsafe, NioIoHandle {
249         @Override
250         public void close() {
251             close(voidPromise());
252         }
253 
254         @Override
255         public SelectableChannel selectableChannel() {
256             return ch();
257         }
258 
259         Channel channel() {
260             return AbstractNioChannel.this;
261         }
262 
263         protected final void removeReadOp() {
264             // Read the field directly as the channel may have been deregistered concurrently (setting
265             // registration to null) after the isRegistered() check in clearReadPending() but before the
266             // clearReadPendingRunnable is executed on the EventLoop. See https://github.com/netty/netty/issues/17103
267             IoRegistration registration = AbstractNioChannel.this.registration;
268             // Check first if the key is still valid as it may be canceled as part of the deregistration
269             // from the EventLoop
270             // See https://github.com/netty/netty/issues/2104
271             if (registration == null || !registration.isValid()) {
272                 return;
273             }
274             removeAndSubmit(readOps);
275         }
276 
277         @Override
278         public final SelectableChannel ch() {
279             return javaChannel();
280         }
281 
282         @Override
283         public final void connect(
284                 final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
285             // Don't mark the connect promise as uncancellable as in fact we can cancel it as it is using
286             // non-blocking io.
287             if (promise.isDone() || !ensureOpen(promise)) {
288                 return;
289             }
290 
291             try {
292                 if (connectPromise != null) {
293                     // Already a connect in process.
294                     throw new ConnectionPendingException();
295                 }
296 
297                 boolean wasActive = isActive();
298                 if (doConnect(remoteAddress, localAddress)) {
299                     fulfillConnectPromise(promise, wasActive);
300                 } else {
301                     connectPromise = promise;
302                     requestedRemoteAddress = remoteAddress;
303 
304                     // Schedule connect timeout.
305                     final int connectTimeoutMillis = config().getConnectTimeoutMillis();
306                     if (connectTimeoutMillis > 0) {
307                         connectTimeoutFuture = eventLoop().schedule(new Runnable() {
308                             @Override
309                             public void run() {
310                                 ChannelPromise connectPromise = AbstractNioChannel.this.connectPromise;
311                                 if (connectPromise != null && !connectPromise.isDone()
312                                         && connectPromise.tryFailure(new ConnectTimeoutException(
313                                                 "connection timed out after " + connectTimeoutMillis + " ms: " +
314                                                         remoteAddress))) {
315                                     close(voidPromise());
316                                 }
317                             }
318                         }, connectTimeoutMillis, TimeUnit.MILLISECONDS);
319                     }
320 
321                     promise.addListener(new ChannelFutureListener() {
322                         @Override
323                         public void operationComplete(ChannelFuture future) {
324                             // If the connect future is cancelled we also cancel the timeout and close the
325                             // underlying socket.
326                             if (future.isCancelled()) {
327                                 if (connectTimeoutFuture != null) {
328                                     connectTimeoutFuture.cancel(false);
329                                 }
330                                 connectPromise = null;
331                                 close(voidPromise());
332                             }
333                         }
334                     });
335                 }
336             } catch (Throwable t) {
337                 promise.tryFailure(annotateConnectException(t, remoteAddress));
338                 closeIfClosed();
339             }
340         }
341 
342         private void fulfillConnectPromise(ChannelPromise promise, boolean wasActive) {
343             if (promise == null) {
344                 // Closed via cancellation and the promise has been notified already.
345                 return;
346             }
347 
348             // Get the state as trySuccess() may trigger an ChannelFutureListener that will close the Channel.
349             // We still need to ensure we call fireChannelActive() in this case.
350             boolean active = isActive();
351 
352             // trySuccess() will return false if a user cancelled the connection attempt.
353             boolean promiseSet = promise.trySuccess();
354 
355             // Regardless if the connection attempt was cancelled, channelActive() event should be triggered,
356             // because what happened is what happened.
357             if (!wasActive && active) {
358                 pipeline().fireChannelActive();
359             }
360 
361             // If a user cancelled the connection attempt, close the channel, which is followed by channelInactive().
362             if (!promiseSet) {
363                 close(voidPromise());
364             }
365         }
366 
367         private void fulfillConnectPromise(ChannelPromise promise, Throwable cause) {
368             if (promise == null) {
369                 // Closed via cancellation and the promise has been notified already.
370                 return;
371             }
372 
373             // Use tryFailure() instead of setFailure() to avoid the race against cancel().
374             promise.tryFailure(cause);
375             closeIfClosed();
376         }
377 
378         @Override
379         public final void finishConnect() {
380             // Note this method is invoked by the event loop only if the connection attempt was
381             // neither cancelled nor timed out.
382 
383             assert eventLoop().inEventLoop();
384 
385             try {
386                 boolean wasActive = isActive();
387                 doFinishConnect();
388                 fulfillConnectPromise(connectPromise, wasActive);
389             } catch (Throwable t) {
390                 fulfillConnectPromise(connectPromise, annotateConnectException(t, requestedRemoteAddress));
391             } finally {
392                 // Check for null as the connectTimeoutFuture is only created if a connectTimeoutMillis > 0 is used
393                 // See https://github.com/netty/netty/issues/1770
394                 if (connectTimeoutFuture != null) {
395                     connectTimeoutFuture.cancel(false);
396                 }
397                 connectPromise = null;
398             }
399         }
400 
401         @Override
402         protected final void flush0() {
403             // Flush immediately only when there's no pending flush.
404             // If there's a pending flush operation, event loop will call forceFlush() later,
405             // and thus there's no need to call it now.
406             if (!isFlushPending()) {
407                 super.flush0();
408             }
409         }
410 
411         @Override
412         public final void forceFlush() {
413             // directly call super.flush0() to force a flush now
414             super.flush0();
415         }
416 
417         private boolean isFlushPending() {
418             IoRegistration registration = registration();
419             return registration.isValid() && NioIoOps.WRITE.isIncludedIn((
420                     (SelectionKey) registration.attachment()).interestOps());
421         }
422 
423         @Override
424         public void handle(IoRegistration registration, IoEvent event) {
425             try {
426                 NioIoEvent nioEvent = (NioIoEvent) event;
427                 NioIoOps nioReadyOps = nioEvent.ops();
428                 // We first need to call finishConnect() before try to trigger a read(...) or write(...) as otherwise
429                 // the NIO JDK channel implementation may throw a NotYetConnectedException.
430                 if (nioReadyOps.contains(NioIoOps.CONNECT)) {
431                     // remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking
432                     // See https://github.com/netty/netty/issues/924
433                     removeAndSubmit(NioIoOps.CONNECT);
434 
435                     unsafe().finishConnect();
436                 }
437 
438                 // Process OP_WRITE first as we may be able to write some queued buffers and so free memory.
439                 if (nioReadyOps.contains(NioIoOps.WRITE)) {
440                     // Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to
441                     // write
442                     forceFlush();
443                 }
444 
445                 // Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead
446                 // to a spin loop
447                 if (nioReadyOps.contains(NioIoOps.READ_AND_ACCEPT) || nioReadyOps.equals(NioIoOps.NONE)) {
448                     read();
449                 }
450             } catch (CancelledKeyException ignored) {
451                 close(voidPromise());
452             }
453         }
454     }
455 
456     @Override
457     protected boolean isCompatible(EventLoop loop) {
458         return loop instanceof IoEventLoop && ((IoEventLoopGroup) loop).isCompatible(AbstractNioUnsafe.class);
459     }
460 
461     @SuppressWarnings("unchecked")
462     @Override
463     protected void doRegister(ChannelPromise promise) {
464         assert registration == null;
465         ((IoEventLoop) eventLoop()).register((AbstractNioUnsafe) unsafe()).addListener(f -> {
466             if (f.isSuccess()) {
467                 registration = (IoRegistration) f.getNow();
468                 promise.setSuccess();
469             } else {
470                 promise.setFailure(f.cause());
471             }
472         });
473     }
474 
475     @Override
476     protected void doDeregister() throws Exception {
477         IoRegistration registration = this.registration;
478         if (registration != null) {
479             this.registration = null;
480             registration.cancel();
481         }
482     }
483 
484     @Override
485     protected void doBeginRead() throws Exception {
486         // Channel.read() or ChannelHandlerContext.read() was called
487         IoRegistration registration = this.registration;
488         if (registration == null || !registration.isValid()) {
489             return;
490         }
491 
492         readPending = true;
493 
494         addAndSubmit(readOps);
495     }
496 
497     /**
498      * Connect to the remote peer
499      */
500     protected abstract boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception;
501 
502     /**
503      * Finish the connect
504      */
505     protected abstract void doFinishConnect() throws Exception;
506 
507     /**
508      * Returns an off-heap copy of the specified {@link ByteBuf}, and releases the original one.
509      * Note that this method does not create an off-heap copy if the allocation / deallocation cost is too high,
510      * but just returns the original {@link ByteBuf}..
511      */
512     protected final ByteBuf newDirectBuffer(ByteBuf buf) {
513         final int readableBytes = buf.readableBytes();
514         if (readableBytes == 0) {
515             ReferenceCountUtil.safeRelease(buf);
516             return Unpooled.EMPTY_BUFFER;
517         }
518 
519         final ByteBufAllocator alloc = alloc();
520         if (alloc.isDirectBufferPooled()) {
521             ByteBuf directBuf = alloc.directBuffer(readableBytes);
522             directBuf.writeBytes(buf, buf.readerIndex(), readableBytes);
523             ReferenceCountUtil.safeRelease(buf);
524             return directBuf;
525         }
526 
527         final ByteBuf directBuf = ByteBufUtil.threadLocalDirectBuffer();
528         if (directBuf != null) {
529             directBuf.writeBytes(buf, buf.readerIndex(), readableBytes);
530             ReferenceCountUtil.safeRelease(buf);
531             return directBuf;
532         }
533 
534         // Allocating and deallocating an unpooled direct buffer is very expensive; give up.
535         return buf;
536     }
537 
538     /**
539      * Returns an off-heap copy of the specified {@link ByteBuf}, and releases the specified holder.
540      * The caller must ensure that the holder releases the original {@link ByteBuf} when the holder is released by
541      * this method.  Note that this method does not create an off-heap copy if the allocation / deallocation cost is
542      * too high, but just returns the original {@link ByteBuf}..
543      */
544     protected final ByteBuf newDirectBuffer(ReferenceCounted holder, ByteBuf buf) {
545         final int readableBytes = buf.readableBytes();
546         if (readableBytes == 0) {
547             ReferenceCountUtil.safeRelease(holder);
548             return Unpooled.EMPTY_BUFFER;
549         }
550 
551         final ByteBufAllocator alloc = alloc();
552         if (alloc.isDirectBufferPooled()) {
553             ByteBuf directBuf = alloc.directBuffer(readableBytes);
554             directBuf.writeBytes(buf, buf.readerIndex(), readableBytes);
555             ReferenceCountUtil.safeRelease(holder);
556             return directBuf;
557         }
558 
559         final ByteBuf directBuf = ByteBufUtil.threadLocalDirectBuffer();
560         if (directBuf != null) {
561             directBuf.writeBytes(buf, buf.readerIndex(), readableBytes);
562             ReferenceCountUtil.safeRelease(holder);
563             return directBuf;
564         }
565 
566         // Allocating and deallocating an unpooled direct buffer is very expensive; give up.
567         if (holder != buf) {
568             // Ensure to call holder.release() to give the holder a chance to release other resources than its content.
569             buf.retain();
570             ReferenceCountUtil.safeRelease(holder);
571         }
572 
573         return buf;
574     }
575 
576     @Override
577     protected void doClose() throws Exception {
578         ChannelPromise promise = connectPromise;
579         if (promise != null) {
580             // Use tryFailure() instead of setFailure() to avoid the race against cancel().
581             promise.tryFailure(new ClosedChannelException());
582             connectPromise = null;
583         }
584 
585         Future<?> future = connectTimeoutFuture;
586         if (future != null) {
587             future.cancel(false);
588             connectTimeoutFuture = null;
589         }
590     }
591 }