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.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.ChannelConfig;
25  import io.netty.channel.ChannelException;
26  import io.netty.channel.ChannelFuture;
27  import io.netty.channel.ChannelFutureListener;
28  import io.netty.channel.ChannelMetadata;
29  import io.netty.channel.ChannelOutboundBuffer;
30  import io.netty.channel.ChannelPromise;
31  import io.netty.channel.ConnectTimeoutException;
32  import io.netty.channel.EventLoop;
33  import io.netty.channel.RecvByteBufAllocator;
34  import io.netty.channel.socket.ChannelInputShutdownEvent;
35  import io.netty.channel.socket.ChannelInputShutdownReadComplete;
36  import io.netty.channel.socket.SocketChannelConfig;
37  import io.netty.channel.unix.FileDescriptor;
38  import io.netty.channel.unix.UnixChannel;
39  import io.netty.util.ReferenceCountUtil;
40  import io.netty.util.concurrent.Future;
41  import io.netty.util.internal.UnstableApi;
42  
43  import java.io.IOException;
44  import java.net.ConnectException;
45  import java.net.InetSocketAddress;
46  import java.net.SocketAddress;
47  import java.nio.ByteBuffer;
48  import java.nio.channels.AlreadyConnectedException;
49  import java.nio.channels.ConnectionPendingException;
50  import java.nio.channels.NotYetConnectedException;
51  import java.nio.channels.UnresolvedAddressException;
52  import java.util.concurrent.TimeUnit;
53  
54  import static io.netty.channel.internal.ChannelUtils.WRITE_STATUS_SNDBUF_FULL;
55  import static io.netty.channel.unix.UnixChannelUtil.computeRemoteAddr;
56  import static io.netty.util.internal.ObjectUtil.checkNotNull;
57  
58  abstract class AbstractKQueueChannel extends AbstractChannel implements UnixChannel {
59      private static final ChannelMetadata METADATA = new ChannelMetadata(false);
60      /**
61       * The future of the current connection attempt.  If not null, subsequent
62       * connection attempts will fail.
63       */
64      private ChannelPromise connectPromise;
65      private Future<?> connectTimeoutFuture;
66      private SocketAddress requestedRemoteAddress;
67  
68      final BsdSocket socket;
69      private boolean readFilterEnabled;
70      private boolean writeFilterEnabled;
71      KQueueEventLoop.KQueueRegistration registration;
72      boolean readReadyRunnablePending;
73      boolean inputClosedSeenErrorOnRead;
74      protected volatile boolean active;
75      private volatile SocketAddress local;
76      private volatile SocketAddress remote;
77  
78      AbstractKQueueChannel(Channel parent, BsdSocket fd, boolean active) {
79          super(parent);
80          socket = checkNotNull(fd, "fd");
81          this.active = active;
82          if (active) {
83              // Directly cache the remote and local addresses
84              // See https://github.com/netty/netty/issues/2359
85              local = fd.localAddress();
86              remote = fd.remoteAddress();
87          }
88      }
89  
90      AbstractKQueueChannel(Channel parent, BsdSocket fd, SocketAddress remote) {
91          super(parent);
92          socket = checkNotNull(fd, "fd");
93          active = true;
94          // Directly cache the remote and local addresses
95          // See https://github.com/netty/netty/issues/2359
96          this.remote = remote;
97          local = fd.localAddress();
98      }
99  
100     static boolean isSoErrorZero(BsdSocket fd) {
101         try {
102             return fd.getSoError() == 0;
103         } catch (IOException e) {
104             throw new ChannelException(e);
105         }
106     }
107 
108     @Override
109     public final FileDescriptor fd() {
110         return socket;
111     }
112 
113     @Override
114     public boolean isActive() {
115         return active;
116     }
117 
118     @Override
119     public ChannelMetadata metadata() {
120         return METADATA;
121     }
122 
123     @Override
124     protected void doClose() throws Exception {
125         // Clear kqueue registrations *before* we close the file descriptor.
126         // Otherwise, the filter removals might hit a reused file descriptor.
127         doDeregister();
128 
129         active = false;
130         // Even if we allow half closed sockets we should give up on reading. Otherwise we may allow a read attempt on a
131         // socket which has not even been connected yet. This has been observed to block during unit tests.
132         inputClosedSeenErrorOnRead = true;
133         socket.close();
134     }
135 
136     @Override
137     protected void doDisconnect() throws Exception {
138         doClose();
139     }
140 
141     void resetCachedAddresses() {
142         local = socket.localAddress();
143         remote = socket.remoteAddress();
144     }
145 
146     @Override
147     protected boolean isCompatible(EventLoop loop) {
148         return loop instanceof KQueueEventLoop;
149     }
150 
151     @Override
152     public boolean isOpen() {
153         return socket.isOpen();
154     }
155 
156     @Override
157     protected void doDeregister() throws Exception {
158         if (registration != null) {
159             registration.remove();
160             registration = null;
161         }
162 
163         // As unregisteredFilters() may have not been called because isOpen() returned false we just set both filters
164         // to false to ensure a consistent state in all cases.
165         readFilterEnabled = false;
166         writeFilterEnabled = false;
167     }
168 
169     void unregisterFilters() throws Exception {
170         // Make sure we unregister our filters from kqueue!
171         readFilter(false);
172         writeFilter(false);
173         clearRdHup0();
174     }
175 
176     private void clearRdHup0() {
177         evSet0(Native.EVFILT_SOCK, Native.EV_DELETE_DISABLE, Native.NOTE_RDHUP);
178     }
179 
180     @Override
181     protected final void doBeginRead() throws Exception {
182         // Channel.read() or ChannelHandlerContext.read() was called
183         final AbstractKQueueUnsafe unsafe = (AbstractKQueueUnsafe) unsafe();
184         unsafe.readPending = true;
185 
186         // We must set the read flag here as it is possible the user didn't read in the last read loop, the
187         // executeReadReadyRunnable could read nothing, and if the user doesn't explicitly call read they will
188         // never get data after this.
189         readFilter(true);
190     }
191 
192     @Override
193     protected void doRegister() throws Exception {
194         // Just in case the previous EventLoop was shutdown abruptly, or an event is still pending on the old EventLoop
195         // make sure the readReadyRunnablePending variable is reset so we will be able to execute the Runnable on the
196         // new EventLoop.
197         readReadyRunnablePending = false;
198 
199         registration = ((KQueueEventLoop) eventLoop()).add(this);
200 
201         // Add the write event first so we get notified of connection refused on the client side!
202         if (writeFilterEnabled) {
203             evSet0(Native.EVFILT_WRITE, Native.EV_ADD_ENABLE);
204         }
205         if (readFilterEnabled) {
206             evSet0(Native.EVFILT_READ, Native.EV_ADD_ENABLE);
207         }
208         evSet0(Native.EVFILT_SOCK, Native.EV_ADD, Native.NOTE_RDHUP);
209     }
210 
211     @Override
212     protected abstract AbstractKQueueUnsafe newUnsafe();
213 
214     @Override
215     public abstract KQueueChannelConfig config();
216 
217     /**
218      * Returns an off-heap copy of the specified {@link ByteBuf}, and releases the original one.
219      */
220     protected final ByteBuf newDirectBuffer(ByteBuf buf) {
221         return newDirectBuffer(buf, buf);
222     }
223 
224     /**
225      * Returns an off-heap copy of the specified {@link ByteBuf}, and releases the specified holder.
226      * The caller must ensure that the holder releases the original {@link ByteBuf} when the holder is released by
227      * this method.
228      */
229     protected final ByteBuf newDirectBuffer(Object holder, ByteBuf buf) {
230         final int readableBytes = buf.readableBytes();
231         if (readableBytes == 0) {
232             ReferenceCountUtil.release(holder);
233             return Unpooled.EMPTY_BUFFER;
234         }
235 
236         final ByteBufAllocator alloc = alloc();
237         if (alloc.isDirectBufferPooled()) {
238             return newDirectBuffer0(holder, buf, alloc, readableBytes);
239         }
240 
241         final ByteBuf directBuf = ByteBufUtil.threadLocalDirectBuffer();
242         if (directBuf == null) {
243             return newDirectBuffer0(holder, buf, alloc, readableBytes);
244         }
245 
246         directBuf.writeBytes(buf, buf.readerIndex(), readableBytes);
247         ReferenceCountUtil.safeRelease(holder);
248         return directBuf;
249     }
250 
251     private static ByteBuf newDirectBuffer0(Object holder, ByteBuf buf, ByteBufAllocator alloc, int capacity) {
252         final ByteBuf directBuf = alloc.directBuffer(capacity);
253         directBuf.writeBytes(buf, buf.readerIndex(), capacity);
254         ReferenceCountUtil.safeRelease(holder);
255         return directBuf;
256     }
257 
258     protected static void checkResolvable(InetSocketAddress addr) {
259         if (addr.isUnresolved()) {
260             throw new UnresolvedAddressException();
261         }
262     }
263 
264     /**
265      * Read bytes into the given {@link ByteBuf} and return the amount.
266      */
267     protected final int doReadBytes(ByteBuf byteBuf) throws Exception {
268         int writerIndex = byteBuf.writerIndex();
269         int localReadAmount;
270         unsafe().recvBufAllocHandle().attemptedBytesRead(byteBuf.writableBytes());
271         if (byteBuf.hasMemoryAddress()) {
272             localReadAmount = socket.readAddress(byteBuf.memoryAddress(), writerIndex, byteBuf.capacity());
273         } else {
274             ByteBuffer buf = byteBuf.internalNioBuffer(writerIndex, byteBuf.writableBytes());
275             localReadAmount = socket.read(buf, buf.position(), buf.limit());
276         }
277         if (localReadAmount > 0) {
278             byteBuf.writerIndex(writerIndex + localReadAmount);
279         }
280         return localReadAmount;
281     }
282 
283     protected final int doWriteBytes(ChannelOutboundBuffer in, ByteBuf buf) throws Exception {
284         if (buf.hasMemoryAddress()) {
285             int localFlushedAmount = socket.writeAddress(buf.memoryAddress(), buf.readerIndex(), buf.writerIndex());
286             if (localFlushedAmount > 0) {
287                 in.removeBytes(localFlushedAmount);
288                 return 1;
289             }
290         } else {
291             final ByteBuffer nioBuf = buf.nioBufferCount() == 1?
292                     buf.internalNioBuffer(buf.readerIndex(), buf.readableBytes()) : buf.nioBuffer();
293             int localFlushedAmount = socket.write(nioBuf, nioBuf.position(), nioBuf.limit());
294             if (localFlushedAmount > 0) {
295                 nioBuf.position(nioBuf.position() + localFlushedAmount);
296                 in.removeBytes(localFlushedAmount);
297                 return 1;
298             }
299         }
300         return WRITE_STATUS_SNDBUF_FULL;
301     }
302 
303     final boolean shouldBreakReadReady(ChannelConfig config) {
304         return socket.isInputShutdown() && (inputClosedSeenErrorOnRead || !isAllowHalfClosure(config));
305     }
306 
307     private static boolean isAllowHalfClosure(ChannelConfig config) {
308         if (config instanceof KQueueDomainSocketChannelConfig) {
309             return ((KQueueDomainSocketChannelConfig) config).isAllowHalfClosure();
310         }
311 
312         return config instanceof SocketChannelConfig &&
313                 ((SocketChannelConfig) config).isAllowHalfClosure();
314     }
315 
316     final void clearReadFilter() {
317         // Only clear if registered with an EventLoop as otherwise
318         if (isRegistered()) {
319             final EventLoop loop = eventLoop();
320             final AbstractKQueueUnsafe unsafe = (AbstractKQueueUnsafe) unsafe();
321             if (loop.inEventLoop()) {
322                 unsafe.clearReadFilter0();
323             } else {
324                 // schedule a task to clear the EPOLLIN as it is not safe to modify it directly
325                 loop.execute(new Runnable() {
326                     @Override
327                     public void run() {
328                         if (!unsafe.readPending && !config().isAutoRead()) {
329                             // Still no read triggered so clear it now
330                             unsafe.clearReadFilter0();
331                         }
332                     }
333                 });
334             }
335         } else  {
336             // The EventLoop is not registered atm so just update the flags so the correct value
337             // will be used once the channel is registered
338             readFilterEnabled = false;
339         }
340     }
341 
342     void readFilter(boolean readFilterEnabled) throws IOException {
343         if (this.readFilterEnabled != readFilterEnabled) {
344             this.readFilterEnabled = readFilterEnabled;
345             evSet(Native.EVFILT_READ, readFilterEnabled ? Native.EV_ADD_ENABLE : Native.EV_DELETE_DISABLE);
346         }
347     }
348 
349     void writeFilter(boolean writeFilterEnabled) throws IOException {
350         if (this.writeFilterEnabled != writeFilterEnabled) {
351             this.writeFilterEnabled = writeFilterEnabled;
352             evSet(Native.EVFILT_WRITE, writeFilterEnabled ? Native.EV_ADD_ENABLE : Native.EV_DELETE_DISABLE);
353         }
354     }
355 
356     private void evSet(short filter, short flags) {
357         if (isRegistered()) {
358             evSet0(filter, flags);
359         }
360     }
361 
362     private void evSet0(short filter, short flags) {
363         evSet0(filter, flags, 0);
364     }
365 
366     private void evSet0(short filter, short flags, int fflags) {
367         // Only try to add to changeList if the FD is still open, if not we already closed it in the meantime.
368         if (isOpen() && registration != null) {
369             registration.evSet(filter, flags, fflags, 0);
370         }
371     }
372 
373     @UnstableApi
374     public abstract class AbstractKQueueUnsafe extends AbstractUnsafe {
375         boolean readPending;
376         private KQueueRecvByteAllocatorHandle allocHandle;
377 
378         void readReady() {
379             readReady(recvBufAllocHandle());
380         }
381 
382         abstract void readReady(KQueueRecvByteAllocatorHandle allocHandle);
383 
384         final boolean shouldStopReading(ChannelConfig config) {
385             // Check if there is a readPending which was not processed yet.
386             // This could be for two reasons:
387             // * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method
388             // * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method
389             //
390             // See https://github.com/netty/netty/issues/2254
391             return !readPending && !config.isAutoRead();
392         }
393 
394         final boolean failConnectPromise(Throwable cause) {
395             if (connectPromise != null) {
396                 // SO_ERROR has been shown to return 0 on macOS if detect an error via read() and the write filter was
397                 // not set before calling connect. This means finishConnect will not detect any error and would
398                 // successfully complete the connectPromise and update the channel state to active (which is incorrect).
399                 ChannelPromise connectPromise = AbstractKQueueChannel.this.connectPromise;
400                 AbstractKQueueChannel.this.connectPromise = null;
401                 if (connectPromise.tryFailure((cause instanceof ConnectException) ? cause
402                                 : new ConnectException("failed to connect").initCause(cause))) {
403                     closeIfClosed();
404                     return true;
405                 }
406             }
407             return false;
408         }
409 
410         final void writeReady() {
411             if (connectPromise != null) {
412                 // pending connect which is now complete so handle it.
413                 finishConnect();
414             } else if (!socket.isOutputShutdown()) {
415                 // directly call super.flush0() to force a flush now
416                 super.flush0();
417             }
418         }
419 
420         /**
421          * Shutdown the input side of the channel.
422          */
423         void shutdownInput(boolean readEOF) {
424             // We need to take special care of calling finishConnect() if readEOF is true and we not
425             // fulfilled the connectPromise yet. If we fail to do so the connectPromise will be failed
426             // with a ClosedChannelException as a close() will happen and so the FD is closed before we
427             // have a chance to call finishConnect() later on. Calling finishConnect() here will ensure
428             // we observe the correct exception in case of a connect failure.
429             if (readEOF && connectPromise != null) {
430                 finishConnect();
431             }
432             if (!socket.isInputShutdown()) {
433                 if (isAllowHalfClosure(config())) {
434                     try {
435                         socket.shutdown(true, false);
436                     } catch (IOException ignored) {
437                         // We attempted to shutdown and failed, which means the input has already effectively been
438                         // shutdown.
439                         fireEventAndClose(ChannelInputShutdownEvent.INSTANCE);
440                         return;
441                     } catch (NotYetConnectedException ignore) {
442                         // We attempted to shutdown and failed, which means the input has already effectively been
443                         // shutdown.
444                     }
445                     if (shouldStopReading(config())) {
446                         clearReadFilter0();
447                     }
448                     pipeline().fireUserEventTriggered(ChannelInputShutdownEvent.INSTANCE);
449                 } else {
450                     close(voidPromise());
451                     return;
452                 }
453             }
454             if (!readEOF && !inputClosedSeenErrorOnRead) {
455                 inputClosedSeenErrorOnRead = true;
456                 pipeline().fireUserEventTriggered(ChannelInputShutdownReadComplete.INSTANCE);
457             }
458         }
459 
460         final void readEOF() {
461             // This must happen before we attempt to read. This will ensure reading continues until an error occurs.
462             final KQueueRecvByteAllocatorHandle allocHandle = recvBufAllocHandle();
463             allocHandle.readEOF();
464 
465             // kqueue reports each filter as its own event, so the EOF may be processed before the EVFILT_WRITE event
466             // that completes the connect. Finish the connect first so the data that the remote peer sent before it
467             // closed the connection is read, and not dropped by shutting down the input.
468             if (connectPromise != null) {
469                 finishConnect();
470             }
471 
472             if (isActive()) {
473                 // If it is still active, we need to call readReady as otherwise we may miss to
474                 // read pending data from the underlying file descriptor.
475                 // See https://github.com/netty/netty/issues/3709
476                 readReady(allocHandle);
477             } else {
478                 // Just to be safe make sure the input marked as closed.
479                 shutdownInput(true);
480             }
481 
482             // Clear the RDHUP flag to prevent continuously getting woken up on this event.
483             clearRdHup0();
484         }
485 
486         @Override
487         public KQueueRecvByteAllocatorHandle recvBufAllocHandle() {
488             if (allocHandle == null) {
489                 allocHandle = new KQueueRecvByteAllocatorHandle(
490                         (RecvByteBufAllocator.ExtendedHandle) super.recvBufAllocHandle());
491             }
492             return allocHandle;
493         }
494 
495         @Override
496         protected final void flush0() {
497             // Flush immediately only when there's no pending flush.
498             // If there's a pending flush operation, event loop will call forceFlush() later,
499             // and thus there's no need to call it now.
500             if (!writeFilterEnabled) {
501                 super.flush0();
502             }
503         }
504 
505         protected final void clearReadFilter0() {
506             assert eventLoop().inEventLoop();
507             try {
508                 readPending = false;
509                 readFilter(false);
510             } catch (IOException e) {
511                 // When this happens there is something completely wrong with either the filedescriptor or epoll,
512                 // so fire the exception through the pipeline and close the Channel.
513                 pipeline().fireExceptionCaught(e);
514                 unsafe().close(unsafe().voidPromise());
515             }
516         }
517 
518         private void fireEventAndClose(Object evt) {
519             pipeline().fireUserEventTriggered(evt);
520             close(voidPromise());
521         }
522 
523         @Override
524         public void connect(
525                 final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
526             // Don't mark the connect promise as uncancellable as in fact we can cancel it as it is using
527             // non-blocking io.
528             if (promise.isDone() || !ensureOpen(promise)) {
529                 return;
530             }
531 
532             try {
533                 if (connectPromise != null) {
534                     throw new ConnectionPendingException();
535                 }
536 
537                 boolean wasActive = isActive();
538                 if (doConnect(remoteAddress, localAddress)) {
539                     fulfillConnectPromise(promise, wasActive);
540                 } else {
541                     connectPromise = promise;
542                     requestedRemoteAddress = remoteAddress;
543 
544                     // Schedule connect timeout.
545                     final int connectTimeoutMillis = config().getConnectTimeoutMillis();
546                     if (connectTimeoutMillis > 0) {
547                         connectTimeoutFuture = eventLoop().schedule(new Runnable() {
548                             @Override
549                             public void run() {
550                                 ChannelPromise connectPromise = AbstractKQueueChannel.this.connectPromise;
551                                 if (connectPromise != null && !connectPromise.isDone()
552                                         && connectPromise.tryFailure(new ConnectTimeoutException(
553                                                 "connection timed out after " + connectTimeoutMillis + " ms: " +
554                                                         remoteAddress))) {
555                                     close(voidPromise());
556                                 }
557                             }
558                         }, connectTimeoutMillis, TimeUnit.MILLISECONDS);
559                     }
560 
561                     promise.addListener(new ChannelFutureListener() {
562                         @Override
563                         public void operationComplete(ChannelFuture future) {
564                             // If the connect future is cancelled we also cancel the timeout and close the
565                             // underlying socket.
566                             if (future.isCancelled()) {
567                                 if (connectTimeoutFuture != null) {
568                                     connectTimeoutFuture.cancel(false);
569                                 }
570                                 connectPromise = null;
571                                 close(voidPromise());
572                             }
573                         }
574                     });
575                 }
576             } catch (Throwable t) {
577                 closeIfClosed();
578                 promise.tryFailure(annotateConnectException(t, remoteAddress));
579             }
580         }
581 
582         private void fulfillConnectPromise(ChannelPromise promise, boolean wasActive) {
583             if (promise == null) {
584                 // Closed via cancellation and the promise has been notified already.
585                 return;
586             }
587             active = true;
588 
589             // Get the state as trySuccess() may trigger an ChannelFutureListener that will close the Channel.
590             // We still need to ensure we call fireChannelActive() in this case.
591             boolean active = isActive();
592 
593             // trySuccess() will return false if a user cancelled the connection attempt.
594             boolean promiseSet = promise.trySuccess();
595 
596             // Regardless if the connection attempt was cancelled, channelActive() event should be triggered,
597             // because what happened is what happened.
598             if (!wasActive && active) {
599                 pipeline().fireChannelActive();
600             }
601 
602             // If a user cancelled the connection attempt, close the channel, which is followed by channelInactive().
603             if (!promiseSet) {
604                 close(voidPromise());
605             }
606         }
607 
608         private void fulfillConnectPromise(ChannelPromise promise, Throwable cause) {
609             if (promise == null) {
610                 // Closed via cancellation and the promise has been notified already.
611                 return;
612             }
613 
614             // Use tryFailure() instead of setFailure() to avoid the race against cancel().
615             promise.tryFailure(cause);
616             closeIfClosed();
617         }
618 
619         private void finishConnect() {
620             // Note this method is invoked by the event loop only if the connection attempt was
621             // neither cancelled nor timed out.
622 
623             assert eventLoop().inEventLoop();
624 
625             boolean connectStillInProgress = false;
626             try {
627                 boolean wasActive = isActive();
628                 if (!doFinishConnect()) {
629                     connectStillInProgress = true;
630                     return;
631                 }
632                 fulfillConnectPromise(connectPromise, wasActive);
633             } catch (Throwable t) {
634                 fulfillConnectPromise(connectPromise, annotateConnectException(t, requestedRemoteAddress));
635             } finally {
636                 if (!connectStillInProgress) {
637                     // Check for null as the connectTimeoutFuture is only created if a connectTimeoutMillis > 0 is used
638                     // See https://github.com/netty/netty/issues/1770
639                     if (connectTimeoutFuture != null) {
640                         connectTimeoutFuture.cancel(false);
641                     }
642                     connectPromise = null;
643                 }
644             }
645         }
646 
647         private boolean doFinishConnect() throws Exception {
648             if (socket.finishConnect()) {
649                 writeFilter(false);
650                 if (requestedRemoteAddress instanceof InetSocketAddress) {
651                     remote = computeRemoteAddr((InetSocketAddress) requestedRemoteAddress, socket.remoteAddress());
652                 }
653                 requestedRemoteAddress = null;
654                 return true;
655             }
656             writeFilter(true);
657             return false;
658         }
659     }
660 
661     @Override
662     protected void doBind(SocketAddress local) throws Exception {
663         if (local instanceof InetSocketAddress) {
664             checkResolvable((InetSocketAddress) local);
665         }
666         socket.bind(local);
667         this.local = socket.localAddress();
668     }
669 
670     /**
671      * Connect to the remote peer
672      */
673     protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
674         if (localAddress instanceof InetSocketAddress) {
675             checkResolvable((InetSocketAddress) localAddress);
676         }
677 
678         InetSocketAddress remoteSocketAddr = remoteAddress instanceof InetSocketAddress
679                 ? (InetSocketAddress) remoteAddress : null;
680         if (remoteSocketAddr != null) {
681             checkResolvable(remoteSocketAddr);
682         }
683 
684         if (remote != null) {
685             // Check if already connected before trying to connect. This is needed as connect(...) will not return -1
686             // and set errno to EISCONN if a previous connect(...) attempt was setting errno to EINPROGRESS and finished
687             // later.
688             throw new AlreadyConnectedException();
689         }
690 
691         if (localAddress != null) {
692             socket.bind(localAddress);
693         }
694 
695         boolean connected = doConnect0(remoteAddress, localAddress);
696         if (connected) {
697             remote = remoteSocketAddr == null?
698                     remoteAddress : computeRemoteAddr(remoteSocketAddr, socket.remoteAddress());
699         }
700         // We always need to set the localAddress even if not connected yet as the bind already took place.
701         //
702         // See https://github.com/netty/netty/issues/3463
703         local = socket.localAddress();
704         return connected;
705     }
706 
707     protected boolean doConnect0(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
708         boolean success = false;
709         try {
710             boolean connected = socket.connect(remoteAddress);
711             if (!connected) {
712                 writeFilter(true);
713             }
714             success = true;
715             return connected;
716         } finally {
717             if (!success) {
718                 doClose();
719             }
720         }
721     }
722 
723     @Override
724     protected SocketAddress localAddress0() {
725         return local;
726     }
727 
728     @Override
729     protected SocketAddress remoteAddress0() {
730         return remote;
731     }
732 }