package io.netty.channel.local;
import io.netty.channel.AbstractChannel;
import io.netty.channel.Channel;
import io.netty.channel.ChannelConfig;
import io.netty.channel.ChannelMetadata;
import io.netty.channel.ChannelOutboundBuffer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPromise;
import io.netty.channel.DefaultChannelConfig;
import io.netty.channel.EventLoop;
import io.netty.channel.PreferHeapByteBufAllocator;
import io.netty.channel.RecvByteBufAllocator;
import io.netty.channel.SingleThreadEventLoop;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.SingleThreadEventExecutor;
import io.netty.util.internal.InternalThreadLocalMap;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.ThrowableUtil;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import java.net.ConnectException;
import java.net.SocketAddress;
import java.nio.channels.AlreadyConnectedException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ConnectionPendingException;
import java.nio.channels.NotYetConnectedException;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
public class LocalChannel extends AbstractChannel {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(LocalChannel.class);
@SuppressWarnings({ "rawtypes" })
private static final AtomicReferenceFieldUpdater<LocalChannel, Future> FINISH_READ_FUTURE_UPDATER =
AtomicReferenceFieldUpdater.newUpdater(LocalChannel.class, Future.class, "finishReadFuture");
private static final ChannelMetadata METADATA = new ChannelMetadata(false);
private static final int MAX_READER_STACK_DEPTH = 8;
private static final ClosedChannelException DO_WRITE_CLOSED_CHANNEL_EXCEPTION = ThrowableUtil.unknownStackTrace(
new ClosedChannelException(), LocalChannel.class, "doWrite(...)");
private static final ClosedChannelException DO_CLOSE_CLOSED_CHANNEL_EXCEPTION = ThrowableUtil.unknownStackTrace(
new ClosedChannelException(), LocalChannel.class, "doClose()");
private enum State { OPEN, BOUND, CONNECTED, CLOSED }
private final ChannelConfig config = new DefaultChannelConfig(this);
final Queue<Object> inboundBuffer = PlatformDependent.newSpscQueue();
private final Runnable readTask = new Runnable() {
@Override
public void run() {
if (!inboundBuffer.isEmpty()) {
readInbound();
}
}
};
private final Runnable shutdownHook = new Runnable() {
@Override
public void run() {
unsafe().close(unsafe().voidPromise());
}
};
private volatile State state;
private volatile LocalChannel peer;
private volatile LocalAddress localAddress;
private volatile LocalAddress remoteAddress;
private volatile ChannelPromise connectPromise;
private volatile boolean readInProgress;
private volatile boolean writeInProgress;
private volatile Future<?> finishReadFuture;
public LocalChannel() {
super(null);
config().setAllocator(new PreferHeapByteBufAllocator(config.getAllocator()));
}
protected LocalChannel(LocalServerChannel parent, LocalChannel peer) {
super(parent);
config().setAllocator(new PreferHeapByteBufAllocator(config.getAllocator()));
this.peer = peer;
localAddress = parent.localAddress();
remoteAddress = peer.localAddress();
}
@Override
public ChannelMetadata metadata() {
return METADATA;
}
@Override
public ChannelConfig config() {
return config;
}
@Override
public LocalServerChannel parent() {
return (LocalServerChannel) super.parent();
}
@Override
public LocalAddress localAddress() {
return (LocalAddress) super.localAddress();
}
@Override
public LocalAddress remoteAddress() {
return (LocalAddress) super.remoteAddress();
}
@Override
public boolean isOpen() {
return state != State.CLOSED;
}
@Override
public boolean isActive() {
return state == State.CONNECTED;
}
@Override
protected AbstractUnsafe newUnsafe() {
return new LocalUnsafe();
}
@Override
protected boolean isCompatible(EventLoop loop) {
return loop instanceof SingleThreadEventLoop;
}
@Override
protected SocketAddress localAddress0() {
return localAddress;
}
@Override
protected SocketAddress remoteAddress0() {
return remoteAddress;
}
@Override
protected void doRegister() throws Exception {
if (peer != null && parent() != null) {
final LocalChannel peer = this.peer;
state = State.CONNECTED;
peer.remoteAddress = parent() == null ? null : parent().localAddress();
peer.state = State.CONNECTED;
peer.eventLoop().execute(new Runnable() {
@Override
public void run() {
ChannelPromise promise = peer.connectPromise;
if (promise != null && promise.trySuccess()) {
peer.pipeline().fireChannelActive();
}
}
});
}
((SingleThreadEventExecutor) eventLoop()).addShutdownHook(shutdownHook);
}
@Override
protected void doBind(SocketAddress localAddress) throws Exception {
this.localAddress =
LocalChannelRegistry.register(this, this.localAddress,
localAddress);
state = State.BOUND;
}
@Override
protected void doDisconnect() throws Exception {
doClose();
}
@Override
protected void doClose() throws Exception {
final LocalChannel peer = this.peer;
State oldState = state;
try {
if (oldState != State.CLOSED) {
if (localAddress != null) {
if (parent() == null) {
LocalChannelRegistry.unregister(localAddress);
}
localAddress = null;
}
state = State.CLOSED;
if (writeInProgress && peer != null) {
finishPeerRead(peer);
}
ChannelPromise promise = connectPromise;
if (promise != null) {
promise.tryFailure(DO_CLOSE_CLOSED_CHANNEL_EXCEPTION);
connectPromise = null;
}
}
if (peer != null) {
this.peer = null;
EventLoop peerEventLoop = peer.eventLoop();
final boolean peerIsActive = peer.isActive();
try {
peerEventLoop.execute(new Runnable() {
@Override
public void run() {
peer.tryClose(peerIsActive);
}
});
} catch (Throwable cause) {
logger.warn("Releasing Inbound Queues for channels {}-{} because exception occurred!",
this, peer, cause);
if (peerEventLoop.inEventLoop()) {
peer.releaseInboundBuffers();
} else {
peer.close();
}
PlatformDependent.throwException(cause);
}
}
} finally {
if (oldState != null && oldState != State.CLOSED) {
releaseInboundBuffers();
}
}
}
private void tryClose(boolean isActive) {
if (isActive) {
unsafe().close(unsafe().voidPromise());
} else {
releaseInboundBuffers();
}
}
@Override
protected void doDeregister() throws Exception {
((SingleThreadEventExecutor) eventLoop()).removeShutdownHook(shutdownHook);
}
private void readInbound() {
RecvByteBufAllocator.Handle handle = unsafe().recvBufAllocHandle();
handle.reset(config());
ChannelPipeline pipeline = pipeline();
do {
Object received = inboundBuffer.poll();
if (received == null) {
break;
}
pipeline.fireChannelRead(received);
} while (handle.continueReading());
pipeline.fireChannelReadComplete();
}
@Override
protected void doBeginRead() throws Exception {
if (readInProgress) {
return;
}
Queue<Object> inboundBuffer = this.inboundBuffer;
if (inboundBuffer.isEmpty()) {
readInProgress = true;
return;
}
final InternalThreadLocalMap threadLocals = InternalThreadLocalMap.get();
final Integer stackDepth = threadLocals.localChannelReaderStackDepth();
if (stackDepth < MAX_READER_STACK_DEPTH) {
threadLocals.setLocalChannelReaderStackDepth(stackDepth + 1);
try {
readInbound();
} finally {
threadLocals.setLocalChannelReaderStackDepth(stackDepth);
}
} else {
try {
eventLoop().execute(readTask);
} catch (Throwable cause) {
logger.warn("Closing Local channels {}-{} because exception occurred!", this, peer, cause);
close();
peer.close();
PlatformDependent.throwException(cause);
}
}
}
@Override
protected void doWrite(ChannelOutboundBuffer in) throws Exception {
switch (state) {
case OPEN:
case BOUND:
throw new NotYetConnectedException();
case CLOSED:
throw DO_WRITE_CLOSED_CHANNEL_EXCEPTION;
case CONNECTED:
break;
}
final LocalChannel peer = this.peer;
writeInProgress = true;
try {
for (;;) {
Object msg = in.current();
if (msg == null) {
break;
}
try {
if (peer.state == State.CONNECTED) {
peer.inboundBuffer.add(ReferenceCountUtil.retain(msg));
in.remove();
} else {
in.remove(DO_WRITE_CLOSED_CHANNEL_EXCEPTION);
}
} catch (Throwable cause) {
in.remove(cause);
}
}
} finally {
writeInProgress = false;
}
finishPeerRead(peer);
}
private void finishPeerRead(final LocalChannel peer) {
if (peer.eventLoop() == eventLoop() && !peer.writeInProgress) {
finishPeerRead0(peer);
} else {
runFinishPeerReadTask(peer);
}
}
private void runFinishPeerReadTask(final LocalChannel peer) {
final Runnable finishPeerReadTask = new Runnable() {
@Override
public void run() {
finishPeerRead0(peer);
}
};
try {
if (peer.writeInProgress) {
peer.finishReadFuture = peer.eventLoop().submit(finishPeerReadTask);
} else {
peer.eventLoop().execute(finishPeerReadTask);
}
} catch (Throwable cause) {
logger.warn("Closing Local channels {}-{} because exception occurred!", this, peer, cause);
close();
peer.close();
PlatformDependent.throwException(cause);
}
}
private void releaseInboundBuffers() {
assert eventLoop() == null || eventLoop().inEventLoop();
readInProgress = false;
Queue<Object> inboundBuffer = this.inboundBuffer;
Object msg;
while ((msg = inboundBuffer.poll()) != null) {
ReferenceCountUtil.release(msg);
}
}
private void finishPeerRead0(LocalChannel peer) {
Future<?> peerFinishReadFuture = peer.finishReadFuture;
if (peerFinishReadFuture != null) {
if (!peerFinishReadFuture.isDone()) {
runFinishPeerReadTask(peer);
return;
} else {
FINISH_READ_FUTURE_UPDATER.compareAndSet(peer, peerFinishReadFuture, null);
}
}
if (peer.readInProgress && !peer.inboundBuffer.isEmpty()) {
peer.readInProgress = false;
peer.readInbound();
}
}
private class LocalUnsafe extends AbstractUnsafe {
@Override
public void connect(final SocketAddress remoteAddress,
SocketAddress localAddress, final ChannelPromise promise) {
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
}
if (state == State.CONNECTED) {
Exception cause = new AlreadyConnectedException();
safeSetFailure(promise, cause);
pipeline().fireExceptionCaught(cause);
return;
}
if (connectPromise != null) {
throw new ConnectionPendingException();
}
connectPromise = promise;
if (state != State.BOUND) {
if (localAddress == null) {
localAddress = new LocalAddress(LocalChannel.this);
}
}
if (localAddress != null) {
try {
doBind(localAddress);
} catch (Throwable t) {
safeSetFailure(promise, t);
close(voidPromise());
return;
}
}
Channel boundChannel = LocalChannelRegistry.get(remoteAddress);
if (!(boundChannel instanceof LocalServerChannel)) {
Exception cause = new ConnectException("connection refused: " + remoteAddress);
safeSetFailure(promise, cause);
close(voidPromise());
return;
}
LocalServerChannel serverChannel = (LocalServerChannel) boundChannel;
peer = serverChannel.serve(LocalChannel.this);
}
}
}