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