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.local;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.channel.AbstractChannel;
20  import io.netty.channel.Channel;
21  import io.netty.channel.ChannelConfig;
22  import io.netty.channel.ChannelMetadata;
23  import io.netty.channel.ChannelOutboundBuffer;
24  import io.netty.channel.ChannelPipeline;
25  import io.netty.channel.ChannelPromise;
26  import io.netty.channel.DefaultChannelConfig;
27  import io.netty.channel.EventLoop;
28  import io.netty.channel.IoEvent;
29  import io.netty.channel.IoEventLoop;
30  import io.netty.channel.IoRegistration;
31  import io.netty.channel.PreferHeapByteBufAllocator;
32  import io.netty.channel.RecvByteBufAllocator;
33  import io.netty.channel.SingleThreadEventLoop;
34  import io.netty.util.ReferenceCountUtil;
35  import io.netty.util.concurrent.Future;
36  import io.netty.util.concurrent.SingleThreadEventExecutor;
37  import io.netty.util.internal.InternalThreadLocalMap;
38  import io.netty.util.internal.PlatformDependent;
39  import io.netty.util.internal.logging.InternalLogger;
40  import io.netty.util.internal.logging.InternalLoggerFactory;
41  
42  import java.net.ConnectException;
43  import java.net.SocketAddress;
44  import java.nio.channels.AlreadyConnectedException;
45  import java.nio.channels.ClosedChannelException;
46  import java.nio.channels.ConnectionPendingException;
47  import java.nio.channels.NotYetConnectedException;
48  import java.util.Queue;
49  import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
50  
51  /**
52   * A {@link Channel} for the local transport.
53   */
54  public class LocalChannel extends AbstractChannel {
55      private static final InternalLogger logger = InternalLoggerFactory.getInstance(LocalChannel.class);
56      @SuppressWarnings({ "rawtypes" })
57      private static final AtomicReferenceFieldUpdater<LocalChannel, Future> FINISH_READ_FUTURE_UPDATER =
58              AtomicReferenceFieldUpdater.newUpdater(LocalChannel.class, Future.class, "finishReadFuture");
59      private static final AtomicReferenceFieldUpdater<LocalChannel, State> STATE_UPDATER =
60              AtomicReferenceFieldUpdater.newUpdater(LocalChannel.class, State.class, "state");
61      private static final ChannelMetadata METADATA = new ChannelMetadata(false);
62      private static final int MAX_READER_STACK_DEPTH = 8;
63  
64      private enum State { OPEN, BOUND, CONNECTED, CLOSED }
65  
66      private final ChannelConfig config = new DefaultChannelConfig(this);
67      // To further optimize this we could write our own SPSC queue.
68      final Queue<Object> inboundBuffer = PlatformDependent.newSpscQueue();
69      private final Runnable readTask = new Runnable() {
70          @Override
71          public void run() {
72              // Ensure the inboundBuffer is not empty as readInbound() will always call fireChannelReadComplete()
73              if (!inboundBuffer.isEmpty()) {
74                  readInbound();
75              }
76          }
77      };
78  
79      private final Runnable finishReadTask = new Runnable() {
80          @Override
81          public void run() {
82              finishPeerRead0(LocalChannel.this);
83          }
84      };
85  
86      private IoRegistration registration;
87  
88      private volatile State state;
89      private volatile LocalChannel peer;
90      private volatile LocalAddress localAddress;
91      private volatile LocalAddress remoteAddress;
92      private volatile ChannelPromise connectPromise;
93      private volatile boolean readInProgress;
94      private volatile boolean writeInProgress;
95      private volatile Future<?> finishReadFuture;
96  
97      public LocalChannel() {
98          super(null);
99          config().setAllocator(new PreferHeapByteBufAllocator(config.getAllocator()));
100     }
101 
102     protected LocalChannel(LocalServerChannel parent, LocalChannel peer) {
103         super(parent);
104         config().setAllocator(new PreferHeapByteBufAllocator(config.getAllocator()));
105         this.peer = peer;
106         localAddress = parent.localAddress();
107         remoteAddress = peer.localAddress();
108     }
109 
110     @Override
111     public ChannelMetadata metadata() {
112         return METADATA;
113     }
114 
115     @Override
116     public ChannelConfig config() {
117         return config;
118     }
119 
120     @Override
121     public LocalServerChannel parent() {
122         return (LocalServerChannel) super.parent();
123     }
124 
125     @Override
126     public LocalAddress localAddress() {
127         return (LocalAddress) super.localAddress();
128     }
129 
130     @Override
131     public LocalAddress remoteAddress() {
132         return (LocalAddress) super.remoteAddress();
133     }
134 
135     @Override
136     public boolean isOpen() {
137         return state != State.CLOSED;
138     }
139 
140     @Override
141     public boolean isActive() {
142         return state == State.CONNECTED;
143     }
144 
145     @Override
146     protected AbstractUnsafe newUnsafe() {
147         return new LocalUnsafe();
148     }
149 
150     @Override
151     protected boolean isCompatible(EventLoop loop) {
152         return loop instanceof SingleThreadEventLoop ||
153                 (loop instanceof IoEventLoop && ((IoEventLoop) loop).isCompatible(LocalUnsafe.class));
154     }
155 
156     @Override
157     protected SocketAddress localAddress0() {
158         return localAddress;
159     }
160 
161     @Override
162     protected SocketAddress remoteAddress0() {
163         return remoteAddress;
164     }
165 
166     @Override
167     protected void doRegister(ChannelPromise promise) {
168         EventLoop loop = eventLoop();
169         if (loop instanceof IoEventLoop) {
170             assert registration == null;
171             ((IoEventLoop) loop).register((LocalUnsafe) unsafe()).addListener(f -> {
172                if (f.isSuccess()) {
173                    registration = (IoRegistration) f.getNow();
174                    promise.setSuccess();
175                } else {
176                    promise.setFailure(f.cause());
177                }
178             });
179         } else {
180             try {
181                 ((LocalUnsafe) unsafe()).registered();
182             } catch (Throwable cause) {
183                 promise.setFailure(cause);
184             }
185             promise.setSuccess();
186         }
187     }
188 
189     @Override
190     protected void doDeregister() throws Exception {
191         EventLoop loop = eventLoop();
192         if (loop instanceof IoEventLoop) {
193             IoRegistration registration = this.registration;
194             if (registration != null) {
195                 this.registration = null;
196                 registration.cancel();
197             }
198         } else {
199             ((LocalUnsafe) unsafe()).unregistered();
200         }
201     }
202 
203     @Override
204     protected void doBind(SocketAddress localAddress) throws Exception {
205         this.localAddress =
206                 LocalChannelRegistry.register(this, this.localAddress,
207                         localAddress);
208         state = State.BOUND;
209     }
210 
211     @Override
212     protected void doDisconnect() throws Exception {
213         doClose();
214     }
215 
216     @Override
217     protected void doClose() throws Exception {
218         final LocalChannel peer = this.peer;
219         State oldState = state;
220         State closedFrom = oldState;
221         try {
222             if (oldState != State.CLOSED) {
223                 // Update all internal state before the closeFuture is notified.
224                 if (localAddress != null) {
225                     if (parent() == null) {
226                         LocalChannelRegistry.unregister(localAddress);
227                     }
228                     localAddress = null;
229                 }
230 
231                 // State change must happen before finishPeerRead to ensure writes are released either in doWrite or
232                 // channelRead.
233                 // Use getAndSet() as an accepted channel may concurrently move this channel from BOUND to CONNECTED
234                 // when it is registered. See LocalUnsafe.registered().
235                 closedFrom = STATE_UPDATER.getAndSet(this, State.CLOSED);
236 
237                 // Preserve order of event and force a read operation now before the close operation is processed.
238                 if (writeInProgress && peer != null) {
239                     finishPeerRead(peer);
240                 }
241 
242                 ChannelPromise promise = connectPromise;
243                 if (promise != null) {
244                     // Use tryFailure() instead of setFailure() to avoid the race against cancel().
245                     promise.tryFailure(new ClosedChannelException());
246                     connectPromise = null;
247                 }
248             }
249 
250             if (peer != null && parent() == null && closedFrom != State.CONNECTED) {
251                 // The accepted channel did not complete the connection. Don't touch it: it may not have an event
252                 // loop yet and is owned by the server channel or its own event loop. When it is registered it sees
253                 // that this channel is closed and closes itself. If it was closed already, its close has notified
254                 // this channel or will do so.
255                 this.peer = null;
256             } else if (peer != null) {
257                 this.peer = null;
258                 // Always call peer.eventLoop().execute() even if peer.eventLoop().inEventLoop() is true.
259                 // This ensures that if both channels are on the same event loop, the peer's channelInActive
260                 // event is triggered *after* this peer's channelInActive event
261                 EventLoop peerEventLoop = peer.eventLoop();
262                 final boolean peerIsActive = peer.isActive();
263                 try {
264                     peerEventLoop.execute(new Runnable() {
265                         @Override
266                         public void run() {
267                             peer.tryClose(peerIsActive);
268                         }
269                     });
270                 } catch (Throwable cause) {
271                     logger.warn("Releasing Inbound Queues for channels {}-{} because exception occurred!",
272                             this, peer, cause);
273                     if (peerEventLoop.inEventLoop()) {
274                         peer.releaseInboundBuffers();
275                     } else {
276                         // inboundBuffers is a SPSC so we may leak if the event loop is shutdown prematurely or
277                         // rejects the close Runnable but give a best effort.
278                         peer.close();
279                     }
280                     PlatformDependent.throwException(cause);
281                 }
282             }
283         } finally {
284             // Release all buffers if the Channel was already registered in the past and if it was not closed before.
285             if (oldState != null && oldState != State.CLOSED) {
286                 // We need to release all the buffers that may be put into our inbound queue since we closed the Channel
287                 // to ensure we not leak any memory. This is fine as it basically gives the same guarantees as TCP which
288                 // means even if the promise was notified before its not really guaranteed that the "remote peer" will
289                 // see the buffer at all.
290                 releaseInboundBuffers();
291             }
292         }
293     }
294 
295     private void tryClose(boolean isActive) {
296         if (isActive) {
297             unsafe().close(unsafe().voidPromise());
298         } else if (connectPromise != null) {
299             // The accepted channel was closed before the connection was established, for example because the server
300             // channel was closed. It is closed already, so don't notify it again. Fail the connect like a refused
301             // connection and close this channel.
302             ChannelPromise promise = connectPromise;
303             connectPromise = null;
304             peer = null;
305             promise.tryFailure(new ConnectException("connection refused: the accepted channel was closed"));
306             unsafe().close(unsafe().voidPromise());
307         } else {
308             releaseInboundBuffers();
309         }
310     }
311 
312     private void readInbound() {
313         RecvByteBufAllocator.Handle handle = unsafe().recvBufAllocHandle();
314         handle.reset(config());
315         ChannelPipeline pipeline = pipeline();
316         do {
317             Object received = inboundBuffer.poll();
318             if (received == null) {
319                 break;
320             }
321             if (received instanceof ByteBuf && inboundBuffer.peek() instanceof ByteBuf) {
322                 ByteBuf msg = (ByteBuf) received;
323                 ByteBuf output = handle.allocate(alloc());
324                 if (msg.readableBytes() < output.writableBytes()) {
325                     // We have an opportunity to coalesce buffers.
326                     output.writeBytes(msg, msg.readerIndex(), msg.readableBytes());
327                     msg.release();
328                     while ((received = inboundBuffer.peek()) instanceof ByteBuf &&
329                             ((ByteBuf) received).readableBytes() < output.writableBytes()) {
330                         inboundBuffer.poll();
331                         msg = (ByteBuf) received;
332                         output.writeBytes(msg, msg.readerIndex(), msg.readableBytes());
333                         msg.release();
334                     }
335                     handle.lastBytesRead(output.readableBytes());
336                     received = output; // Send the coalesced buffer down the pipeline.
337                 } else {
338                     // It won't be profitable to coalesce buffers this time around.
339                     handle.lastBytesRead(output.capacity());
340                     output.release();
341                 }
342             }
343             handle.incMessagesRead(1);
344             pipeline.fireChannelRead(received);
345         } while (handle.continueReading());
346         handle.readComplete();
347         pipeline.fireChannelReadComplete();
348     }
349 
350     @Override
351     protected void doBeginRead() throws Exception {
352         if (readInProgress) {
353             return;
354         }
355 
356         Queue<Object> inboundBuffer = this.inboundBuffer;
357         if (inboundBuffer.isEmpty()) {
358             readInProgress = true;
359             return;
360         }
361 
362         final InternalThreadLocalMap threadLocals = InternalThreadLocalMap.get();
363         final int stackDepth = threadLocals.localChannelReaderStackDepth();
364         if (stackDepth < MAX_READER_STACK_DEPTH) {
365             threadLocals.setLocalChannelReaderStackDepth(stackDepth + 1);
366             try {
367                 readInbound();
368             } finally {
369                 threadLocals.setLocalChannelReaderStackDepth(stackDepth);
370             }
371         } else {
372             try {
373                 eventLoop().execute(readTask);
374             } catch (Throwable cause) {
375                 logger.warn("Closing Local channels {}-{} because exception occurred!", this, peer, cause);
376                 close();
377                 peer.close();
378                 PlatformDependent.throwException(cause);
379             }
380         }
381     }
382 
383     @Override
384     protected void doWrite(ChannelOutboundBuffer in) throws Exception {
385         switch (state) {
386         case OPEN:
387         case BOUND:
388             throw new NotYetConnectedException();
389         case CLOSED:
390             throw new ClosedChannelException();
391         case CONNECTED:
392             break;
393         }
394 
395         final LocalChannel peer = this.peer;
396 
397         writeInProgress = true;
398         try {
399             ClosedChannelException exception = null;
400             for (;;) {
401                 Object msg = in.current();
402                 if (msg == null) {
403                     break;
404                 }
405                 try {
406                     // It is possible the peer could have closed while we are writing, and in this case we should
407                     // simulate real socket behavior and ensure the write operation is failed.
408                     if (peer.state == State.CONNECTED) {
409                         peer.inboundBuffer.add(ReferenceCountUtil.retain(msg));
410                         in.remove();
411                     } else {
412                         if (exception == null) {
413                             exception = new ClosedChannelException();
414                         }
415                         in.remove(exception);
416                     }
417                 } catch (Throwable cause) {
418                     in.remove(cause);
419                 }
420             }
421         } finally {
422             // The following situation may cause trouble:
423             // 1. Write (with promise X)
424             // 2. promise X is completed when in.remove() is called, and a listener on this promise calls close()
425             // 3. Then the close event will be executed for the peer before the write events, when the write events
426             // actually happened before the close event.
427             writeInProgress = false;
428         }
429 
430         finishPeerRead(peer);
431     }
432 
433     private void finishPeerRead(final LocalChannel peer) {
434         // If the peer is also writing, then we must schedule the event on the event loop to preserve read order.
435         if (peer.eventLoop() == eventLoop() && !peer.writeInProgress) {
436             finishPeerRead0(peer);
437         } else {
438             runFinishPeerReadTask(peer);
439         }
440     }
441 
442     private void runFinishTask0() {
443         // If the peer is writing, we must wait until after reads are completed for that peer before we can read. So
444         // we keep track of the task, and coordinate later that our read can't happen until the peer is done.
445         if (writeInProgress) {
446             finishReadFuture = eventLoop().submit(finishReadTask);
447         } else {
448             eventLoop().execute(finishReadTask);
449         }
450     }
451 
452     private void runFinishPeerReadTask(final LocalChannel peer) {
453         try {
454             peer.runFinishTask0();
455         } catch (Throwable cause) {
456             logger.warn("Closing Local channels {}-{} because exception occurred!", this, peer, cause);
457             close();
458             peer.close();
459             PlatformDependent.throwException(cause);
460         }
461     }
462 
463     private void releaseInboundBuffers() {
464         assert eventLoop() == null || eventLoop().inEventLoop();
465         readInProgress = false;
466         Queue<Object> inboundBuffer = this.inboundBuffer;
467         Object msg;
468         while ((msg = inboundBuffer.poll()) != null) {
469             ReferenceCountUtil.release(msg);
470         }
471     }
472 
473     private void finishPeerRead0(LocalChannel peer) {
474         Future<?> peerFinishReadFuture = peer.finishReadFuture;
475         if (peerFinishReadFuture != null) {
476             if (!peerFinishReadFuture.isDone()) {
477                 runFinishPeerReadTask(peer);
478                 return;
479             } else {
480                 // Lazy unset to make sure we don't prematurely unset it while scheduling a new task.
481                 FINISH_READ_FUTURE_UPDATER.compareAndSet(peer, peerFinishReadFuture, null);
482             }
483         }
484         // We should only set readInProgress to false if there is any data that was read as otherwise we may miss to
485         // forward data later on.
486         if (peer.readInProgress && !peer.inboundBuffer.isEmpty()) {
487             peer.readInProgress = false;
488             peer.readInbound();
489         }
490     }
491 
492     private class LocalUnsafe extends AbstractUnsafe implements LocalIoHandle {
493 
494         private final Runnable shutdownHook = this::closeNow;
495 
496         @Override
497         public void close() {
498             close(voidPromise());
499         }
500 
501         @Override
502         public void handle(IoRegistration registration, IoEvent event) {
503             // NOOP
504         }
505 
506         @Override
507         public void registered() {
508             // Check if both peer and parent are non-null because this channel was created by a LocalServerChannel.
509             // This is needed as a peer may not be null also if a LocalChannel was connected before and
510             // deregistered / registered later again.
511             //
512             // See https://github.com/netty/netty/issues/2400
513             //
514             // Store the peer in a local variable as it may be set to null if doClose() is called.
515             // See https://github.com/netty/netty/issues/2144
516             final LocalChannel peer = LocalChannel.this.peer;
517             if (peer != null && parent() != null) {
518                 // Set our state before the peer's, so the peer sees this channel as active once it is connected.
519                 State oldState = state;
520                 state = State.CONNECTED;
521 
522                 // Move the peer from BOUND to CONNECTED atomically, as it may be closed concurrently on its own
523                 // event loop. If this channel was connected before and is registered again, the peer is CONNECTED
524                 // already.
525                 if (STATE_UPDATER.compareAndSet(peer, State.BOUND, State.CONNECTED) ||
526                         peer.state == State.CONNECTED) {
527                     peer.remoteAddress = parent().localAddress();
528 
529                     // Always call peer.eventLoop().execute() even if peer.eventLoop().inEventLoop() is true.
530                     // This ensures that if both channels are on the same event loop, the peer's channelActive
531                     // event is triggered *after* this channel's channelRegistered event, so that this channel's
532                     // pipeline is fully initialized by ChannelInitializer before any channelRead events.
533                     peer.eventLoop().execute(new Runnable() {
534                         @Override
535                         public void run() {
536                             ChannelPromise promise = peer.connectPromise;
537 
538                             // Only trigger fireChannelActive() if the promise was not null and was not completed
539                             // yet. connectPromise may be set to null if doClose() was called in the meantime.
540                             if (promise != null && promise.trySuccess()) {
541                                 peer.pipeline().fireChannelActive();
542                             }
543                         }
544                     });
545                 } else {
546                     // The peer was closed before this channel was registered, so it never uses this channel.
547                     // Close this channel once the registration is complete.
548                     LocalChannel.this.peer = null;
549                     state = oldState;
550                     eventLoop().execute(this::closeNow);
551                 }
552             }
553             EventLoop loop = eventLoop();
554             if (!(loop instanceof IoEventLoop) && loop instanceof SingleThreadEventExecutor) {
555                 ((SingleThreadEventExecutor) eventLoop()).addShutdownHook(shutdownHook);
556             }
557         }
558 
559         @Override
560         public void unregistered() {
561             EventLoop loop = eventLoop();
562             if (!(loop instanceof IoEventLoop) && loop instanceof SingleThreadEventExecutor) {
563                 ((SingleThreadEventExecutor) eventLoop()).removeShutdownHook(shutdownHook);
564             }
565         }
566 
567         @Override
568         public void closeNow() {
569             close(voidPromise());
570         }
571 
572         @Override
573         public void connect(final SocketAddress remoteAddress,
574                 SocketAddress localAddress, final ChannelPromise promise) {
575             if (!promise.setUncancellable() || !ensureOpen(promise)) {
576                 return;
577             }
578 
579             if (state == State.CONNECTED) {
580                 Exception cause = new AlreadyConnectedException();
581                 safeSetFailure(promise, cause);
582                 return;
583             }
584 
585             if (connectPromise != null) {
586                 throw new ConnectionPendingException();
587             }
588 
589             connectPromise = promise;
590 
591             if (state != State.BOUND) {
592                 // Not bound yet and no localAddress specified - get one.
593                 if (localAddress == null) {
594                     localAddress = new LocalAddress(LocalChannel.this);
595                 }
596             }
597 
598             if (localAddress != null) {
599                 try {
600                     doBind(localAddress);
601                 } catch (Throwable t) {
602                     safeSetFailure(promise, t);
603                     close(voidPromise());
604                     return;
605                 }
606             }
607 
608             Channel boundChannel = LocalChannelRegistry.get(remoteAddress);
609             if (!(boundChannel instanceof LocalServerChannel)) {
610                 Exception cause = new ConnectException("connection refused: " + remoteAddress);
611                 safeSetFailure(promise, cause);
612                 close(voidPromise());
613                 return;
614             }
615 
616             LocalServerChannel serverChannel = (LocalServerChannel) boundChannel;
617             peer = serverChannel.serve(LocalChannel.this);
618         }
619     }
620 }