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             unsafe().close(unsafe().voidPromise());
317         } else if (connectPromise != null) {
318             // The accepted channel was closed before the connection was established, for example because the server
319             // channel was closed. It is closed already, so don't notify it again. Fail the connect like a refused
320             // connection and close this channel.
321             ChannelPromise promise = connectPromise;
322             connectPromise = null;
323             peer = null;
324             promise.tryFailure(new ConnectException("connection refused: the accepted channel was closed"));
325             unsafe().close(unsafe().voidPromise());
326         } else {
327             releaseInboundBuffers();
328         }
329     }
330 
331     @Override
332     protected void doDeregister() throws Exception {
333         // Just remove the shutdownHook as this Channel may be closed later or registered to another EventLoop
334         ((SingleThreadEventExecutor) eventLoop()).removeShutdownHook(shutdownHook);
335     }
336 
337     private void readInbound() {
338         RecvByteBufAllocator.Handle handle = unsafe().recvBufAllocHandle();
339         handle.reset(config());
340         ChannelPipeline pipeline = pipeline();
341         do {
342             Object received = inboundBuffer.poll();
343             if (received == null) {
344                 break;
345             }
346             if (received instanceof ByteBuf && inboundBuffer.peek() instanceof ByteBuf) {
347                 ByteBuf msg = (ByteBuf) received;
348                 ByteBuf output = handle.allocate(alloc());
349                 if (msg.readableBytes() < output.writableBytes()) {
350                     // We have an opportunity to coalesce buffers.
351                     output.writeBytes(msg, msg.readerIndex(), msg.readableBytes());
352                     msg.release();
353                     while ((received = inboundBuffer.peek()) instanceof ByteBuf &&
354                             ((ByteBuf) received).readableBytes() < output.writableBytes()) {
355                         inboundBuffer.poll();
356                         msg = (ByteBuf) received;
357                         output.writeBytes(msg, msg.readerIndex(), msg.readableBytes());
358                         msg.release();
359                     }
360                     handle.lastBytesRead(output.readableBytes());
361                     received = output; // Send the coalesced buffer down the pipeline.
362                 } else {
363                     // It won't be profitable to coalesce buffers this time around.
364                     handle.lastBytesRead(output.capacity());
365                     output.release();
366                 }
367             }
368             handle.incMessagesRead(1);
369             pipeline.fireChannelRead(received);
370         } while (handle.continueReading());
371         handle.readComplete();
372         pipeline.fireChannelReadComplete();
373     }
374 
375     @Override
376     protected void doBeginRead() throws Exception {
377         if (readInProgress) {
378             return;
379         }
380 
381         Queue<Object> inboundBuffer = this.inboundBuffer;
382         if (inboundBuffer.isEmpty()) {
383             readInProgress = true;
384             return;
385         }
386 
387         final InternalThreadLocalMap threadLocals = InternalThreadLocalMap.get();
388         final int stackDepth = threadLocals.localChannelReaderStackDepth();
389         if (stackDepth < MAX_READER_STACK_DEPTH) {
390             threadLocals.setLocalChannelReaderStackDepth(stackDepth + 1);
391             try {
392                 readInbound();
393             } finally {
394                 threadLocals.setLocalChannelReaderStackDepth(stackDepth);
395             }
396         } else {
397             try {
398                 eventLoop().execute(readTask);
399             } catch (Throwable cause) {
400                 logger.warn("Closing Local channels {}-{} because exception occurred!", this, peer, cause);
401                 close();
402                 peer.close();
403                 PlatformDependent.throwException(cause);
404             }
405         }
406     }
407 
408     @Override
409     protected void doWrite(ChannelOutboundBuffer in) throws Exception {
410         switch (state) {
411         case OPEN:
412         case BOUND:
413             throw new NotYetConnectedException();
414         case CLOSED:
415             throw new ClosedChannelException();
416         case CONNECTED:
417             break;
418         }
419 
420         final LocalChannel peer = this.peer;
421 
422         writeInProgress = true;
423         try {
424             ClosedChannelException exception = null;
425             for (;;) {
426                 Object msg = in.current();
427                 if (msg == null) {
428                     break;
429                 }
430                 try {
431                     // It is possible the peer could have closed while we are writing, and in this case we should
432                     // simulate real socket behavior and ensure the write operation is failed.
433                     if (peer.state == State.CONNECTED) {
434                         peer.inboundBuffer.add(ReferenceCountUtil.retain(msg));
435                         in.remove();
436                     } else {
437                         if (exception == null) {
438                             exception = new ClosedChannelException();
439                         }
440                         in.remove(exception);
441                     }
442                 } catch (Throwable cause) {
443                     in.remove(cause);
444                 }
445             }
446         } finally {
447             // The following situation may cause trouble:
448             // 1. Write (with promise X)
449             // 2. promise X is completed when in.remove() is called, and a listener on this promise calls close()
450             // 3. Then the close event will be executed for the peer before the write events, when the write events
451             // actually happened before the close event.
452             writeInProgress = false;
453         }
454 
455         finishPeerRead(peer);
456     }
457 
458     private void finishPeerRead(final LocalChannel peer) {
459         // If the peer is also writing, then we must schedule the event on the event loop to preserve read order.
460         if (peer.eventLoop() == eventLoop() && !peer.writeInProgress) {
461             finishPeerRead0(peer);
462         } else {
463             runFinishPeerReadTask(peer);
464         }
465     }
466 
467     private void runFinishTask0() {
468         // If the peer is writing, we must wait until after reads are completed for that peer before we can read. So
469         // we keep track of the task, and coordinate later that our read can't happen until the peer is done.
470         if (writeInProgress) {
471             finishReadFuture = eventLoop().submit(finishReadTask);
472         } else {
473             eventLoop().execute(finishReadTask);
474         }
475     }
476 
477     private void runFinishPeerReadTask(final LocalChannel peer) {
478         try {
479             peer.runFinishTask0();
480         } catch (Throwable cause) {
481             logger.warn("Closing Local channels {}-{} because exception occurred!", this, peer, cause);
482             close();
483             peer.close();
484             PlatformDependent.throwException(cause);
485         }
486     }
487 
488     private void releaseInboundBuffers() {
489         assert eventLoop() == null || eventLoop().inEventLoop();
490         readInProgress = false;
491         Queue<Object> inboundBuffer = this.inboundBuffer;
492         Object msg;
493         while ((msg = inboundBuffer.poll()) != null) {
494             ReferenceCountUtil.release(msg);
495         }
496     }
497 
498     private void finishPeerRead0(LocalChannel peer) {
499         Future<?> peerFinishReadFuture = peer.finishReadFuture;
500         if (peerFinishReadFuture != null) {
501             if (!peerFinishReadFuture.isDone()) {
502                 runFinishPeerReadTask(peer);
503                 return;
504             } else {
505                 // Lazy unset to make sure we don't prematurely unset it while scheduling a new task.
506                 FINISH_READ_FUTURE_UPDATER.compareAndSet(peer, peerFinishReadFuture, null);
507             }
508         }
509         // We should only set readInProgress to false if there is any data that was read as otherwise we may miss to
510         // forward data later on.
511         if (peer.readInProgress && !peer.inboundBuffer.isEmpty()) {
512             peer.readInProgress = false;
513             peer.readInbound();
514         }
515     }
516 
517     private class LocalUnsafe extends AbstractUnsafe {
518 
519         @Override
520         public void connect(final SocketAddress remoteAddress,
521                 SocketAddress localAddress, final ChannelPromise promise) {
522             if (!promise.setUncancellable() || !ensureOpen(promise)) {
523                 return;
524             }
525 
526             if (state == State.CONNECTED) {
527                 Exception cause = new AlreadyConnectedException();
528                 safeSetFailure(promise, cause);
529                 return;
530             }
531 
532             if (connectPromise != null) {
533                 throw new ConnectionPendingException();
534             }
535 
536             connectPromise = promise;
537 
538             if (state != State.BOUND) {
539                 // Not bound yet and no localAddress specified - get one.
540                 if (localAddress == null) {
541                     localAddress = new LocalAddress(LocalChannel.this);
542                 }
543             }
544 
545             if (localAddress != null) {
546                 try {
547                     doBind(localAddress);
548                 } catch (Throwable t) {
549                     safeSetFailure(promise, t);
550                     close(voidPromise());
551                     return;
552                 }
553             }
554 
555             Channel boundChannel = LocalChannelRegistry.get(remoteAddress);
556             if (!(boundChannel instanceof LocalServerChannel)) {
557                 Exception cause = new ConnectException("connection refused: " + remoteAddress);
558                 safeSetFailure(promise, cause);
559                 close(voidPromise());
560                 return;
561             }
562 
563             LocalServerChannel serverChannel = (LocalServerChannel) boundChannel;
564             peer = serverChannel.serve(LocalChannel.this);
565         }
566     }
567 }