i = selectedKeys.iterator(); i.hasNext();) {
SelectionKey k = i.next();
diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/handler/SimpleSctpChannelHandler.java b/transport-sctp/src/main/java/io/netty/channel/sctp/handler/SimpleSctpChannelHandler.java
index 1a14464999..910fd0093b 100644
--- a/transport-sctp/src/main/java/io/netty/channel/sctp/handler/SimpleSctpChannelHandler.java
+++ b/transport-sctp/src/main/java/io/netty/channel/sctp/handler/SimpleSctpChannelHandler.java
@@ -19,8 +19,6 @@ import io.netty.channel.ChannelEvent;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelHandler;
import io.netty.channel.sctp.SctpNotificationEvent;
-import io.netty.logging.InternalLogger;
-import io.netty.logging.InternalLoggerFactory;
/**
* SCTP Channel Handler (upstream + downstream) with SCTP notification handling
@@ -28,10 +26,6 @@ import io.netty.logging.InternalLoggerFactory;
public class SimpleSctpChannelHandler extends SimpleChannelHandler {
- private static final InternalLogger logger =
- InternalLoggerFactory.getInstance(SimpleSctpUpstreamHandler.class.getName());
-
-
@Override
public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent event) throws Exception {
if (!(event instanceof SctpNotificationEvent)) {
diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/handler/SimpleSctpUpstreamHandler.java b/transport-sctp/src/main/java/io/netty/channel/sctp/handler/SimpleSctpUpstreamHandler.java
index cf69c86301..37c3696de4 100644
--- a/transport-sctp/src/main/java/io/netty/channel/sctp/handler/SimpleSctpUpstreamHandler.java
+++ b/transport-sctp/src/main/java/io/netty/channel/sctp/handler/SimpleSctpUpstreamHandler.java
@@ -19,17 +19,12 @@ import io.netty.channel.ChannelEvent;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelUpstreamHandler;
import io.netty.channel.sctp.SctpNotificationEvent;
-import io.netty.logging.InternalLogger;
-import io.netty.logging.InternalLoggerFactory;
/**
* SCTP Upstream Channel Handler with SCTP notification handling
*/
public class SimpleSctpUpstreamHandler extends SimpleChannelUpstreamHandler {
- private static final InternalLogger logger =
- InternalLoggerFactory.getInstance(SimpleSctpUpstreamHandler.class.getName());
-
-
+
@Override
public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent event) throws Exception {
if (!(event instanceof SctpNotificationEvent)) {
diff --git a/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketClientBootstrapTest.java b/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketClientBootstrapTest.java
index e8ca6d7fd7..509a35398b 100644
--- a/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketClientBootstrapTest.java
+++ b/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketClientBootstrapTest.java
@@ -15,7 +15,6 @@
*/
package io.netty.testsuite.transport;
-import com.sun.nio.sctp.SctpChannel;
import com.sun.nio.sctp.SctpServerChannel;
import io.netty.bootstrap.ClientBootstrap;
import io.netty.channel.ChannelFactory;
@@ -40,7 +39,6 @@ import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
diff --git a/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpMultiStreamingEchoTest.java b/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpMultiStreamingEchoTest.java
index 9df1837864..7f8b104db8 100644
--- a/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpMultiStreamingEchoTest.java
+++ b/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpMultiStreamingEchoTest.java
@@ -23,8 +23,6 @@ import io.netty.channel.*;
import io.netty.channel.sctp.SctpClientSocketChannelFactory;
import io.netty.channel.sctp.SctpFrame;
import io.netty.channel.sctp.SctpServerSocketChannelFactory;
-import io.netty.channel.sctp.codec.SctpFrameDecoder;
-import io.netty.channel.sctp.codec.SctpFrameEncoder;
import io.netty.testsuite.util.SctpSocketAddresses;
import io.netty.util.internal.ExecutorUtil;
import org.junit.AfterClass;
diff --git a/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpServerBootstrapTest.java b/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpServerBootstrapTest.java
index bcb680c1f7..a20c6ae0fe 100644
--- a/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpServerBootstrapTest.java
+++ b/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpServerBootstrapTest.java
@@ -16,7 +16,6 @@
package io.netty.testsuite.transport.sctp;
import io.netty.channel.ChannelFactory;
-import io.netty.channel.sctp.SctpClientSocketChannelFactory;
import io.netty.channel.sctp.SctpServerSocketChannelFactory;
import io.netty.testsuite.transport.AbstractSocketServerBootstrapTest;
diff --git a/transport/src/main/java/io/netty/channel/AbstractChannelSink.java b/transport/src/main/java/io/netty/channel/AbstractChannelSink.java
index a1c27839ff..1210a8b94e 100644
--- a/transport/src/main/java/io/netty/channel/AbstractChannelSink.java
+++ b/transport/src/main/java/io/netty/channel/AbstractChannelSink.java
@@ -43,7 +43,28 @@ public abstract class AbstractChannelSink implements ChannelSink {
if (actualCause == null) {
actualCause = cause;
}
+ if (isFireExceptionCaughtLater(event, actualCause)) {
+ fireExceptionCaughtLater(event.getChannel(), actualCause);
+ } else {
+ fireExceptionCaught(event.getChannel(), actualCause);
+ }
+ }
+
+ protected boolean isFireExceptionCaughtLater(ChannelEvent event, Throwable actualCause) {
+ return false;
+ }
- fireExceptionCaught(event.getChannel(), actualCause);
+ /**
+ * This implementation just directly call {@link Runnable#run()}. Sub-classes should override this if they can handle it
+ * in a better way
+ */
+ @Override
+ public ChannelFuture execute(ChannelPipeline pipeline, Runnable task) {
+ try {
+ task.run();
+ return Channels.succeededFuture(pipeline.getChannel());
+ } catch (Throwable t) {
+ return Channels.failedFuture(pipeline.getChannel(), t);
+ }
}
}
diff --git a/transport/src/main/java/io/netty/channel/ChannelDownstreamHandler.java b/transport/src/main/java/io/netty/channel/ChannelDownstreamHandler.java
index 6f652c944d..3cfc13157c 100644
--- a/transport/src/main/java/io/netty/channel/ChannelDownstreamHandler.java
+++ b/transport/src/main/java/io/netty/channel/ChannelDownstreamHandler.java
@@ -56,6 +56,10 @@ package io.netty.channel;
*
* You will also find various helper methods in {@link Channels} to be useful
* to generate and send an artificial or manipulated event.
+ *
+ * Caution:
+ *
+ * Use the *Later(..) methods of the {@link Channels} class if you want to send an upstream event from a {@link ChannelDownstreamHandler} otherwise you may run into threading issues.
*
*
State management
*
diff --git a/transport/src/main/java/io/netty/channel/ChannelPipeline.java b/transport/src/main/java/io/netty/channel/ChannelPipeline.java
index cbdeeab097..f69df63fbe 100644
--- a/transport/src/main/java/io/netty/channel/ChannelPipeline.java
+++ b/transport/src/main/java/io/netty/channel/ChannelPipeline.java
@@ -432,7 +432,6 @@ public interface ChannelPipeline {
*/
ChannelHandlerContext getContext(Class extends ChannelHandler> handlerType);
-
/**
* Sends the specified {@link ChannelEvent} to the first
* {@link ChannelUpstreamHandler} in this pipeline.
@@ -451,6 +450,12 @@ public interface ChannelPipeline {
*/
void sendDownstream(ChannelEvent e);
+ /**
+ * Schedules the specified task to be executed in the I/O thread associated
+ * with this pipeline's {@link Channel}.
+ */
+ ChannelFuture execute(Runnable task);
+
/**
* Returns the {@link Channel} that this pipeline is attached to.
*
diff --git a/transport/src/main/java/io/netty/channel/ChannelSink.java b/transport/src/main/java/io/netty/channel/ChannelSink.java
index 86d042e56f..77187f62b6 100644
--- a/transport/src/main/java/io/netty/channel/ChannelSink.java
+++ b/transport/src/main/java/io/netty/channel/ChannelSink.java
@@ -37,4 +37,10 @@ public interface ChannelSink {
* one of its {@link ChannelHandler}s process a {@link ChannelEvent}.
*/
void exceptionCaught(ChannelPipeline pipeline, ChannelEvent e, ChannelPipelineException cause) throws Exception;
+
+ /**
+ * Execute the given {@link Runnable} later in the io-thread.
+ * Some implementation may not support this and just execute it directly.
+ */
+ ChannelFuture execute(ChannelPipeline pipeline, Runnable task);
}
diff --git a/transport/src/main/java/io/netty/channel/Channels.java b/transport/src/main/java/io/netty/channel/Channels.java
index e5852d2ced..1ea98c0827 100644
--- a/transport/src/main/java/io/netty/channel/Channels.java
+++ b/transport/src/main/java/io/netty/channel/Channels.java
@@ -298,6 +298,22 @@ public final class Channels {
ctx.getChannel(), message, remoteAddress));
}
+ /**
+ * Sends a {@code "writeComplete"} event to the first
+ * {@link ChannelUpstreamHandler} in the {@link ChannelPipeline} of
+ * the specified {@link Channel} in the next io-thread.
+ */
+ public static ChannelFuture fireWriteCompleteLater(final Channel channel, final long amount) {
+ return channel.getPipeline().execute(new Runnable() {
+ @Override
+ public void run() {
+ fireWriteComplete(channel, amount);
+ }
+ });
+
+ }
+
+
/**
* Sends a {@code "writeComplete"} event to the first
* {@link ChannelUpstreamHandler} in the {@link ChannelPipeline} of
@@ -321,6 +337,25 @@ public final class Channels {
public static void fireWriteComplete(ChannelHandlerContext ctx, long amount) {
ctx.sendUpstream(new DefaultWriteCompletionEvent(ctx.getChannel(), amount));
}
+
+
+
+ /**
+ * Sends a {@code "channelInterestChanged"} event to the first
+ * {@link ChannelUpstreamHandler} in the {@link ChannelPipeline} of
+ * the specified {@link Channel} once the io-thread runs again.
+ */
+ public static ChannelFuture fireChannelInterestChangedLater(final Channel channel) {
+ return channel.getPipeline().execute(new Runnable() {
+
+ @Override
+ public void run() {
+ fireChannelInterestChanged(channel);
+
+ }
+ });
+ }
+
/**
* Sends a {@code "channelInterestChanged"} event to the first
* {@link ChannelUpstreamHandler} in the {@link ChannelPipeline} of
@@ -346,6 +381,21 @@ public final class Channels {
ctx.getChannel(), ChannelState.INTEREST_OPS, Channel.OP_READ));
}
+ /**
+ * Sends a {@code "channelDisconnected"} event to the first
+ * {@link ChannelUpstreamHandler} in the {@link ChannelPipeline} of
+ * the specified {@link Channel} once the io-thread runs again.
+ */
+ public static ChannelFuture fireChannelDisconnectedLater(final Channel channel) {
+ return channel.getPipeline().execute(new Runnable() {
+
+ @Override
+ public void run() {
+ fireChannelDisconnected(channel);
+ }
+ });
+ }
+
/**
* Sends a {@code "channelDisconnected"} event to the first
* {@link ChannelUpstreamHandler} in the {@link ChannelPipeline} of
@@ -368,6 +418,23 @@ public final class Channels {
ctx.getChannel(), ChannelState.CONNECTED, null));
}
+
+
+ /**
+ * Sends a {@code "channelUnbound"} event to the first
+ * {@link ChannelUpstreamHandler} in the {@link ChannelPipeline} of
+ * the specified {@link Channel} once the io-thread runs again.
+ */
+ public static ChannelFuture fireChannelUnboundLater(final Channel channel) {
+ return channel.getPipeline().execute(new Runnable() {
+
+ @Override
+ public void run() {
+ fireChannelUnbound(channel);
+ }
+ });
+ }
+
/**
* Sends a {@code "channelUnbound"} event to the first
* {@link ChannelUpstreamHandler} in the {@link ChannelPipeline} of
@@ -390,6 +457,24 @@ public final class Channels {
ctx.getChannel(), ChannelState.BOUND, null));
}
+
+
+ /**
+ * Sends a {@code "channelClosed"} event to the first
+ * {@link ChannelUpstreamHandler} in the {@link ChannelPipeline} of
+ * the specified {@link Channel} once the io-thread runs again.
+ */
+ public static ChannelFuture fireChannelClosedLater(final Channel channel) {
+ return channel.getPipeline().execute(new Runnable() {
+
+ @Override
+ public void run() {
+ fireChannelClosed(channel);
+ }
+ });
+
+ }
+
/**
* Sends a {@code "channelClosed"} event to the first
* {@link ChannelUpstreamHandler} in the {@link ChannelPipeline} of
@@ -418,6 +503,39 @@ public final class Channels {
ctx.getChannel(), ChannelState.OPEN, Boolean.FALSE));
}
+
+
+ /**
+ * Sends a {@code "exceptionCaught"} event to the first
+ * {@link ChannelUpstreamHandler} in the {@link ChannelPipeline} of
+ * the specified {@link Channel} once the io-thread runs again.
+ */
+ public static ChannelFuture fireExceptionCaughtLater(final Channel channel, final Throwable cause) {
+ return channel.getPipeline().execute(new Runnable() {
+
+ @Override
+ public void run() {
+ fireExceptionCaught(channel, cause);
+ }
+ });
+ }
+
+ /**
+ * Sends a {@code "exceptionCaught"} event to the
+ * {@link ChannelUpstreamHandler} which is placed in the closest upstream
+ * from the handler associated with the specified
+ * {@link ChannelHandlerContext} once the io-thread runs again.
+ */
+ public static ChannelFuture fireExceptionCaughtLater(final ChannelHandlerContext ctx, final Throwable cause) {
+ return ctx.getPipeline().execute(new Runnable() {
+
+ @Override
+ public void run() {
+ fireExceptionCaught(ctx, cause);
+ }
+ });
+ }
+
/**
* Sends a {@code "exceptionCaught"} event to the first
* {@link ChannelUpstreamHandler} in the {@link ChannelPipeline} of
@@ -444,6 +562,7 @@ public final class Channels {
new DefaultChildChannelStateEvent(channel, childChannel));
}
+
/**
* Sends a {@code "bind"} request to the last
* {@link ChannelDownstreamHandler} in the {@link ChannelPipeline} of
diff --git a/transport/src/main/java/io/netty/channel/DefaultChannelPipeline.java b/transport/src/main/java/io/netty/channel/DefaultChannelPipeline.java
index 50487ae36e..df7e72ecb8 100644
--- a/transport/src/main/java/io/netty/channel/DefaultChannelPipeline.java
+++ b/transport/src/main/java/io/netty/channel/DefaultChannelPipeline.java
@@ -21,6 +21,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
+import java.util.concurrent.RejectedExecutionException;
import io.netty.logging.InternalLogger;
import io.netty.logging.InternalLoggerFactory;
@@ -649,6 +650,11 @@ public class DefaultChannelPipeline implements ChannelPipeline {
return realCtx;
}
+ @Override
+ public ChannelFuture execute(Runnable task) {
+ return getSink().execute(this, task);
+ }
+
protected void notifyHandlerException(ChannelEvent e, Throwable t) {
if (e instanceof ExceptionEvent) {
if (logger.isWarnEnabled()) {
@@ -832,5 +838,14 @@ public class DefaultChannelPipeline implements ChannelPipeline {
ChannelEvent e, ChannelPipelineException cause) throws Exception {
throw cause;
}
+
+ @Override
+ public ChannelFuture execute(ChannelPipeline pipeline, Runnable task) {
+ if (logger.isWarnEnabled()) {
+ logger.warn("Not attached yet; rejecting: " + task);
+ }
+ return Channels.failedFuture(pipeline.getChannel(), new RejectedExecutionException("Not attached yet"));
+ }
+
}
}
diff --git a/transport/src/main/java/io/netty/channel/SimpleChannelDownstreamHandler.java b/transport/src/main/java/io/netty/channel/SimpleChannelDownstreamHandler.java
index 2108c07734..f98de0bf90 100644
--- a/transport/src/main/java/io/netty/channel/SimpleChannelDownstreamHandler.java
+++ b/transport/src/main/java/io/netty/channel/SimpleChannelDownstreamHandler.java
@@ -49,6 +49,12 @@ import java.net.SocketAddress;
* super.handleDownstream(ctx, e);
* }
* }
+ *
+ *
+ * Caution:
+ *
+ * Use the *Later(..) methods of the {@link Channels} class if you want to send an upstream event from a {@link ChannelDownstreamHandler} otherwise you may run into threading issues.
+ *
*/
public class SimpleChannelDownstreamHandler implements ChannelDownstreamHandler {
diff --git a/transport/src/main/java/io/netty/channel/socket/ChannelRunnableWrapper.java b/transport/src/main/java/io/netty/channel/socket/ChannelRunnableWrapper.java
new file mode 100644
index 0000000000..fcf0e3b00f
--- /dev/null
+++ b/transport/src/main/java/io/netty/channel/socket/ChannelRunnableWrapper.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2011 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.channel.socket;
+
+import io.netty.channel.Channel;
+import io.netty.channel.DefaultChannelFuture;
+
+public class ChannelRunnableWrapper extends DefaultChannelFuture implements Runnable {
+
+ private final Runnable task;
+ private boolean started;
+
+ public ChannelRunnableWrapper(Channel channel, Runnable task) {
+ super(channel, true);
+ this.task = task;
+ }
+
+ @Override
+ public void run() {
+ synchronized (this) {
+ if (!isCancelled()) {
+ started = true;
+ } else {
+ return;
+ }
+ }
+ try {
+ task.run();
+ setSuccess();
+ } catch (Throwable t) {
+ setFailure(t);
+ }
+ }
+
+ @Override
+ public synchronized boolean cancel() {
+ if (started) {
+ return false;
+ }
+ return super.cancel();
+ }
+
+
+
+}
diff --git a/transport/src/main/java/io/netty/channel/socket/Worker.java b/transport/src/main/java/io/netty/channel/socket/Worker.java
new file mode 100644
index 0000000000..271897881a
--- /dev/null
+++ b/transport/src/main/java/io/netty/channel/socket/Worker.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2011 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package io.netty.channel.socket;
+
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+
+/**
+ * A {@link Worker} is responsible to dispatch IO operations
+ *
+ */
+public interface Worker extends Runnable {
+
+ /**
+ * Execute the given {@link Runnable} in the IO-Thread. This may be now or later once the IO-Thread do some other work.
+ *
+ * @param task the {@link Runnable} to execute
+ */
+ ChannelFuture executeInIoThread(Channel channel, Runnable task);
+}
diff --git a/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioChannel.java b/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioChannel.java
new file mode 100644
index 0000000000..27fe0a8e44
--- /dev/null
+++ b/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioChannel.java
@@ -0,0 +1,397 @@
+/*
+ * Copyright 2011 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.channel.socket.nio;
+
+import static io.netty.channel.Channels.fireChannelInterestChanged;
+import io.netty.buffer.ChannelBuffer;
+import io.netty.channel.AbstractChannel;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFactory;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.ChannelSink;
+import io.netty.channel.MessageEvent;
+import io.netty.channel.socket.nio.SocketSendBufferPool.SendBuffer;
+import io.netty.util.internal.QueueFactory;
+import io.netty.util.internal.ThreadLocalBoolean;
+
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.nio.channels.SelectableChannel;
+import java.nio.channels.WritableByteChannel;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Queue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+abstract class AbstractNioChannel extends AbstractChannel {
+
+ /**
+ * The {@link AbstractNioWorker}.
+ */
+ final AbstractNioWorker worker;
+
+ /**
+ * Monitor object to synchronize access to InterestedOps.
+ */
+ final Object interestOpsLock = new Object();
+
+ /**
+ * Monitor object for synchronizing access to the {@link WriteRequestQueue}.
+ */
+ final Object writeLock = new Object();
+
+ /**
+ * WriteTask that performs write operations.
+ */
+ final Runnable writeTask = new WriteTask();
+
+ /**
+ * Indicates if there is a {@link WriteTask} in the task queue.
+ */
+ final AtomicBoolean writeTaskInTaskQueue = new AtomicBoolean();
+
+ /**
+ * Queue of write {@link MessageEvent}s.
+ */
+ final Queue writeBufferQueue = new WriteRequestQueue();
+
+ /**
+ * Keeps track of the number of bytes that the {@link WriteRequestQueue} currently
+ * contains.
+ */
+ final AtomicInteger writeBufferSize = new AtomicInteger();
+
+ /**
+ * Keeps track of the highWaterMark.
+ */
+ final AtomicInteger highWaterMarkCounter = new AtomicInteger();
+
+ /**
+ * The current write {@link MessageEvent}
+ */
+ MessageEvent currentWriteEvent;
+ SendBuffer currentWriteBuffer;
+
+ /**
+ * Boolean that indicates that write operation is in progress.
+ */
+ boolean inWriteNowLoop;
+ boolean writeSuspended;
+
+
+ private volatile InetSocketAddress localAddress;
+ volatile InetSocketAddress remoteAddress;
+
+ final C channel;
+
+ protected AbstractNioChannel(Integer id, Channel parent, ChannelFactory factory, ChannelPipeline pipeline, ChannelSink sink, AbstractNioWorker worker, C ch) {
+ super(id, parent, factory, pipeline, sink);
+ this.worker = worker;
+ this.channel = ch;
+ }
+
+ protected AbstractNioChannel(
+ Channel parent, ChannelFactory factory,
+ ChannelPipeline pipeline, ChannelSink sink, AbstractNioWorker worker, C ch) {
+ super(parent, factory, pipeline, sink);
+ this.worker = worker;
+ this.channel = ch;
+ }
+
+ @Override
+ public InetSocketAddress getLocalAddress() {
+ InetSocketAddress localAddress = this.localAddress;
+ if (localAddress == null) {
+ try {
+ this.localAddress = localAddress =
+ (InetSocketAddress) getLocalSocketAddress();
+ } catch (Throwable t) {
+ // Sometimes fails on a closed socket in Windows.
+ return null;
+ }
+ }
+ return localAddress;
+ }
+
+ @Override
+ public InetSocketAddress getRemoteAddress() {
+ InetSocketAddress remoteAddress = this.remoteAddress;
+ if (remoteAddress == null) {
+ try {
+ this.remoteAddress = remoteAddress =
+ (InetSocketAddress) getRemoteSocketAddress();
+ } catch (Throwable t) {
+ // Sometimes fails on a closed socket in Windows.
+ return null;
+ }
+ }
+ return remoteAddress;
+ }
+
+ @Override
+ public abstract NioChannelConfig getConfig();
+
+ int getRawInterestOps() {
+ return super.getInterestOps();
+ }
+
+ void setRawInterestOpsNow(int interestOps) {
+ super.setInterestOpsNow(interestOps);
+ }
+
+ @Override
+ public ChannelFuture write(Object message, SocketAddress remoteAddress) {
+ if (remoteAddress == null || remoteAddress.equals(getRemoteAddress())) {
+ return super.write(message, null);
+ } else {
+ return getUnsupportedOperationFuture();
+ }
+ }
+
+ @Override
+ public int getInterestOps() {
+ if (!isOpen()) {
+ return Channel.OP_WRITE;
+ }
+
+ int interestOps = getRawInterestOps();
+ int writeBufferSize = this.writeBufferSize.get();
+ if (writeBufferSize != 0) {
+ if (highWaterMarkCounter.get() > 0) {
+ int lowWaterMark = getConfig().getWriteBufferLowWaterMark();
+ if (writeBufferSize >= lowWaterMark) {
+ interestOps |= Channel.OP_WRITE;
+ } else {
+ interestOps &= ~Channel.OP_WRITE;
+ }
+ } else {
+ int highWaterMark = getConfig().getWriteBufferHighWaterMark();
+ if (writeBufferSize >= highWaterMark) {
+ interestOps |= Channel.OP_WRITE;
+ } else {
+ interestOps &= ~Channel.OP_WRITE;
+ }
+ }
+ } else {
+ interestOps &= ~Channel.OP_WRITE;
+ }
+
+ return interestOps;
+ }
+
+ @Override
+ protected boolean setClosed() {
+ return super.setClosed();
+ }
+
+ abstract InetSocketAddress getLocalSocketAddress() throws Exception;
+
+ abstract InetSocketAddress getRemoteSocketAddress() throws Exception;
+
+ private final class WriteRequestQueue implements BlockingQueue {
+ private final ThreadLocalBoolean notifying = new ThreadLocalBoolean();
+
+ private final BlockingQueue queue;
+
+ public WriteRequestQueue() {
+ this.queue = QueueFactory.createQueue(MessageEvent.class);
+ }
+
+ @Override
+ public MessageEvent remove() {
+ return queue.remove();
+ }
+
+ @Override
+ public MessageEvent element() {
+ return queue.element();
+ }
+
+ @Override
+ public MessageEvent peek() {
+ return queue.peek();
+ }
+
+ @Override
+ public int size() {
+ return queue.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return queue.isEmpty();
+ }
+
+ @Override
+ public Iterator iterator() {
+ return queue.iterator();
+ }
+
+ @Override
+ public Object[] toArray() {
+ return queue.toArray();
+ }
+
+ @Override
+ public T[] toArray(T[] a) {
+ return queue.toArray(a);
+ }
+
+ @Override
+ public boolean containsAll(Collection> c) {
+ return queue.containsAll(c);
+ }
+
+ @Override
+ public boolean addAll(Collection extends MessageEvent> c) {
+ return queue.addAll(c);
+ }
+
+ @Override
+ public boolean removeAll(Collection> c) {
+ return queue.removeAll(c);
+ }
+
+ @Override
+ public boolean retainAll(Collection> c) {
+ return queue.retainAll(c);
+ }
+
+ @Override
+ public void clear() {
+ queue.clear();
+ }
+
+ @Override
+ public boolean add(MessageEvent e) {
+ return queue.add(e);
+ }
+
+ @Override
+ public void put(MessageEvent e) throws InterruptedException {
+ queue.put(e);
+ }
+
+ @Override
+ public boolean offer(MessageEvent e, long timeout, TimeUnit unit) throws InterruptedException {
+ return queue.offer(e, timeout, unit);
+ }
+
+ @Override
+ public MessageEvent take() throws InterruptedException {
+ return queue.take();
+ }
+
+ @Override
+ public MessageEvent poll(long timeout, TimeUnit unit) throws InterruptedException {
+ return queue.poll(timeout, unit);
+ }
+
+ @Override
+ public int remainingCapacity() {
+ return queue.remainingCapacity();
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ return queue.remove(o);
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ return queue.contains(o);
+ }
+
+ @Override
+ public int drainTo(Collection super MessageEvent> c) {
+ return queue.drainTo(c);
+ }
+
+ @Override
+ public int drainTo(Collection super MessageEvent> c, int maxElements) {
+ return queue.drainTo(c, maxElements);
+ }
+
+ @Override
+ public boolean offer(MessageEvent e) {
+ boolean success = queue.offer(e);
+ assert success;
+
+ int messageSize = getMessageSize(e);
+ int newWriteBufferSize = writeBufferSize.addAndGet(messageSize);
+ int highWaterMark = getConfig().getWriteBufferHighWaterMark();
+
+ if (newWriteBufferSize >= highWaterMark) {
+ if (newWriteBufferSize - messageSize < highWaterMark) {
+ highWaterMarkCounter.incrementAndGet();
+ if (!notifying.get()) {
+ notifying.set(Boolean.TRUE);
+ fireChannelInterestChanged(AbstractNioChannel.this);
+ notifying.set(Boolean.FALSE);
+ }
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public MessageEvent poll() {
+ MessageEvent e = queue.poll();
+ if (e != null) {
+ int messageSize = getMessageSize(e);
+ int newWriteBufferSize = writeBufferSize.addAndGet(-messageSize);
+ int lowWaterMark = getConfig().getWriteBufferLowWaterMark();
+
+ if (newWriteBufferSize == 0 || newWriteBufferSize < lowWaterMark) {
+ if (newWriteBufferSize + messageSize >= lowWaterMark) {
+ highWaterMarkCounter.decrementAndGet();
+ if (isConnected() && !notifying.get()) {
+ notifying.set(Boolean.TRUE);
+ fireChannelInterestChanged(AbstractNioChannel.this);
+ notifying.set(Boolean.FALSE);
+ }
+ }
+ }
+ }
+ return e;
+ }
+
+ private int getMessageSize(MessageEvent e) {
+ Object m = e.getMessage();
+ if (m instanceof ChannelBuffer) {
+ return ((ChannelBuffer) m).readableBytes();
+ }
+ return 0;
+ }
+ }
+
+ private final class WriteTask implements Runnable {
+
+ WriteTask() {
+ }
+
+ @Override
+ public void run() {
+ writeTaskInTaskQueue.set(false);
+ worker.writeFromTaskLoop(AbstractNioChannel.this);
+ }
+ }
+
+}
diff --git a/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioChannelSink.java b/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioChannelSink.java
new file mode 100644
index 0000000000..1a29f5ef19
--- /dev/null
+++ b/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioChannelSink.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2011 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package io.netty.channel.socket.nio;
+
+import io.netty.channel.AbstractChannelSink;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelEvent;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelPipeline;
+
+public abstract class AbstractNioChannelSink extends AbstractChannelSink {
+
+ @Override
+ public ChannelFuture execute(ChannelPipeline pipeline, final Runnable task) {
+ Channel ch = pipeline.getChannel();
+ if (ch instanceof AbstractNioChannel>) {
+ AbstractNioChannel> channel = (AbstractNioChannel>) ch;
+
+ return channel.worker.executeInIoThread(ch, task);
+ }
+ return super.execute(pipeline, task);
+
+
+ }
+
+ @Override
+ protected boolean isFireExceptionCaughtLater(ChannelEvent event, Throwable actualCause) {
+ Channel channel = event.getChannel();
+ boolean fireLater = false;
+ if (channel instanceof AbstractNioChannel>) {
+ fireLater = !AbstractNioWorker.isIoThread((AbstractNioChannel>) channel);
+ }
+ return fireLater;
+ }
+
+}
diff --git a/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java b/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java
new file mode 100644
index 0000000000..43aa1bca84
--- /dev/null
+++ b/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java
@@ -0,0 +1,785 @@
+/*
+ * Copyright 2011 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.channel.socket.nio;
+
+import static io.netty.channel.Channels.*;
+
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelException;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.MessageEvent;
+import io.netty.channel.socket.ChannelRunnableWrapper;
+import io.netty.channel.socket.Worker;
+import io.netty.channel.socket.nio.SocketSendBufferPool.SendBuffer;
+import io.netty.logging.InternalLogger;
+import io.netty.logging.InternalLoggerFactory;
+import io.netty.util.internal.DeadLockProofWorker;
+import io.netty.util.internal.QueueFactory;
+
+import java.io.IOException;
+import java.nio.channels.AsynchronousCloseException;
+import java.nio.channels.CancelledKeyException;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.NotYetConnectedException;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.WritableByteChannel;
+import java.util.Iterator;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+abstract class AbstractNioWorker implements Worker {
+ /**
+ * Internal Netty logger.
+ */
+ private static final InternalLogger logger = InternalLoggerFactory
+ .getInstance(AbstractNioWorker.class);
+
+ private static final int CONSTRAINT_LEVEL = NioProviderMetadata.CONSTRAINT_LEVEL;
+
+ static final int CLEANUP_INTERVAL = 256; // XXX Hard-coded value, but won't need customization.
+
+
+ /**
+ * Executor used to execute {@link Runnable}s such as registration task.
+ */
+ private final Executor executor;
+
+ /**
+ * Boolean to indicate if this worker has been started.
+ */
+ private boolean started;
+
+ /**
+ * If this worker has been started thread will be a reference to the thread
+ * used when starting. i.e. the current thread when the run method is executed.
+ */
+ protected volatile Thread thread;
+
+ /**
+ * The NIO {@link Selector}.
+ */
+ volatile Selector selector;
+
+ /**
+ * Boolean that controls determines if a blocked Selector.select should
+ * break out of its selection process. In our case we use a timeone for
+ * the select method and the select method will block for that time unless
+ * waken up.
+ */
+ protected final AtomicBoolean wakenUp = new AtomicBoolean();
+
+ /**
+ * Lock for this workers Selector.
+ */
+ private final ReadWriteLock selectorGuard = new ReentrantReadWriteLock();
+
+ /**
+ * Monitor object used to synchronize selector open/close.
+ */
+ private final Object startStopLock = new Object();
+
+ /**
+ * Queue of channel registration tasks.
+ */
+ private final Queue registerTaskQueue = QueueFactory.createQueue(Runnable.class);
+
+ /**
+ * Queue of WriteTasks
+ */
+ protected final Queue writeTaskQueue = QueueFactory.createQueue(Runnable.class);
+
+ private final Queue eventQueue = QueueFactory.createQueue(Runnable.class);
+
+
+ private volatile int cancelledKeys; // should use AtomicInteger but we just need approximation
+
+ private final SocketSendBufferPool sendBufferPool = new SocketSendBufferPool();
+
+ AbstractNioWorker(Executor executor) {
+ this.executor = executor;
+ }
+
+ void register(AbstractNioChannel> channel, ChannelFuture future) {
+
+ Runnable registerTask = createRegisterTask(channel, future);
+ Selector selector;
+
+ synchronized (startStopLock) {
+ if (!started) {
+ // Open a selector if this worker didn't start yet.
+ try {
+ this.selector = selector = Selector.open();
+ } catch (Throwable t) {
+ throw new ChannelException("Failed to create a selector.", t);
+ }
+
+ // Start the worker thread with the new Selector.
+ boolean success = false;
+ try {
+ DeadLockProofWorker.start(executor, this);
+ success = true;
+ } finally {
+ if (!success) {
+ // Release the Selector if the execution fails.
+ try {
+ selector.close();
+ } catch (Throwable t) {
+ logger.warn("Failed to close a selector.", t);
+ }
+ this.selector = selector = null;
+ // The method will return to the caller at this point.
+ }
+ }
+ } else {
+ // Use the existing selector if this worker has been started.
+ selector = this.selector;
+ }
+
+ assert selector != null && selector.isOpen();
+
+ started = true;
+ boolean offered = registerTaskQueue.offer(registerTask);
+ assert offered;
+ }
+
+ if (wakenUp.compareAndSet(false, true)) {
+ selector.wakeup();
+ }
+ }
+
+
+ @Override
+ public void run() {
+ thread = Thread.currentThread();
+
+ boolean shutdown = false;
+ Selector selector = this.selector;
+ for (;;) {
+ wakenUp.set(false);
+
+ if (CONSTRAINT_LEVEL != 0) {
+ selectorGuard.writeLock().lock();
+ // This empty synchronization block prevents the selector
+ // from acquiring its lock.
+ selectorGuard.writeLock().unlock();
+ }
+
+ try {
+ SelectorUtil.select(selector);
+
+ // 'wakenUp.compareAndSet(false, true)' is always evaluated
+ // before calling 'selector.wakeup()' to reduce the wake-up
+ // overhead. (Selector.wakeup() is an expensive operation.)
+ //
+ // However, there is a race condition in this approach.
+ // The race condition is triggered when 'wakenUp' is set to
+ // true too early.
+ //
+ // 'wakenUp' is set to true too early if:
+ // 1) Selector is waken up between 'wakenUp.set(false)' and
+ // 'selector.select(...)'. (BAD)
+ // 2) Selector is waken up between 'selector.select(...)' and
+ // 'if (wakenUp.get()) { ... }'. (OK)
+ //
+ // In the first case, 'wakenUp' is set to true and the
+ // following 'selector.select(...)' will wake up immediately.
+ // Until 'wakenUp' is set to false again in the next round,
+ // 'wakenUp.compareAndSet(false, true)' will fail, and therefore
+ // any attempt to wake up the Selector will fail, too, causing
+ // the following 'selector.select(...)' call to block
+ // unnecessarily.
+ //
+ // To fix this problem, we wake up the selector again if wakenUp
+ // is true immediately after selector.select(...).
+ // It is inefficient in that it wakes up the selector for both
+ // the first case (BAD - wake-up required) and the second case
+ // (OK - no wake-up required).
+
+ if (wakenUp.get()) {
+ selector.wakeup();
+ }
+
+ cancelledKeys = 0;
+ processRegisterTaskQueue();
+ processEventQueue();
+ processWriteTaskQueue();
+ processSelectedKeys(selector.selectedKeys());
+
+ // Exit the loop when there's nothing to handle.
+ // The shutdown flag is used to delay the shutdown of this
+ // loop to avoid excessive Selector creation when
+ // connections are registered in a one-by-one manner instead of
+ // concurrent manner.
+ if (selector.keys().isEmpty()) {
+ if (shutdown ||
+ executor instanceof ExecutorService && ((ExecutorService) executor).isShutdown()) {
+
+ synchronized (startStopLock) {
+ if (registerTaskQueue.isEmpty() && selector.keys().isEmpty()) {
+ started = false;
+ try {
+ selector.close();
+ } catch (IOException e) {
+ logger.warn(
+ "Failed to close a selector.", e);
+ } finally {
+ this.selector = null;
+ }
+ break;
+ } else {
+ shutdown = false;
+ }
+ }
+ } else {
+ // Give one more second.
+ shutdown = true;
+ }
+ } else {
+ shutdown = false;
+ }
+ } catch (Throwable t) {
+ logger.warn(
+ "Unexpected exception in the selector loop.", t);
+
+ // Prevent possible consecutive immediate failures that lead to
+ // excessive CPU consumption.
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ // Ignore.
+ }
+ }
+ }
+ }
+
+ @Override
+ public ChannelFuture executeInIoThread(Channel channel, Runnable task) {
+ if (channel instanceof AbstractNioChannel> && isIoThread((AbstractNioChannel>) channel)) {
+ try {
+ task.run();
+ return succeededFuture(channel);
+ } catch (Throwable t) {
+ return failedFuture(channel, t);
+ }
+ } else {
+ ChannelRunnableWrapper channelRunnable = new ChannelRunnableWrapper(channel, task);
+ boolean added = eventQueue.offer(channelRunnable);
+
+ if (added) {
+ // wake up the selector to speed things
+ Selector selector = this.selector;
+ if (selector != null) {
+ selector.wakeup();
+ }
+ } else {
+ channelRunnable.setFailure(new RejectedExecutionException("Unable to queue task " + task));
+ }
+ return channelRunnable;
+ }
+
+
+ }
+
+ private void processRegisterTaskQueue() throws IOException {
+ for (;;) {
+ final Runnable task = registerTaskQueue.poll();
+ if (task == null) {
+ break;
+ }
+
+ task.run();
+ cleanUpCancelledKeys();
+ }
+ }
+
+ private void processWriteTaskQueue() throws IOException {
+ for (;;) {
+ final Runnable task = writeTaskQueue.poll();
+ if (task == null) {
+ break;
+ }
+
+ task.run();
+ cleanUpCancelledKeys();
+ }
+ }
+
+ private void processEventQueue() throws IOException {
+ for (;;) {
+ final Runnable task = eventQueue.poll();
+ if (task == null) {
+ break;
+ }
+ task.run();
+ cleanUpCancelledKeys();
+ }
+ }
+
+ private void processSelectedKeys(Set selectedKeys) throws IOException {
+ for (Iterator i = selectedKeys.iterator(); i.hasNext();) {
+ SelectionKey k = i.next();
+ i.remove();
+ try {
+ int readyOps = k.readyOps();
+ if ((readyOps & SelectionKey.OP_READ) != 0 || readyOps == 0) {
+ if (!read(k)) {
+ // Connection already closed - no need to handle write.
+ continue;
+ }
+ }
+ if ((readyOps & SelectionKey.OP_WRITE) != 0) {
+ writeFromSelectorLoop(k);
+ }
+ } catch (CancelledKeyException e) {
+ close(k);
+ }
+
+ if (cleanUpCancelledKeys()) {
+ break; // break the loop to avoid ConcurrentModificationException
+ }
+ }
+ }
+
+ private boolean cleanUpCancelledKeys() throws IOException {
+ if (cancelledKeys >= CLEANUP_INTERVAL) {
+ cancelledKeys = 0;
+ selector.selectNow();
+ return true;
+ }
+ return false;
+ }
+
+
+
+ private void close(SelectionKey k) {
+ AbstractNioChannel> ch = (AbstractNioChannel>) k.attachment();
+ close(ch, succeededFuture(ch));
+ }
+
+ void writeFromUserCode(final AbstractNioChannel> channel) {
+ if (!channel.isConnected()) {
+ cleanUpWriteBuffer(channel);
+ return;
+ }
+
+ if (scheduleWriteIfNecessary(channel)) {
+ return;
+ }
+
+ // From here, we are sure Thread.currentThread() == workerThread.
+
+ if (channel.writeSuspended) {
+ return;
+ }
+
+ if (channel.inWriteNowLoop) {
+ return;
+ }
+
+ write0(channel);
+ }
+
+ void writeFromTaskLoop(AbstractNioChannel> ch) {
+ if (!ch.writeSuspended) {
+ write0(ch);
+ }
+ }
+
+ void writeFromSelectorLoop(final SelectionKey k) {
+ AbstractNioChannel> ch = (AbstractNioChannel>) k.attachment();
+ ch.writeSuspended = false;
+ write0(ch);
+ }
+
+ protected abstract boolean scheduleWriteIfNecessary(final AbstractNioChannel> channel);
+
+
+ private void write0(AbstractNioChannel> channel) {
+ boolean open = true;
+ boolean addOpWrite = false;
+ boolean removeOpWrite = false;
+ boolean iothread = isIoThread(channel);
+
+ long writtenBytes = 0;
+
+ final SocketSendBufferPool sendBufferPool = this.sendBufferPool;
+ final WritableByteChannel ch = channel.channel;
+ final Queue writeBuffer = channel.writeBufferQueue;
+ final int writeSpinCount = channel.getConfig().getWriteSpinCount();
+ synchronized (channel.writeLock) {
+ channel.inWriteNowLoop = true;
+ for (;;) {
+ MessageEvent evt = channel.currentWriteEvent;
+ SendBuffer buf;
+ if (evt == null) {
+ if ((channel.currentWriteEvent = evt = writeBuffer.poll()) == null) {
+ removeOpWrite = true;
+ channel.writeSuspended = false;
+ break;
+ }
+
+ channel.currentWriteBuffer = buf = sendBufferPool.acquire(evt.getMessage());
+ } else {
+ buf = channel.currentWriteBuffer;
+ }
+
+ ChannelFuture future = evt.getFuture();
+ try {
+ long localWrittenBytes = 0;
+ for (int i = writeSpinCount; i > 0; i --) {
+ localWrittenBytes = buf.transferTo(ch);
+ if (localWrittenBytes != 0) {
+ writtenBytes += localWrittenBytes;
+ break;
+ }
+ if (buf.finished()) {
+ break;
+ }
+ }
+
+ if (buf.finished()) {
+ // Successful write - proceed to the next message.
+ buf.release();
+ channel.currentWriteEvent = null;
+ channel.currentWriteBuffer = null;
+ evt = null;
+ buf = null;
+ future.setSuccess();
+ } else {
+ // Not written fully - perhaps the kernel buffer is full.
+ addOpWrite = true;
+ channel.writeSuspended = true;
+
+ if (localWrittenBytes > 0) {
+ // Notify progress listeners if necessary.
+ future.setProgress(
+ localWrittenBytes,
+ buf.writtenBytes(), buf.totalBytes());
+ }
+ break;
+ }
+ } catch (AsynchronousCloseException e) {
+ // Doesn't need a user attention - ignore.
+ } catch (Throwable t) {
+ if (buf != null) {
+ buf.release();
+ }
+ channel.currentWriteEvent = null;
+ channel.currentWriteBuffer = null;
+ buf = null;
+ evt = null;
+ future.setFailure(t);
+ if (iothread) {
+ fireExceptionCaught(channel, t);
+ } else {
+ fireExceptionCaughtLater(channel, t);
+ }
+ if (t instanceof IOException) {
+ open = false;
+ close(channel, succeededFuture(channel));
+ }
+ }
+ }
+ channel.inWriteNowLoop = false;
+
+ // Initially, the following block was executed after releasing
+ // the writeLock, but there was a race condition, and it has to be
+ // executed before releasing the writeLock:
+ //
+ // https://issues.jboss.org/browse/NETTY-410
+ //
+ if (open) {
+ if (addOpWrite) {
+ setOpWrite(channel);
+ } else if (removeOpWrite) {
+ clearOpWrite(channel);
+ }
+ }
+ }
+ if (iothread) {
+ fireWriteComplete(channel, writtenBytes);
+ } else {
+ fireWriteCompleteLater(channel, writtenBytes);
+ }
+ }
+
+ static boolean isIoThread(AbstractNioChannel> channel) {
+ return Thread.currentThread() == channel.worker.thread;
+ }
+
+ private void setOpWrite(AbstractNioChannel> channel) {
+ Selector selector = this.selector;
+ SelectionKey key = channel.channel.keyFor(selector);
+ if (key == null) {
+ return;
+ }
+ if (!key.isValid()) {
+ close(key);
+ return;
+ }
+
+ // interestOps can change at any time and at any thread.
+ // Acquire a lock to avoid possible race condition.
+ synchronized (channel.interestOpsLock) {
+ int interestOps = channel.getRawInterestOps();
+ if ((interestOps & SelectionKey.OP_WRITE) == 0) {
+ interestOps |= SelectionKey.OP_WRITE;
+ key.interestOps(interestOps);
+ channel.setRawInterestOpsNow(interestOps);
+ }
+ }
+ }
+
+ private void clearOpWrite(AbstractNioChannel> channel) {
+ Selector selector = this.selector;
+ SelectionKey key = channel.channel.keyFor(selector);
+ if (key == null) {
+ return;
+ }
+ if (!key.isValid()) {
+ close(key);
+ return;
+ }
+
+ // interestOps can change at any time and at any thread.
+ // Acquire a lock to avoid possible race condition.
+ synchronized (channel.interestOpsLock) {
+ int interestOps = channel.getRawInterestOps();
+ if ((interestOps & SelectionKey.OP_WRITE) != 0) {
+ interestOps &= ~SelectionKey.OP_WRITE;
+ key.interestOps(interestOps);
+ channel.setRawInterestOpsNow(interestOps);
+ }
+ }
+ }
+
+
+ void close(AbstractNioChannel> channel, ChannelFuture future) {
+ boolean connected = channel.isConnected();
+ boolean bound = channel.isBound();
+ boolean iothread = isIoThread(channel);
+
+ try {
+ channel.channel.close();
+ cancelledKeys ++;
+
+ if (channel.setClosed()) {
+ future.setSuccess();
+ if (connected) {
+ if (iothread) {
+ fireChannelDisconnected(channel);
+ } else {
+ fireChannelDisconnectedLater(channel);
+ }
+ }
+ if (bound) {
+ if (iothread) {
+ fireChannelUnbound(channel);
+ } else {
+ fireChannelUnboundLater(channel);
+ }
+ }
+
+ cleanUpWriteBuffer(channel);
+ if (iothread) {
+ fireChannelClosed(channel);
+ } else {
+ fireChannelClosedLater(channel);
+ }
+ } else {
+ future.setSuccess();
+ }
+ } catch (Throwable t) {
+ future.setFailure(t);
+ if (iothread) {
+ fireExceptionCaught(channel, t);
+ } else {
+ fireExceptionCaughtLater(channel, t);
+ }
+ }
+ }
+
+ private void cleanUpWriteBuffer(AbstractNioChannel> channel) {
+ Exception cause = null;
+ boolean fireExceptionCaught = false;
+
+ // Clean up the stale messages in the write buffer.
+ synchronized (channel.writeLock) {
+ MessageEvent evt = channel.currentWriteEvent;
+ if (evt != null) {
+ // Create the exception only once to avoid the excessive overhead
+ // caused by fillStackTrace.
+ if (channel.isOpen()) {
+ cause = new NotYetConnectedException();
+ } else {
+ cause = new ClosedChannelException();
+ }
+
+ ChannelFuture future = evt.getFuture();
+ channel.currentWriteBuffer.release();
+ channel.currentWriteBuffer = null;
+ channel.currentWriteEvent = null;
+ evt = null;
+ future.setFailure(cause);
+ fireExceptionCaught = true;
+ }
+
+ Queue writeBuffer = channel.writeBufferQueue;
+ if (!writeBuffer.isEmpty()) {
+ // Create the exception only once to avoid the excessive overhead
+ // caused by fillStackTrace.
+ if (cause == null) {
+ if (channel.isOpen()) {
+ cause = new NotYetConnectedException();
+ } else {
+ cause = new ClosedChannelException();
+ }
+ }
+
+ for (;;) {
+ evt = writeBuffer.poll();
+ if (evt == null) {
+ break;
+ }
+ evt.getFuture().setFailure(cause);
+ fireExceptionCaught = true;
+ }
+ }
+ }
+
+ if (fireExceptionCaught) {
+ if (isIoThread(channel)) {
+ fireExceptionCaught(channel, cause);
+ } else {
+ fireExceptionCaughtLater(channel, cause);
+ }
+ }
+ }
+
+ void setInterestOps(AbstractNioChannel> channel, ChannelFuture future, int interestOps) {
+ boolean changed = false;
+ boolean iothread = isIoThread(channel);
+ try {
+ // interestOps can change at any time and at any thread.
+ // Acquire a lock to avoid possible race condition.
+ synchronized (channel.interestOpsLock) {
+ Selector selector = this.selector;
+ SelectionKey key = channel.channel.keyFor(selector);
+
+ if (key == null || selector == null) {
+ // Not registered to the worker yet.
+ // Set the rawInterestOps immediately; RegisterTask will pick it up.
+ channel.setRawInterestOpsNow(interestOps);
+ return;
+ }
+
+ // Override OP_WRITE flag - a user cannot change this flag.
+ interestOps &= ~Channel.OP_WRITE;
+ interestOps |= channel.getRawInterestOps() & Channel.OP_WRITE;
+
+ switch (CONSTRAINT_LEVEL) {
+ case 0:
+ if (channel.getRawInterestOps() != interestOps) {
+ key.interestOps(interestOps);
+ if (Thread.currentThread() != thread &&
+ wakenUp.compareAndSet(false, true)) {
+ selector.wakeup();
+ }
+ changed = true;
+ }
+ break;
+ case 1:
+ case 2:
+ if (channel.getRawInterestOps() != interestOps) {
+ if (Thread.currentThread() == thread) {
+ key.interestOps(interestOps);
+ changed = true;
+ } else {
+ selectorGuard.readLock().lock();
+ try {
+ if (wakenUp.compareAndSet(false, true)) {
+ selector.wakeup();
+ }
+ key.interestOps(interestOps);
+ changed = true;
+ } finally {
+ selectorGuard.readLock().unlock();
+ }
+ }
+ }
+ break;
+ default:
+ throw new Error();
+ }
+
+ if (changed) {
+ channel.setRawInterestOpsNow(interestOps);
+ }
+ }
+
+ future.setSuccess();
+ if (changed) {
+ if (iothread) {
+ fireChannelInterestChanged(channel);
+ } else {
+ fireChannelInterestChangedLater(channel);
+ }
+ }
+ } catch (CancelledKeyException e) {
+ // setInterestOps() was called on a closed channel.
+ ClosedChannelException cce = new ClosedChannelException();
+ future.setFailure(cce);
+ if (iothread) {
+ fireExceptionCaught(channel, cce);
+ } else {
+ fireExceptionCaughtLater(channel, cce);
+ }
+ } catch (Throwable t) {
+ future.setFailure(t);
+ if (iothread) {
+ fireExceptionCaught(channel, t);
+ } else {
+ fireExceptionCaughtLater(channel, t);
+ }
+ }
+ }
+
+ /**
+ * Read is called when a Selector has been notified that the underlying channel
+ * was something to be read. The channel would previously have registered its interest
+ * in read operations.
+ *
+ * @param k The selection key which contains the Selector registration information.
+ */
+ protected abstract boolean read(SelectionKey k);
+
+ /**
+ * Create a new {@link Runnable} which will register the {@link AbstractNioWorker} with the {@link Channel}
+ *
+ * @param channel
+ * @param future
+ * @return task
+ */
+ protected abstract Runnable createRegisterTask(AbstractNioChannel> channel, ChannelFuture future);
+
+}
diff --git a/transport/src/main/java/io/netty/channel/socket/nio/AbstractWriteRequestQueue.java b/transport/src/main/java/io/netty/channel/socket/nio/AbstractWriteRequestQueue.java
deleted file mode 100644
index b9a848ead1..0000000000
--- a/transport/src/main/java/io/netty/channel/socket/nio/AbstractWriteRequestQueue.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright 2011 The Netty Project
- *
- * The Netty Project licenses this file to you under the Apache License,
- * version 2.0 (the "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at:
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations
- * under the License.
- */
-package io.netty.channel.socket.nio;
-
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.TimeUnit;
-
-import io.netty.channel.MessageEvent;
-import io.netty.util.internal.QueueFactory;
-
-abstract class AbstractWriteRequestQueue implements BlockingQueue {
-
- protected final BlockingQueue queue;
-
- public AbstractWriteRequestQueue() {
- this.queue = QueueFactory.createQueue(MessageEvent.class);
- }
-
- @Override
- public MessageEvent remove() {
- return queue.remove();
- }
-
- @Override
- public MessageEvent element() {
- return queue.element();
- }
-
- @Override
- public MessageEvent peek() {
- return queue.peek();
- }
-
- @Override
- public int size() {
- return queue.size();
- }
-
- @Override
- public boolean isEmpty() {
- return queue.isEmpty();
- }
-
- @Override
- public Iterator iterator() {
- return queue.iterator();
- }
-
- @Override
- public Object[] toArray() {
- return queue.toArray();
- }
-
- @Override
- public T[] toArray(T[] a) {
- return queue.toArray(a);
- }
-
- @Override
- public boolean containsAll(Collection> c) {
- return queue.containsAll(c);
- }
-
- @Override
- public boolean addAll(Collection extends MessageEvent> c) {
- return queue.addAll(c);
- }
-
- @Override
- public boolean removeAll(Collection> c) {
- return queue.removeAll(c);
- }
-
- @Override
- public boolean retainAll(Collection> c) {
- return queue.retainAll(c);
- }
-
- @Override
- public void clear() {
- queue.clear();
- }
-
- @Override
- public boolean add(MessageEvent e) {
- return queue.add(e);
- }
-
- @Override
- public void put(MessageEvent e) throws InterruptedException {
- queue.put(e);
- }
-
- @Override
- public boolean offer(MessageEvent e, long timeout, TimeUnit unit) throws InterruptedException {
- return queue.offer(e, timeout, unit);
- }
-
- @Override
- public MessageEvent take() throws InterruptedException {
- return queue.take();
- }
-
- @Override
- public MessageEvent poll(long timeout, TimeUnit unit) throws InterruptedException {
- return queue.poll(timeout, unit);
- }
-
- @Override
- public int remainingCapacity() {
- return queue.remainingCapacity();
- }
-
- @Override
- public boolean remove(Object o) {
- return queue.remove(o);
- }
-
- @Override
- public boolean contains(Object o) {
- return queue.contains(o);
- }
-
- @Override
- public int drainTo(Collection super MessageEvent> c) {
- return queue.drainTo(c);
- }
-
- @Override
- public int drainTo(Collection super MessageEvent> c, int maxElements) {
- return queue.drainTo(c, maxElements);
- }
-
-}
diff --git a/transport/src/main/java/io/netty/channel/socket/nio/NioClientSocketPipelineSink.java b/transport/src/main/java/io/netty/channel/socket/nio/NioClientSocketPipelineSink.java
index 32ffb4d5ce..512d17da00 100644
--- a/transport/src/main/java/io/netty/channel/socket/nio/NioClientSocketPipelineSink.java
+++ b/transport/src/main/java/io/netty/channel/socket/nio/NioClientSocketPipelineSink.java
@@ -31,7 +31,6 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
-import io.netty.channel.AbstractChannelSink;
import io.netty.channel.ChannelEvent;
import io.netty.channel.ChannelException;
import io.netty.channel.ChannelFuture;
@@ -45,7 +44,7 @@ import io.netty.logging.InternalLoggerFactory;
import io.netty.util.internal.DeadLockProofWorker;
import io.netty.util.internal.QueueFactory;
-class NioClientSocketPipelineSink extends AbstractChannelSink {
+class NioClientSocketPipelineSink extends AbstractNioChannelSink {
static final InternalLogger logger =
InternalLoggerFactory.getInstance(NioClientSocketPipelineSink.class);
@@ -113,7 +112,7 @@ class NioClientSocketPipelineSink extends AbstractChannelSink {
} else if (e instanceof MessageEvent) {
MessageEvent event = (MessageEvent) e;
NioSocketChannel channel = (NioSocketChannel) event.getChannel();
- boolean offered = channel.writeBuffer.offer(event);
+ boolean offered = channel.writeBufferQueue.offer(event);
assert offered;
channel.worker.writeFromUserCode(channel);
}
@@ -123,7 +122,7 @@ class NioClientSocketPipelineSink extends AbstractChannelSink {
NioClientSocketChannel channel, ChannelFuture future,
SocketAddress localAddress) {
try {
- channel.socket.socket().bind(localAddress);
+ channel.channel.socket().bind(localAddress);
channel.boundManually = true;
channel.setBound();
future.setSuccess();
@@ -138,7 +137,7 @@ class NioClientSocketPipelineSink extends AbstractChannelSink {
final NioClientSocketChannel channel, final ChannelFuture cf,
SocketAddress remoteAddress) {
try {
- if (channel.socket.connect(remoteAddress)) {
+ if (channel.channel.connect(remoteAddress)) {
channel.worker.register(channel, cf);
} else {
channel.getCloseFuture().addListener(new ChannelFutureListener() {
@@ -242,7 +241,7 @@ class NioClientSocketPipelineSink extends AbstractChannelSink {
wakenUp.set(false);
try {
- int selectedKeyCount = selector.select(500);
+ int selectedKeyCount = selector.select(10);
// 'wakenUp.compareAndSet(false, true)' is always evaluated
// before calling 'selector.wakeup()' to reduce the wake-up
@@ -282,9 +281,9 @@ class NioClientSocketPipelineSink extends AbstractChannelSink {
processSelectedKeys(selector.selectedKeys());
}
- // Handle connection timeout every 0.5 seconds approximately.
+ // Handle connection timeout every 10 milliseconds approximately.
long currentTimeNanos = System.nanoTime();
- if (currentTimeNanos - lastConnectTimeoutCheckTimeNanos >= 500 * 1000000L) {
+ if (currentTimeNanos - lastConnectTimeoutCheckTimeNanos >= 10 * 1000000L) {
lastConnectTimeoutCheckTimeNanos = currentTimeNanos;
processConnectTimeout(selector.keys(), currentTimeNanos);
}
@@ -400,7 +399,7 @@ class NioClientSocketPipelineSink extends AbstractChannelSink {
private void connect(SelectionKey k) {
NioClientSocketChannel ch = (NioClientSocketChannel) k.attachment();
try {
- if (ch.socket.finishConnect()) {
+ if (ch.channel.finishConnect()) {
k.cancel();
ch.worker.register(ch, ch.connectFuture);
}
@@ -430,7 +429,7 @@ class NioClientSocketPipelineSink extends AbstractChannelSink {
@Override
public void run() {
try {
- channel.socket.register(
+ channel.channel.register(
boss.selector, SelectionKey.OP_CONNECT, channel);
} catch (ClosedChannelException e) {
channel.worker.close(channel, succeededFuture(channel));
diff --git a/transport/src/main/java/io/netty/channel/socket/nio/NioDatagramChannel.java b/transport/src/main/java/io/netty/channel/socket/nio/NioDatagramChannel.java
index 89d54527cc..c8dcc77813 100644
--- a/transport/src/main/java/io/netty/channel/socket/nio/NioDatagramChannel.java
+++ b/transport/src/main/java/io/netty/channel/socket/nio/NioDatagramChannel.java
@@ -15,36 +15,23 @@
*/
package io.netty.channel.socket.nio;
-import static io.netty.channel.Channels.*;
+import static io.netty.channel.Channels.fireChannelOpen;
+import io.netty.channel.ChannelException;
+import io.netty.channel.ChannelFactory;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.ChannelSink;
+import io.netty.channel.socket.DatagramChannelConfig;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
-import java.net.SocketAddress;
import java.nio.channels.DatagramChannel;
-import java.util.Queue;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import io.netty.buffer.ChannelBuffer;
-import io.netty.channel.AbstractChannel;
-import io.netty.channel.Channel;
-import io.netty.channel.ChannelException;
-import io.netty.channel.ChannelFactory;
-import io.netty.channel.ChannelFuture;
-import io.netty.channel.ChannelPipeline;
-import io.netty.channel.ChannelSink;
-import io.netty.channel.MessageEvent;
-import io.netty.channel.socket.DatagramChannelConfig;
-import io.netty.channel.socket.nio.SocketSendBufferPool.SendBuffer;
-import io.netty.util.internal.LegacyLinkedTransferQueue;
-import io.netty.util.internal.ThreadLocalBoolean;
/**
* Provides an NIO based {@link io.netty.channel.socket.DatagramChannel}.
*/
-final class NioDatagramChannel extends AbstractChannel
+final class NioDatagramChannel extends AbstractNioChannel
implements io.netty.channel.socket.DatagramChannel {
/**
@@ -52,67 +39,7 @@ final class NioDatagramChannel extends AbstractChannel
*/
private final NioDatagramChannelConfig config;
- /**
- * The {@link NioDatagramWorker} for this NioDatagramChannnel.
- */
- final NioDatagramWorker worker;
-
- /**
- * The {@link DatagramChannel} that this channel uses.
- */
- private final java.nio.channels.DatagramChannel datagramChannel;
-
- /**
- * Monitor object to synchronize access to InterestedOps.
- */
- final Object interestOpsLock = new Object();
-
- /**
- * Monitor object for synchronizing access to the {@link WriteRequestQueue}.
- */
- final Object writeLock = new Object();
-
- /**
- * WriteTask that performs write operations.
- */
- final Runnable writeTask = new WriteTask();
-
- /**
- * Indicates if there is a {@link WriteTask} in the task queue.
- */
- final AtomicBoolean writeTaskInTaskQueue = new AtomicBoolean();
-
- /**
- * Queue of write {@link MessageEvent}s.
- */
- final Queue writeBufferQueue = new WriteRequestQueue();
-
- /**
- * Keeps track of the number of bytes that the {@link WriteRequestQueue} currently
- * contains.
- */
- final AtomicInteger writeBufferSize = new AtomicInteger();
-
- /**
- * Keeps track of the highWaterMark.
- */
- final AtomicInteger highWaterMarkCounter = new AtomicInteger();
-
- /**
- * The current write {@link MessageEvent}
- */
- MessageEvent currentWriteEvent;
- SendBuffer currentWriteBuffer;
-
- /**
- * Boolean that indicates that write operation is in progress.
- */
- boolean inWriteNowLoop;
- boolean writeSuspended;
-
- private volatile InetSocketAddress localAddress;
- volatile InetSocketAddress remoteAddress;
-
+
static NioDatagramChannel create(ChannelFactory factory,
ChannelPipeline pipeline, ChannelSink sink, NioDatagramWorker worker) {
NioDatagramChannel instance =
@@ -124,13 +51,11 @@ final class NioDatagramChannel extends AbstractChannel
private NioDatagramChannel(final ChannelFactory factory,
final ChannelPipeline pipeline, final ChannelSink sink,
final NioDatagramWorker worker) {
- super(null, factory, pipeline, sink);
- this.worker = worker;
- datagramChannel = openNonBlockingChannel();
- config = new DefaultNioDatagramChannelConfig(datagramChannel.socket());
+ super(null, factory, pipeline, sink, worker, openNonBlockingChannel());
+ config = new DefaultNioDatagramChannelConfig(channel.socket());
}
- private DatagramChannel openNonBlockingChannel() {
+ private static DatagramChannel openNonBlockingChannel() {
try {
final DatagramChannel channel = DatagramChannel.open();
channel.configureBlocking(false);
@@ -140,44 +65,15 @@ final class NioDatagramChannel extends AbstractChannel
}
}
- @Override
- public InetSocketAddress getLocalAddress() {
- InetSocketAddress localAddress = this.localAddress;
- if (localAddress == null) {
- try {
- this.localAddress = localAddress =
- (InetSocketAddress) datagramChannel.socket().getLocalSocketAddress();
- } catch (Throwable t) {
- // Sometimes fails on a closed socket in Windows.
- return null;
- }
- }
- return localAddress;
- }
-
- @Override
- public InetSocketAddress getRemoteAddress() {
- InetSocketAddress remoteAddress = this.remoteAddress;
- if (remoteAddress == null) {
- try {
- this.remoteAddress = remoteAddress =
- (InetSocketAddress) datagramChannel.socket().getRemoteSocketAddress();
- } catch (Throwable t) {
- // Sometimes fails on a closed socket in Windows.
- return null;
- }
- }
- return remoteAddress;
- }
@Override
public boolean isBound() {
- return isOpen() && datagramChannel.socket().isBound();
+ return isOpen() && channel.socket().isBound();
}
@Override
public boolean isConnected() {
- return datagramChannel.isConnected();
+ return channel.isConnected();
}
@Override
@@ -191,140 +87,7 @@ final class NioDatagramChannel extends AbstractChannel
}
DatagramChannel getDatagramChannel() {
- return datagramChannel;
- }
-
- @Override
- public int getInterestOps() {
- if (!isOpen()) {
- return Channel.OP_WRITE;
- }
-
- int interestOps = getRawInterestOps();
- int writeBufferSize = this.writeBufferSize.get();
- if (writeBufferSize != 0) {
- if (highWaterMarkCounter.get() > 0) {
- int lowWaterMark = getConfig().getWriteBufferLowWaterMark();
- if (writeBufferSize >= lowWaterMark) {
- interestOps |= Channel.OP_WRITE;
- } else {
- interestOps &= ~Channel.OP_WRITE;
- }
- } else {
- int highWaterMark = getConfig().getWriteBufferHighWaterMark();
- if (writeBufferSize >= highWaterMark) {
- interestOps |= Channel.OP_WRITE;
- } else {
- interestOps &= ~Channel.OP_WRITE;
- }
- }
- } else {
- interestOps &= ~Channel.OP_WRITE;
- }
-
- return interestOps;
- }
-
- int getRawInterestOps() {
- return super.getInterestOps();
- }
-
- void setRawInterestOpsNow(int interestOps) {
- super.setInterestOpsNow(interestOps);
- }
-
- @Override
- public ChannelFuture write(Object message, SocketAddress remoteAddress) {
- if (remoteAddress == null || remoteAddress.equals(getRemoteAddress())) {
- return super.write(message, null);
- } else {
- return super.write(message, remoteAddress);
- }
- }
-
- /**
- * {@link WriteRequestQueue} is an extension of {@link AbstractWriteRequestQueue}
- * that adds support for highWaterMark checking of the write buffer size.
- */
- private final class WriteRequestQueue extends
- AbstractWriteRequestQueue {
-
- private final ThreadLocalBoolean notifying = new ThreadLocalBoolean();
-
-
- /**
- * This method first delegates to {@link LegacyLinkedTransferQueue#offer(Object)} and
- * adds support for keeping track of the size of the this write buffer.
- */
- @Override
- public boolean offer(MessageEvent e) {
- boolean success = queue.offer(e);
- assert success;
-
- int messageSize = getMessageSize(e);
- int newWriteBufferSize = writeBufferSize.addAndGet(messageSize);
- int highWaterMark = getConfig().getWriteBufferHighWaterMark();
-
- if (newWriteBufferSize >= highWaterMark) {
- if (newWriteBufferSize - messageSize < highWaterMark) {
- highWaterMarkCounter.incrementAndGet();
- if (!notifying.get()) {
- notifying.set(Boolean.TRUE);
- fireChannelInterestChanged(NioDatagramChannel.this);
- notifying.set(Boolean.FALSE);
- }
- }
- }
- return true;
- }
-
- /**
- * This method first delegates to {@link LegacyLinkedTransferQueue#poll()} and
- * adds support for keeping track of the size of the this writebuffers queue.
- */
- @Override
- public MessageEvent poll() {
- MessageEvent e = queue.poll();
- if (e != null) {
- int messageSize = getMessageSize(e);
- int newWriteBufferSize = writeBufferSize.addAndGet(-messageSize);
- int lowWaterMark = getConfig().getWriteBufferLowWaterMark();
-
- if (newWriteBufferSize == 0 || newWriteBufferSize < lowWaterMark) {
- if (newWriteBufferSize + messageSize >= lowWaterMark) {
- highWaterMarkCounter.decrementAndGet();
- if (isBound() && !notifying.get()) {
- notifying.set(Boolean.TRUE);
- fireChannelInterestChanged(NioDatagramChannel.this);
- notifying.set(Boolean.FALSE);
- }
- }
- }
- }
- return e;
- }
-
- private int getMessageSize(MessageEvent e) {
- Object m = e.getMessage();
- if (m instanceof ChannelBuffer) {
- return ((ChannelBuffer) m).readableBytes();
- }
- return 0;
- }
- }
-
- /**
- * WriteTask is a simple runnable performs writes by delegating the {@link NioDatagramWorker}.
- */
- private final class WriteTask implements Runnable {
- WriteTask() {
- }
-
- @Override
- public void run() {
- writeTaskInTaskQueue.set(false);
- worker.writeFromTaskLoop(NioDatagramChannel.this);
- }
+ return channel;
}
@Override
@@ -348,4 +111,14 @@ final class NioDatagramChannel extends AbstractChannel
NetworkInterface networkInterface) {
throw new UnsupportedOperationException();
}
+
+ @Override
+ InetSocketAddress getLocalSocketAddress() throws Exception {
+ return (InetSocketAddress) channel.socket().getLocalSocketAddress();
+ }
+
+ @Override
+ InetSocketAddress getRemoteSocketAddress() throws Exception {
+ return (InetSocketAddress) channel.socket().getRemoteSocketAddress();
+ }
}
diff --git a/transport/src/main/java/io/netty/channel/socket/nio/NioDatagramPipelineSink.java b/transport/src/main/java/io/netty/channel/socket/nio/NioDatagramPipelineSink.java
index 60b6943725..401d6dbf8b 100644
--- a/transport/src/main/java/io/netty/channel/socket/nio/NioDatagramPipelineSink.java
+++ b/transport/src/main/java/io/netty/channel/socket/nio/NioDatagramPipelineSink.java
@@ -22,7 +22,6 @@ import java.net.SocketAddress;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
-import io.netty.channel.AbstractChannelSink;
import io.netty.channel.ChannelEvent;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
@@ -35,7 +34,7 @@ import io.netty.channel.MessageEvent;
* Receives downstream events from a {@link ChannelPipeline}. It contains
* an array of I/O workers.
*/
-class NioDatagramPipelineSink extends AbstractChannelSink {
+class NioDatagramPipelineSink extends AbstractNioChannelSink {
private final NioDatagramWorker[] workers;
private final AtomicInteger workerIndex = new AtomicInteger();
diff --git a/transport/src/main/java/io/netty/channel/socket/nio/NioDatagramWorker.java b/transport/src/main/java/io/netty/channel/socket/nio/NioDatagramWorker.java
index 1e8583e856..6191df2e08 100644
--- a/transport/src/main/java/io/netty/channel/socket/nio/NioDatagramWorker.java
+++ b/transport/src/main/java/io/netty/channel/socket/nio/NioDatagramWorker.java
@@ -15,102 +15,30 @@
*/
package io.netty.channel.socket.nio;
-import static io.netty.channel.Channels.*;
-
-import java.io.IOException;
-import java.net.SocketAddress;
-import java.nio.ByteBuffer;
-import java.nio.channels.AsynchronousCloseException;
-import java.nio.channels.CancelledKeyException;
-import java.nio.channels.ClosedChannelException;
-import java.nio.channels.DatagramChannel;
-import java.nio.channels.NotYetBoundException;
-import java.nio.channels.SelectionKey;
-import java.nio.channels.Selector;
-import java.util.Iterator;
-import java.util.Queue;
-import java.util.Set;
-import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-
+import static io.netty.channel.Channels.fireChannelDisconnected;
+import static io.netty.channel.Channels.fireChannelDisconnectedLater;
+import static io.netty.channel.Channels.fireExceptionCaught;
+import static io.netty.channel.Channels.fireExceptionCaughtLater;
+import static io.netty.channel.Channels.fireMessageReceived;
+import static io.netty.channel.Channels.succeededFuture;
import io.netty.buffer.ChannelBufferFactory;
-import io.netty.channel.Channel;
import io.netty.channel.ChannelException;
import io.netty.channel.ChannelFuture;
-import io.netty.channel.MessageEvent;
import io.netty.channel.ReceiveBufferSizePredictor;
-import io.netty.channel.socket.nio.SocketSendBufferPool.SendBuffer;
-import io.netty.logging.InternalLogger;
-import io.netty.logging.InternalLoggerFactory;
-import io.netty.util.internal.QueueFactory;
+
+import java.net.SocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.DatagramChannel;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.util.concurrent.Executor;
/**
* A class responsible for registering channels with {@link Selector}.
* It also implements the {@link Selector} loop.
*/
-class NioDatagramWorker implements Runnable {
- /**
- * Internal Netty logger.
- */
- private static final InternalLogger logger = InternalLoggerFactory
- .getInstance(NioDatagramWorker.class);
-
- /**
- * Executor used to execute {@link Runnable}s such as
- * {@link ChannelRegistionTask}.
- */
- private final Executor executor;
-
- /**
- * Boolean to indicate if this worker has been started.
- */
- private boolean started;
-
- /**
- * If this worker has been started thread will be a reference to the thread
- * used when starting. i.e. the current thread when the run method is executed.
- */
- private volatile Thread thread;
-
- /**
- * The NIO {@link Selector}.
- */
- volatile Selector selector;
-
- /**
- * Boolean that controls determines if a blocked Selector.select should
- * break out of its selection process. In our case we use a timeone for
- * the select method and the select method will block for that time unless
- * waken up.
- */
- private final AtomicBoolean wakenUp = new AtomicBoolean();
-
- /**
- * Lock for this workers Selector.
- */
- private final ReadWriteLock selectorGuard = new ReentrantReadWriteLock();
-
- /**
- * Monitor object used to synchronize selector open/close.
- */
- private final Object startStopLock = new Object();
-
- /**
- * Queue of {@link ChannelRegistionTask}s
- */
- private final Queue registerTaskQueue = QueueFactory.createQueue(Runnable.class);
-
- /**
- * Queue of WriteTasks
- */
- private final Queue writeTaskQueue = QueueFactory.createQueue(Runnable.class);
-
- private volatile int cancelledKeys; // should use AtomicInteger but we just need approximation
-
- private final SocketSendBufferPool sendBufferPool = new SocketSendBufferPool();
+class NioDatagramWorker extends AbstractNioWorker {
/**
* Sole constructor.
@@ -119,248 +47,11 @@ class NioDatagramWorker implements Runnable {
* such as {@link ChannelRegistionTask}
*/
NioDatagramWorker(final Executor executor) {
- this.executor = executor;
+ super(executor);
}
- /**
- * Registers the passed-in channel with a selector.
- *
- * @param channel the channel to register
- * @param future the {@link ChannelFuture} that has to be notified on
- * completion
- */
- void register(final NioDatagramChannel channel, final ChannelFuture future) {
- final Runnable channelRegTask = new ChannelRegistionTask(channel,
- future);
- Selector selector;
-
- synchronized (startStopLock) {
- if (!started) {
- // Open a selector if this worker didn't start yet.
- try {
- this.selector = selector = Selector.open();
- } catch (final Throwable t) {
- throw new ChannelException("Failed to create a selector.",
- t);
- }
-
- boolean success = false;
- try {
- // Start the main selector loop. See run() for details.
- executor.execute(this);
- success = true;
- } finally {
- if (!success) {
- try {
- // Release the Selector if the execution fails.
- selector.close();
- } catch (final Throwable t) {
- if (logger.isWarnEnabled()) {
- logger.warn("Failed to close a selector.", t);
- }
- }
- this.selector = selector = null;
- // The method will return to the caller at this point.
- }
- }
- } else {
- // Use the existing selector if this worker has been started.
- selector = this.selector;
- }
- assert selector != null && selector.isOpen();
-
- started = true;
-
- // "Add" the registration task to the register task queue.
- boolean offered = registerTaskQueue.offer(channelRegTask);
- assert offered;
- }
-
- if (wakenUp.compareAndSet(false, true)) {
- selector.wakeup();
- }
- }
-
- /**
- * Selector loop.
- */
@Override
- public void run() {
- // Store a ref to the current thread.
- thread = Thread.currentThread();
-
- final Selector selector = this.selector;
- boolean shutdown = false;
-
- for (;;) {
- wakenUp.set(false);
-
- if (NioProviderMetadata.CONSTRAINT_LEVEL != 0) {
- selectorGuard.writeLock().lock();
- // This empty synchronization block prevents the selector from acquiring its lock.
- selectorGuard.writeLock().unlock();
- }
-
- try {
- SelectorUtil.select(selector);
-
- // 'wakenUp.compareAndSet(false, true)' is always evaluated
- // before calling 'selector.wakeup()' to reduce the wake-up
- // overhead. (Selector.wakeup() is an expensive operation.)
- //
- // However, there is a race condition in this approach.
- // The race condition is triggered when 'wakenUp' is set to
- // true too early.
- //
- // 'wakenUp' is set to true too early if:
- // 1) Selector is waken up between 'wakenUp.set(false)' and
- // 'selector.select(...)'. (BAD)
- // 2) Selector is waken up between 'selector.select(...)' and
- // 'if (wakenUp.get()) { ... }'. (OK)
- //
- // In the first case, 'wakenUp' is set to true and the
- // following 'selector.select(...)' will wake up immediately.
- // Until 'wakenUp' is set to false again in the next round,
- // 'wakenUp.compareAndSet(false, true)' will fail, and therefore
- // any attempt to wake up the Selector will fail, too, causing
- // the following 'selector.select(...)' call to block
- // unnecessarily.
- //
- // To fix this problem, we wake up the selector again if wakenUp
- // is true immediately after selector.select(...).
- // It is inefficient in that it wakes up the selector for both
- // the first case (BAD - wake-up required) and the second case
- // (OK - no wake-up required).
-
- if (wakenUp.get()) {
- selector.wakeup();
- }
-
- cancelledKeys = 0;
- processRegisterTaskQueue();
- processWriteTaskQueue();
- processSelectedKeys(selector.selectedKeys());
-
- // Exit the loop when there's nothing to handle (the registered
- // key set is empty.
- // The shutdown flag is used to delay the shutdown of this
- // loop to avoid excessive Selector creation when
- // connections are registered in a one-by-one manner instead of
- // concurrent manner.
- if (selector.keys().isEmpty()) {
- if (shutdown || executor instanceof ExecutorService &&
- ((ExecutorService) executor).isShutdown()) {
- synchronized (startStopLock) {
- if (registerTaskQueue.isEmpty() &&
- selector.keys().isEmpty()) {
- started = false;
- try {
- selector.close();
- } catch (IOException e) {
- logger.warn("Failed to close a selector.",
- e);
- } finally {
- this.selector = null;
- }
- break;
- } else {
- shutdown = false;
- }
- }
- } else {
- // Give one more second.
- shutdown = true;
- }
- } else {
- shutdown = false;
- }
- } catch (Throwable t) {
- logger.warn("Unexpected exception in the selector loop.", t);
-
- // Prevent possible consecutive immediate failures that lead to
- // excessive CPU consumption.
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- // Ignore.
- }
- }
- }
- }
-
- /**
- * Will go through all the {@link ChannelRegistionTask}s in the
- * task queue and run them (registering them).
- */
- private void processRegisterTaskQueue() throws IOException {
- for (;;) {
- final Runnable task = registerTaskQueue.poll();
- if (task == null) {
- break;
- }
-
- task.run();
- cleanUpCancelledKeys();
- }
- }
-
- /**
- * Will go through all the WriteTasks and run them.
- */
- private void processWriteTaskQueue() throws IOException {
- for (;;) {
- final Runnable task = writeTaskQueue.poll();
- if (task == null) {
- break;
- }
-
- task.run();
- cleanUpCancelledKeys();
- }
- }
-
- private void processSelectedKeys(final Set selectedKeys) throws IOException {
- for (Iterator i = selectedKeys.iterator(); i.hasNext();) {
- SelectionKey k = i.next();
- i.remove();
- try {
- int readyOps = k.readyOps();
- if ((readyOps & SelectionKey.OP_READ) != 0 || readyOps == 0) {
- if (!read(k)) {
- // Connection already closed - no need to handle write.
- continue;
- }
- }
- if ((readyOps & SelectionKey.OP_WRITE) != 0) {
- writeFromSelectorLoop(k);
- }
- } catch (CancelledKeyException e) {
- close(k);
- }
-
- if (cleanUpCancelledKeys()) {
- break; // Break the loop to avoid ConcurrentModificationException
- }
- }
- }
-
- private boolean cleanUpCancelledKeys() throws IOException {
- if (cancelledKeys >= NioWorker.CLEANUP_INTERVAL) {
- cancelledKeys = 0;
- selector.selectNow();
- return true;
- }
- return false;
- }
-
- /**
- * Read is called when a Selector has been notified that the underlying channel
- * was something to be read. The channel would previously have registered its interest
- * in read operations.
- *
- * @param key The selection key which contains the Selector registration information.
- */
- private boolean read(final SelectionKey key) {
+ protected boolean read(final SelectionKey key) {
final NioDatagramChannel channel = (NioDatagramChannel) key.attachment();
ReceiveBufferSizePredictor predictor =
channel.getConfig().getReceiveBufferSizePredictor();
@@ -410,53 +101,10 @@ class NioDatagramWorker implements Runnable {
return true;
}
+
- private void close(SelectionKey k) {
- final NioDatagramChannel ch = (NioDatagramChannel) k.attachment();
- close(ch, succeededFuture(ch));
- }
-
- void writeFromUserCode(final NioDatagramChannel channel) {
- /*
- * Note that we are not checking if the channel is connected. Connected
- * has a different meaning in UDP and means that the channels socket is
- * configured to only send and receive from a given remote peer.
- */
- if (!channel.isBound()) {
- cleanUpWriteBuffer(channel);
- return;
- }
-
- if (scheduleWriteIfNecessary(channel)) {
- return;
- }
-
- // From here, we are sure Thread.currentThread() == workerThread.
-
- if (channel.writeSuspended) {
- return;
- }
-
- if (channel.inWriteNowLoop) {
- return;
- }
-
- write0(channel);
- }
-
- void writeFromTaskLoop(final NioDatagramChannel ch) {
- if (!ch.writeSuspended) {
- write0(ch);
- }
- }
-
- void writeFromSelectorLoop(final SelectionKey k) {
- NioDatagramChannel ch = (NioDatagramChannel) k.attachment();
- ch.writeSuspended = false;
- write0(ch);
- }
-
- private boolean scheduleWriteIfNecessary(final NioDatagramChannel channel) {
+ @Override
+ protected boolean scheduleWriteIfNecessary(final AbstractNioChannel> channel) {
final Thread workerThread = thread;
if (workerThread == null || Thread.currentThread() != workerThread) {
if (channel.writeTaskInTaskQueue.compareAndSet(false, true)) {
@@ -477,335 +125,36 @@ class NioDatagramWorker implements Runnable {
return false;
}
- private void write0(final NioDatagramChannel channel) {
-
- boolean addOpWrite = false;
- boolean removeOpWrite = false;
-
- long writtenBytes = 0;
-
- final SocketSendBufferPool sendBufferPool = this.sendBufferPool;
- final DatagramChannel ch = channel.getDatagramChannel();
- final Queue writeBuffer = channel.writeBufferQueue;
- final int writeSpinCount = channel.getConfig().getWriteSpinCount();
- synchronized (channel.writeLock) {
- // inform the channel that write is in-progress
- channel.inWriteNowLoop = true;
-
- // loop forever...
- for (;;) {
- MessageEvent evt = channel.currentWriteEvent;
- SendBuffer buf;
- if (evt == null) {
- if ((channel.currentWriteEvent = evt = writeBuffer.poll()) == null) {
- removeOpWrite = true;
- channel.writeSuspended = false;
- break;
- }
-
- channel.currentWriteBuffer = buf = sendBufferPool.acquire(evt.getMessage());
- } else {
- buf = channel.currentWriteBuffer;
- }
-
- try {
- long localWrittenBytes = 0;
- SocketAddress raddr = evt.getRemoteAddress();
- if (raddr == null) {
- for (int i = writeSpinCount; i > 0; i --) {
- localWrittenBytes = buf.transferTo(ch);
- if (localWrittenBytes != 0) {
- writtenBytes += localWrittenBytes;
- break;
- }
- if (buf.finished()) {
- break;
- }
- }
- } else {
- for (int i = writeSpinCount; i > 0; i --) {
- localWrittenBytes = buf.transferTo(ch, raddr);
- if (localWrittenBytes != 0) {
- writtenBytes += localWrittenBytes;
- break;
- }
- if (buf.finished()) {
- break;
- }
- }
- }
-
- if (localWrittenBytes > 0 || buf.finished()) {
- // Successful write - proceed to the next message.
- buf.release();
- ChannelFuture future = evt.getFuture();
- channel.currentWriteEvent = null;
- channel.currentWriteBuffer = null;
- evt = null;
- buf = null;
- future.setSuccess();
- } else {
- // Not written at all - perhaps the kernel buffer is full.
- addOpWrite = true;
- channel.writeSuspended = true;
- break;
- }
- } catch (final AsynchronousCloseException e) {
- // Doesn't need a user attention - ignore.
- } catch (final Throwable t) {
- buf.release();
- ChannelFuture future = evt.getFuture();
- channel.currentWriteEvent = null;
- channel.currentWriteBuffer = null;
- buf = null;
- evt = null;
- future.setFailure(t);
- fireExceptionCaught(channel, t);
- }
- }
- channel.inWriteNowLoop = false;
-
- // Initially, the following block was executed after releasing
- // the writeLock, but there was a race condition, and it has to be
- // executed before releasing the writeLock:
- //
- // https://issues.jboss.org/browse/NETTY-410
- //
- if (addOpWrite) {
- setOpWrite(channel);
- } else if (removeOpWrite) {
- clearOpWrite(channel);
- }
- }
-
- fireWriteComplete(channel, writtenBytes);
- }
-
- private void setOpWrite(final NioDatagramChannel channel) {
- Selector selector = this.selector;
- SelectionKey key = channel.getDatagramChannel().keyFor(selector);
- if (key == null) {
- return;
- }
- if (!key.isValid()) {
- close(key);
- return;
- }
-
- // interestOps can change at any time and at any thread.
- // Acquire a lock to avoid possible race condition.
- synchronized (channel.interestOpsLock) {
- int interestOps = channel.getRawInterestOps();
- if ((interestOps & SelectionKey.OP_WRITE) == 0) {
- interestOps |= SelectionKey.OP_WRITE;
- key.interestOps(interestOps);
- channel.setRawInterestOpsNow(interestOps);
- }
- }
- }
-
- private void clearOpWrite(NioDatagramChannel channel) {
- Selector selector = this.selector;
- SelectionKey key = channel.getDatagramChannel().keyFor(selector);
- if (key == null) {
- return;
- }
- if (!key.isValid()) {
- close(key);
- return;
- }
-
- // interestOps can change at any time and at any thread.
- // Acquire a lock to avoid possible race condition.
- synchronized (channel.interestOpsLock) {
- int interestOps = channel.getRawInterestOps();
- if ((interestOps & SelectionKey.OP_WRITE) != 0) {
- interestOps &= ~SelectionKey.OP_WRITE;
- key.interestOps(interestOps);
- channel.setRawInterestOpsNow(interestOps);
- }
- }
- }
static void disconnect(NioDatagramChannel channel, ChannelFuture future) {
boolean connected = channel.isConnected();
+ boolean iothread = isIoThread(channel);
try {
channel.getDatagramChannel().disconnect();
future.setSuccess();
if (connected) {
- fireChannelDisconnected(channel);
- }
- } catch (Throwable t) {
- future.setFailure(t);
- fireExceptionCaught(channel, t);
- }
- }
-
- void close(final NioDatagramChannel channel,
- final ChannelFuture future) {
- boolean connected = channel.isConnected();
- boolean bound = channel.isBound();
- try {
- channel.getDatagramChannel().close();
- cancelledKeys ++;
-
- if (channel.setClosed()) {
- future.setSuccess();
- if (connected) {
+ if (iothread) {
fireChannelDisconnected(channel);
+ } else {
+ fireChannelDisconnectedLater(channel);
}
- if (bound) {
- fireChannelUnbound(channel);
- }
-
- cleanUpWriteBuffer(channel);
- fireChannelClosed(channel);
- } else {
- future.setSuccess();
}
} catch (Throwable t) {
future.setFailure(t);
- fireExceptionCaught(channel, t);
+ if (iothread) {
+ fireExceptionCaught(channel, t);
+ } else {
+ fireExceptionCaughtLater(channel, t);
+ }
}
}
- private void cleanUpWriteBuffer(final NioDatagramChannel channel) {
- Exception cause = null;
- boolean fireExceptionCaught = false;
- // Clean up the stale messages in the write buffer.
- synchronized (channel.writeLock) {
- MessageEvent evt = channel.currentWriteEvent;
- if (evt != null) {
- // Create the exception only once to avoid the excessive overhead
- // caused by fillStackTrace.
- if (channel.isOpen()) {
- cause = new NotYetBoundException();
- } else {
- cause = new ClosedChannelException();
- }
-
- ChannelFuture future = evt.getFuture();
- channel.currentWriteBuffer.release();
- channel.currentWriteBuffer = null;
- channel.currentWriteEvent = null;
- evt = null;
- future.setFailure(cause);
- fireExceptionCaught = true;
- }
-
- Queue writeBuffer = channel.writeBufferQueue;
- if (!writeBuffer.isEmpty()) {
- // Create the exception only once to avoid the excessive overhead
- // caused by fillStackTrace.
- if (cause == null) {
- if (channel.isOpen()) {
- cause = new NotYetBoundException();
- } else {
- cause = new ClosedChannelException();
- }
- }
-
- for (;;) {
- evt = writeBuffer.poll();
- if (evt == null) {
- break;
- }
- evt.getFuture().setFailure(cause);
- fireExceptionCaught = true;
- }
- }
- }
-
- if (fireExceptionCaught) {
- fireExceptionCaught(channel, cause);
- }
+ @Override
+ protected Runnable createRegisterTask(AbstractNioChannel> channel, ChannelFuture future) {
+ return new ChannelRegistionTask((NioDatagramChannel) channel, future);
}
-
- void setInterestOps(final NioDatagramChannel channel,
- ChannelFuture future, int interestOps) {
-
- boolean changed = false;
- try {
- // interestOps can change at any time and by any thread.
- // Acquire a lock to avoid possible race condition.
- synchronized (channel.interestOpsLock) {
- final Selector selector = this.selector;
- final SelectionKey key = channel.getDatagramChannel().keyFor(selector);
-
- if (key == null || selector == null) {
- // Not registered to the worker yet.
- // Set the rawInterestOps immediately; RegisterTask will pick it up.
- channel.setRawInterestOpsNow(interestOps);
- return;
- }
-
- // Override OP_WRITE flag - a user cannot change this flag.
- interestOps &= ~Channel.OP_WRITE;
- interestOps |= channel.getRawInterestOps() & Channel.OP_WRITE;
-
- switch (NioProviderMetadata.CONSTRAINT_LEVEL) {
- case 0:
- if (channel.getRawInterestOps() != interestOps) {
- // Set the interesteOps on the SelectionKey
- key.interestOps(interestOps);
- // If the worker thread (the one that that might possibly be blocked
- // in a select() call) is not the thread executing this method wakeup
- // the select() operation.
- if (Thread.currentThread() != thread &&
- wakenUp.compareAndSet(false, true)) {
- selector.wakeup();
- }
- changed = true;
- }
- break;
- case 1:
- case 2:
- if (channel.getRawInterestOps() != interestOps) {
- if (Thread.currentThread() == thread) {
- // Going to set the interestOps from the same thread.
- // Set the interesteOps on the SelectionKey
- key.interestOps(interestOps);
- changed = true;
- } else {
- // Going to set the interestOps from a different thread
- // and some old provides will need synchronization.
- selectorGuard.readLock().lock();
- try {
- if (wakenUp.compareAndSet(false, true)) {
- selector.wakeup();
- }
- key.interestOps(interestOps);
- changed = true;
- } finally {
- selectorGuard.readLock().unlock();
- }
- }
- }
- break;
- default:
- throw new Error();
- }
- if (changed) {
- channel.setRawInterestOpsNow(interestOps);
- }
- }
-
- future.setSuccess();
- if (changed) {
- fireChannelInterestChanged(channel);
- }
- } catch (final CancelledKeyException e) {
- // setInterestOps() was called on a closed channel.
- ClosedChannelException cce = new ClosedChannelException();
- future.setFailure(cce);
- fireExceptionCaught(channel, cce);
- } catch (final Throwable t) {
- future.setFailure(t);
- fireExceptionCaught(channel, t);
- }
- }
-
+
/**
* RegisterTask is a task responsible for registering a channel with a
* selector.
@@ -854,4 +203,5 @@ class NioDatagramWorker implements Runnable {
}
}
}
+
}
diff --git a/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketPipelineSink.java b/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketPipelineSink.java
index ce25a36f91..965c585827 100644
--- a/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketPipelineSink.java
+++ b/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketPipelineSink.java
@@ -29,7 +29,6 @@ import java.nio.channels.SocketChannel;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
-import io.netty.channel.AbstractChannelSink;
import io.netty.channel.Channel;
import io.netty.channel.ChannelEvent;
import io.netty.channel.ChannelFuture;
@@ -41,7 +40,7 @@ import io.netty.logging.InternalLogger;
import io.netty.logging.InternalLoggerFactory;
import io.netty.util.internal.DeadLockProofWorker;
-class NioServerSocketPipelineSink extends AbstractChannelSink {
+class NioServerSocketPipelineSink extends AbstractNioChannelSink {
static final InternalLogger logger =
InternalLoggerFactory.getInstance(NioServerSocketPipelineSink.class);
@@ -122,7 +121,7 @@ class NioServerSocketPipelineSink extends AbstractChannelSink {
} else if (e instanceof MessageEvent) {
MessageEvent event = (MessageEvent) e;
NioSocketChannel channel = (NioSocketChannel) event.getChannel();
- boolean offered = channel.writeBuffer.offer(event);
+ boolean offered = channel.writeBufferQueue.offer(event);
assert offered;
channel.worker.writeFromUserCode(channel);
}
diff --git a/transport/src/main/java/io/netty/channel/socket/nio/NioSocketChannel.java b/transport/src/main/java/io/netty/channel/socket/nio/NioSocketChannel.java
index e73d133d2b..37f4e669ff 100644
--- a/transport/src/main/java/io/netty/channel/socket/nio/NioSocketChannel.java
+++ b/transport/src/main/java/io/netty/channel/socket/nio/NioSocketChannel.java
@@ -15,27 +15,15 @@
*/
package io.netty.channel.socket.nio;
-import static io.netty.channel.Channels.*;
-
-import java.net.InetSocketAddress;
-import java.net.SocketAddress;
-import java.nio.channels.SocketChannel;
-import java.util.Queue;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import io.netty.buffer.ChannelBuffer;
-import io.netty.channel.AbstractChannel;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFactory;
-import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelSink;
-import io.netty.channel.MessageEvent;
-import io.netty.channel.socket.nio.SocketSendBufferPool.SendBuffer;
-import io.netty.util.internal.ThreadLocalBoolean;
-class NioSocketChannel extends AbstractChannel
+import java.net.InetSocketAddress;
+import java.nio.channels.SocketChannel;
+
+class NioSocketChannel extends AbstractNioChannel
implements io.netty.channel.socket.SocketChannel {
private static final int ST_OPEN = 0;
@@ -44,35 +32,14 @@ class NioSocketChannel extends AbstractChannel
private static final int ST_CLOSED = -1;
volatile int state = ST_OPEN;
- final SocketChannel socket;
- final NioWorker worker;
private final NioSocketChannelConfig config;
- private volatile InetSocketAddress localAddress;
- private volatile InetSocketAddress remoteAddress;
-
- final Object interestOpsLock = new Object();
- final Object writeLock = new Object();
-
- final Runnable writeTask = new WriteTask();
- final AtomicBoolean writeTaskInTaskQueue = new AtomicBoolean();
-
- final Queue writeBuffer = new WriteRequestQueue();
- final AtomicInteger writeBufferSize = new AtomicInteger();
- final AtomicInteger highWaterMarkCounter = new AtomicInteger();
- boolean inWriteNowLoop;
- boolean writeSuspended;
-
- MessageEvent currentWriteEvent;
- SendBuffer currentWriteBuffer;
public NioSocketChannel(
Channel parent, ChannelFactory factory,
ChannelPipeline pipeline, ChannelSink sink,
SocketChannel socket, NioWorker worker) {
- super(parent, factory, pipeline, sink);
+ super(parent, factory, pipeline, sink, worker, socket);
- this.socket = socket;
- this.worker = worker;
config = new DefaultNioSocketChannelConfig(socket.socket());
}
@@ -81,36 +48,6 @@ class NioSocketChannel extends AbstractChannel
return config;
}
- @Override
- public InetSocketAddress getLocalAddress() {
- InetSocketAddress localAddress = this.localAddress;
- if (localAddress == null) {
- try {
- this.localAddress = localAddress =
- (InetSocketAddress) socket.socket().getLocalSocketAddress();
- } catch (Throwable t) {
- // Sometimes fails on a closed socket in Windows.
- return null;
- }
- }
- return localAddress;
- }
-
- @Override
- public InetSocketAddress getRemoteAddress() {
- InetSocketAddress remoteAddress = this.remoteAddress;
- if (remoteAddress == null) {
- try {
- this.remoteAddress = remoteAddress =
- (InetSocketAddress) socket.socket().getRemoteSocketAddress();
- } catch (Throwable t) {
- // Sometimes fails on a closed socket in Windows.
- return null;
- }
- }
- return remoteAddress;
- }
-
@Override
public boolean isOpen() {
return state >= ST_OPEN;
@@ -143,123 +80,14 @@ class NioSocketChannel extends AbstractChannel
return super.setClosed();
}
+
@Override
- public int getInterestOps() {
- if (!isOpen()) {
- return Channel.OP_WRITE;
- }
-
- int interestOps = getRawInterestOps();
- int writeBufferSize = this.writeBufferSize.get();
- if (writeBufferSize != 0) {
- if (highWaterMarkCounter.get() > 0) {
- int lowWaterMark = getConfig().getWriteBufferLowWaterMark();
- if (writeBufferSize >= lowWaterMark) {
- interestOps |= Channel.OP_WRITE;
- } else {
- interestOps &= ~Channel.OP_WRITE;
- }
- } else {
- int highWaterMark = getConfig().getWriteBufferHighWaterMark();
- if (writeBufferSize >= highWaterMark) {
- interestOps |= Channel.OP_WRITE;
- } else {
- interestOps &= ~Channel.OP_WRITE;
- }
- }
- } else {
- interestOps &= ~Channel.OP_WRITE;
- }
-
- return interestOps;
- }
-
- int getRawInterestOps() {
- return super.getInterestOps();
- }
-
- void setRawInterestOpsNow(int interestOps) {
- super.setInterestOpsNow(interestOps);
+ InetSocketAddress getLocalSocketAddress() throws Exception {
+ return (InetSocketAddress) channel.socket().getLocalSocketAddress();
}
@Override
- public ChannelFuture write(Object message, SocketAddress remoteAddress) {
- if (remoteAddress == null || remoteAddress.equals(getRemoteAddress())) {
- return super.write(message, null);
- } else {
- return getUnsupportedOperationFuture();
- }
- }
-
- private final class WriteRequestQueue extends AbstractWriteRequestQueue {
-
- private final ThreadLocalBoolean notifying = new ThreadLocalBoolean();
-
- WriteRequestQueue() {
- }
-
- @Override
- public boolean offer(MessageEvent e) {
- boolean success = queue.offer(e);
- assert success;
-
- int messageSize = getMessageSize(e);
- int newWriteBufferSize = writeBufferSize.addAndGet(messageSize);
- int highWaterMark = getConfig().getWriteBufferHighWaterMark();
-
- if (newWriteBufferSize >= highWaterMark) {
- if (newWriteBufferSize - messageSize < highWaterMark) {
- highWaterMarkCounter.incrementAndGet();
- if (!notifying.get()) {
- notifying.set(Boolean.TRUE);
- fireChannelInterestChanged(NioSocketChannel.this);
- notifying.set(Boolean.FALSE);
- }
- }
- }
- return true;
- }
-
- @Override
- public MessageEvent poll() {
- MessageEvent e = queue.poll();
- if (e != null) {
- int messageSize = getMessageSize(e);
- int newWriteBufferSize = writeBufferSize.addAndGet(-messageSize);
- int lowWaterMark = getConfig().getWriteBufferLowWaterMark();
-
- if (newWriteBufferSize == 0 || newWriteBufferSize < lowWaterMark) {
- if (newWriteBufferSize + messageSize >= lowWaterMark) {
- highWaterMarkCounter.decrementAndGet();
- if (isConnected() && !notifying.get()) {
- notifying.set(Boolean.TRUE);
- fireChannelInterestChanged(NioSocketChannel.this);
- notifying.set(Boolean.FALSE);
- }
- }
- }
- }
- return e;
- }
-
- private int getMessageSize(MessageEvent e) {
- Object m = e.getMessage();
- if (m instanceof ChannelBuffer) {
- return ((ChannelBuffer) m).readableBytes();
- }
- return 0;
- }
- }
-
- private final class WriteTask implements Runnable {
-
- WriteTask() {
- }
-
- @Override
- public void run() {
- writeTaskInTaskQueue.set(false);
- worker.writeFromTaskLoop(NioSocketChannel.this);
- }
+ InetSocketAddress getRemoteSocketAddress() throws Exception {
+ return (InetSocketAddress) channel.socket().getRemoteSocketAddress();
}
}
diff --git a/transport/src/main/java/io/netty/channel/socket/nio/NioWorker.java b/transport/src/main/java/io/netty/channel/socket/nio/NioWorker.java
index 35f07a512d..f9fb541cdd 100644
--- a/transport/src/main/java/io/netty/channel/socket/nio/NioWorker.java
+++ b/transport/src/main/java/io/netty/channel/socket/nio/NioWorker.java
@@ -15,286 +15,36 @@
*/
package io.netty.channel.socket.nio;
-import static io.netty.channel.Channels.*;
+import static io.netty.channel.Channels.fireChannelBound;
+import static io.netty.channel.Channels.fireChannelConnected;
+import static io.netty.channel.Channels.fireExceptionCaught;
+import static io.netty.channel.Channels.fireMessageReceived;
+import static io.netty.channel.Channels.succeededFuture;
+import io.netty.buffer.ChannelBuffer;
+import io.netty.buffer.ChannelBufferFactory;
+import io.netty.channel.ChannelException;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ReceiveBufferSizePredictor;
import java.io.IOException;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
-import java.nio.channels.AsynchronousCloseException;
-import java.nio.channels.CancelledKeyException;
import java.nio.channels.ClosedChannelException;
-import java.nio.channels.NotYetConnectedException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
-import java.util.Iterator;
-import java.util.Queue;
-import java.util.Set;
import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-import io.netty.buffer.ChannelBuffer;
-import io.netty.buffer.ChannelBufferFactory;
-import io.netty.channel.Channel;
-import io.netty.channel.ChannelException;
-import io.netty.channel.ChannelFuture;
-import io.netty.channel.MessageEvent;
-import io.netty.channel.ReceiveBufferSizePredictor;
-import io.netty.channel.socket.nio.SocketSendBufferPool.SendBuffer;
-import io.netty.logging.InternalLogger;
-import io.netty.logging.InternalLoggerFactory;
-import io.netty.util.internal.DeadLockProofWorker;
-import io.netty.util.internal.QueueFactory;
-
-class NioWorker implements Runnable {
-
- private static final InternalLogger logger =
- InternalLoggerFactory.getInstance(NioWorker.class);
-
- private static final int CONSTRAINT_LEVEL = NioProviderMetadata.CONSTRAINT_LEVEL;
-
- static final int CLEANUP_INTERVAL = 256; // XXX Hard-coded value, but won't need customization.
-
- private final Executor executor;
- private boolean started;
- private volatile Thread thread;
- volatile Selector selector;
- private final AtomicBoolean wakenUp = new AtomicBoolean();
- private final ReadWriteLock selectorGuard = new ReentrantReadWriteLock();
- private final Object startStopLock = new Object();
- private final Queue registerTaskQueue = QueueFactory.createQueue(Runnable.class);
- private final Queue writeTaskQueue = QueueFactory.createQueue(Runnable.class);
- private volatile int cancelledKeys; // should use AtomicInteger but we just need approximation
+class NioWorker extends AbstractNioWorker {
private final SocketReceiveBufferPool recvBufferPool = new SocketReceiveBufferPool();
- private final SocketSendBufferPool sendBufferPool = new SocketSendBufferPool();
NioWorker(Executor executor) {
- this.executor = executor;
- }
-
- void register(NioSocketChannel channel, ChannelFuture future) {
-
- boolean server = !(channel instanceof NioClientSocketChannel);
- Runnable registerTask = new RegisterTask(channel, future, server);
- Selector selector;
-
- synchronized (startStopLock) {
- if (!started) {
- // Open a selector if this worker didn't start yet.
- try {
- this.selector = selector = Selector.open();
- } catch (Throwable t) {
- throw new ChannelException(
- "Failed to create a selector.", t);
- }
-
- // Start the worker thread with the new Selector.
- boolean success = false;
- try {
- DeadLockProofWorker.start(executor, this);
- success = true;
- } finally {
- if (!success) {
- // Release the Selector if the execution fails.
- try {
- selector.close();
- } catch (Throwable t) {
- if (logger.isWarnEnabled()) {
- logger.warn("Failed to close a selector.", t);
- }
- }
- this.selector = selector = null;
- // The method will return to the caller at this point.
- }
- }
- } else {
- // Use the existing selector if this worker has been started.
- selector = this.selector;
- }
-
- assert selector != null && selector.isOpen();
-
- started = true;
- boolean offered = registerTaskQueue.offer(registerTask);
- assert offered;
- }
-
- if (wakenUp.compareAndSet(false, true)) {
- selector.wakeup();
- }
+ super(executor);
}
@Override
- public void run() {
- thread = Thread.currentThread();
-
- boolean shutdown = false;
- Selector selector = this.selector;
- for (;;) {
- wakenUp.set(false);
-
- if (CONSTRAINT_LEVEL != 0) {
- selectorGuard.writeLock().lock();
- // This empty synchronization block prevents the selector
- // from acquiring its lock.
- selectorGuard.writeLock().unlock();
- }
-
- try {
- SelectorUtil.select(selector);
-
- // 'wakenUp.compareAndSet(false, true)' is always evaluated
- // before calling 'selector.wakeup()' to reduce the wake-up
- // overhead. (Selector.wakeup() is an expensive operation.)
- //
- // However, there is a race condition in this approach.
- // The race condition is triggered when 'wakenUp' is set to
- // true too early.
- //
- // 'wakenUp' is set to true too early if:
- // 1) Selector is waken up between 'wakenUp.set(false)' and
- // 'selector.select(...)'. (BAD)
- // 2) Selector is waken up between 'selector.select(...)' and
- // 'if (wakenUp.get()) { ... }'. (OK)
- //
- // In the first case, 'wakenUp' is set to true and the
- // following 'selector.select(...)' will wake up immediately.
- // Until 'wakenUp' is set to false again in the next round,
- // 'wakenUp.compareAndSet(false, true)' will fail, and therefore
- // any attempt to wake up the Selector will fail, too, causing
- // the following 'selector.select(...)' call to block
- // unnecessarily.
- //
- // To fix this problem, we wake up the selector again if wakenUp
- // is true immediately after selector.select(...).
- // It is inefficient in that it wakes up the selector for both
- // the first case (BAD - wake-up required) and the second case
- // (OK - no wake-up required).
-
- if (wakenUp.get()) {
- selector.wakeup();
- }
-
- cancelledKeys = 0;
- processRegisterTaskQueue();
- processWriteTaskQueue();
- processSelectedKeys(selector.selectedKeys());
-
- // Exit the loop when there's nothing to handle.
- // The shutdown flag is used to delay the shutdown of this
- // loop to avoid excessive Selector creation when
- // connections are registered in a one-by-one manner instead of
- // concurrent manner.
- if (selector.keys().isEmpty()) {
- if (shutdown ||
- executor instanceof ExecutorService && ((ExecutorService) executor).isShutdown()) {
-
- synchronized (startStopLock) {
- if (registerTaskQueue.isEmpty() && selector.keys().isEmpty()) {
- started = false;
- try {
- selector.close();
- } catch (IOException e) {
- if (logger.isWarnEnabled()) {
- logger.warn(
- "Failed to close a selector.", e);
- }
- } finally {
- this.selector = null;
- }
- break;
- } else {
- shutdown = false;
- }
- }
- } else {
- // Give one more second.
- shutdown = true;
- }
- } else {
- shutdown = false;
- }
- } catch (Throwable t) {
- if (logger.isWarnEnabled()) {
- logger.warn(
- "Unexpected exception in the selector loop.", t);
- }
-
-
- // Prevent possible consecutive immediate failures that lead to
- // excessive CPU consumption.
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- // Ignore.
- }
- }
- }
- }
-
- private void processRegisterTaskQueue() throws IOException {
- for (;;) {
- final Runnable task = registerTaskQueue.poll();
- if (task == null) {
- break;
- }
-
- task.run();
- cleanUpCancelledKeys();
- }
- }
-
- private void processWriteTaskQueue() throws IOException {
- for (;;) {
- final Runnable task = writeTaskQueue.poll();
- if (task == null) {
- break;
- }
-
- task.run();
- cleanUpCancelledKeys();
- }
- }
-
- private void processSelectedKeys(Set selectedKeys) throws IOException {
- for (Iterator i = selectedKeys.iterator(); i.hasNext();) {
- SelectionKey k = i.next();
- i.remove();
- try {
- int readyOps = k.readyOps();
- if ((readyOps & SelectionKey.OP_READ) != 0 || readyOps == 0) {
- if (!read(k)) {
- // Connection already closed - no need to handle write.
- continue;
- }
- }
- if ((readyOps & SelectionKey.OP_WRITE) != 0) {
- writeFromSelectorLoop(k);
- }
- } catch (CancelledKeyException e) {
- close(k);
- }
-
- if (cleanUpCancelledKeys()) {
- break; // break the loop to avoid ConcurrentModificationException
- }
- }
- }
-
- private boolean cleanUpCancelledKeys() throws IOException {
- if (cancelledKeys >= CLEANUP_INTERVAL) {
- cancelledKeys = 0;
- selector.selectNow();
- return true;
- }
- return false;
- }
-
- private boolean read(SelectionKey k) {
+ protected boolean read(SelectionKey k) {
final SocketChannel ch = (SocketChannel) k.channel();
final NioSocketChannel channel = (NioSocketChannel) k.attachment();
@@ -350,47 +100,9 @@ class NioWorker implements Runnable {
return true;
}
- private void close(SelectionKey k) {
- NioSocketChannel ch = (NioSocketChannel) k.attachment();
- close(ch, succeededFuture(ch));
- }
- void writeFromUserCode(final NioSocketChannel channel) {
- if (!channel.isConnected()) {
- cleanUpWriteBuffer(channel);
- return;
- }
-
- if (scheduleWriteIfNecessary(channel)) {
- return;
- }
-
- // From here, we are sure Thread.currentThread() == workerThread.
-
- if (channel.writeSuspended) {
- return;
- }
-
- if (channel.inWriteNowLoop) {
- return;
- }
-
- write0(channel);
- }
-
- void writeFromTaskLoop(final NioSocketChannel ch) {
- if (!ch.writeSuspended) {
- write0(ch);
- }
- }
-
- void writeFromSelectorLoop(final SelectionKey k) {
- NioSocketChannel ch = (NioSocketChannel) k.attachment();
- ch.writeSuspended = false;
- write0(ch);
- }
-
- private boolean scheduleWriteIfNecessary(final NioSocketChannel channel) {
+ @Override
+ protected boolean scheduleWriteIfNecessary(final AbstractNioChannel> channel) {
final Thread currentThread = Thread.currentThread();
final Thread workerThread = thread;
if (currentThread != workerThread) {
@@ -424,310 +136,13 @@ class NioWorker implements Runnable {
return false;
}
-
- private void write0(NioSocketChannel channel) {
- boolean open = true;
- boolean addOpWrite = false;
- boolean removeOpWrite = false;
-
- long writtenBytes = 0;
-
- final SocketSendBufferPool sendBufferPool = this.sendBufferPool;
- final SocketChannel ch = channel.socket;
- final Queue writeBuffer = channel.writeBuffer;
- final int writeSpinCount = channel.getConfig().getWriteSpinCount();
- synchronized (channel.writeLock) {
- channel.inWriteNowLoop = true;
- for (;;) {
- MessageEvent evt = channel.currentWriteEvent;
- SendBuffer buf;
- if (evt == null) {
- if ((channel.currentWriteEvent = evt = writeBuffer.poll()) == null) {
- removeOpWrite = true;
- channel.writeSuspended = false;
- break;
- }
-
- channel.currentWriteBuffer = buf = sendBufferPool.acquire(evt.getMessage());
- } else {
- buf = channel.currentWriteBuffer;
- }
-
- ChannelFuture future = evt.getFuture();
- try {
- long localWrittenBytes = 0;
- for (int i = writeSpinCount; i > 0; i --) {
- localWrittenBytes = buf.transferTo(ch);
- if (localWrittenBytes != 0) {
- writtenBytes += localWrittenBytes;
- break;
- }
- if (buf.finished()) {
- break;
- }
- }
-
- if (buf.finished()) {
- // Successful write - proceed to the next message.
- buf.release();
- channel.currentWriteEvent = null;
- channel.currentWriteBuffer = null;
- evt = null;
- buf = null;
- future.setSuccess();
- } else {
- // Not written fully - perhaps the kernel buffer is full.
- addOpWrite = true;
- channel.writeSuspended = true;
-
- if (localWrittenBytes > 0) {
- // Notify progress listeners if necessary.
- future.setProgress(
- localWrittenBytes,
- buf.writtenBytes(), buf.totalBytes());
- }
- break;
- }
- } catch (AsynchronousCloseException e) {
- // Doesn't need a user attention - ignore.
- } catch (Throwable t) {
- if (buf != null) {
- buf.release();
- }
- channel.currentWriteEvent = null;
- channel.currentWriteBuffer = null;
- buf = null;
- evt = null;
- future.setFailure(t);
- fireExceptionCaught(channel, t);
- if (t instanceof IOException) {
- open = false;
- close(channel, succeededFuture(channel));
- }
- }
- }
- channel.inWriteNowLoop = false;
-
- // Initially, the following block was executed after releasing
- // the writeLock, but there was a race condition, and it has to be
- // executed before releasing the writeLock:
- //
- // https://issues.jboss.org/browse/NETTY-410
- //
- if (open) {
- if (addOpWrite) {
- setOpWrite(channel);
- } else if (removeOpWrite) {
- clearOpWrite(channel);
- }
- }
- }
-
- fireWriteComplete(channel, writtenBytes);
+
+ @Override
+ protected Runnable createRegisterTask(AbstractNioChannel> channel, ChannelFuture future) {
+ boolean server = !(channel instanceof NioClientSocketChannel);
+ return new RegisterTask((NioSocketChannel) channel, future, server);
}
-
- private void setOpWrite(NioSocketChannel channel) {
- Selector selector = this.selector;
- SelectionKey key = channel.socket.keyFor(selector);
- if (key == null) {
- return;
- }
- if (!key.isValid()) {
- close(key);
- return;
- }
-
- // interestOps can change at any time and at any thread.
- // Acquire a lock to avoid possible race condition.
- synchronized (channel.interestOpsLock) {
- int interestOps = channel.getRawInterestOps();
- if ((interestOps & SelectionKey.OP_WRITE) == 0) {
- interestOps |= SelectionKey.OP_WRITE;
- key.interestOps(interestOps);
- channel.setRawInterestOpsNow(interestOps);
- }
- }
- }
-
- private void clearOpWrite(NioSocketChannel channel) {
- Selector selector = this.selector;
- SelectionKey key = channel.socket.keyFor(selector);
- if (key == null) {
- return;
- }
- if (!key.isValid()) {
- close(key);
- return;
- }
-
- // interestOps can change at any time and at any thread.
- // Acquire a lock to avoid possible race condition.
- synchronized (channel.interestOpsLock) {
- int interestOps = channel.getRawInterestOps();
- if ((interestOps & SelectionKey.OP_WRITE) != 0) {
- interestOps &= ~SelectionKey.OP_WRITE;
- key.interestOps(interestOps);
- channel.setRawInterestOpsNow(interestOps);
- }
- }
- }
-
- void close(NioSocketChannel channel, ChannelFuture future) {
- boolean connected = channel.isConnected();
- boolean bound = channel.isBound();
- try {
- channel.socket.close();
- cancelledKeys ++;
-
- if (channel.setClosed()) {
- future.setSuccess();
- if (connected) {
- fireChannelDisconnected(channel);
- }
- if (bound) {
- fireChannelUnbound(channel);
- }
-
- cleanUpWriteBuffer(channel);
- fireChannelClosed(channel);
- } else {
- future.setSuccess();
- }
- } catch (Throwable t) {
- future.setFailure(t);
- fireExceptionCaught(channel, t);
- }
- }
-
- private void cleanUpWriteBuffer(NioSocketChannel channel) {
- Exception cause = null;
- boolean fireExceptionCaught = false;
-
- // Clean up the stale messages in the write buffer.
- synchronized (channel.writeLock) {
- MessageEvent evt = channel.currentWriteEvent;
- if (evt != null) {
- // Create the exception only once to avoid the excessive overhead
- // caused by fillStackTrace.
- if (channel.isOpen()) {
- cause = new NotYetConnectedException();
- } else {
- cause = new ClosedChannelException();
- }
-
- ChannelFuture future = evt.getFuture();
- channel.currentWriteBuffer.release();
- channel.currentWriteBuffer = null;
- channel.currentWriteEvent = null;
- evt = null;
- future.setFailure(cause);
- fireExceptionCaught = true;
- }
-
- Queue writeBuffer = channel.writeBuffer;
- if (!writeBuffer.isEmpty()) {
- // Create the exception only once to avoid the excessive overhead
- // caused by fillStackTrace.
- if (cause == null) {
- if (channel.isOpen()) {
- cause = new NotYetConnectedException();
- } else {
- cause = new ClosedChannelException();
- }
- }
-
- for (;;) {
- evt = writeBuffer.poll();
- if (evt == null) {
- break;
- }
- evt.getFuture().setFailure(cause);
- fireExceptionCaught = true;
- }
- }
- }
-
- if (fireExceptionCaught) {
- fireExceptionCaught(channel, cause);
- }
- }
-
- void setInterestOps(
- NioSocketChannel channel, ChannelFuture future, int interestOps) {
- boolean changed = false;
- try {
- // interestOps can change at any time and at any thread.
- // Acquire a lock to avoid possible race condition.
- synchronized (channel.interestOpsLock) {
- Selector selector = this.selector;
- SelectionKey key = channel.socket.keyFor(selector);
-
- if (key == null || selector == null) {
- // Not registered to the worker yet.
- // Set the rawInterestOps immediately; RegisterTask will pick it up.
- channel.setRawInterestOpsNow(interestOps);
- return;
- }
-
- // Override OP_WRITE flag - a user cannot change this flag.
- interestOps &= ~Channel.OP_WRITE;
- interestOps |= channel.getRawInterestOps() & Channel.OP_WRITE;
-
- switch (CONSTRAINT_LEVEL) {
- case 0:
- if (channel.getRawInterestOps() != interestOps) {
- key.interestOps(interestOps);
- if (Thread.currentThread() != thread &&
- wakenUp.compareAndSet(false, true)) {
- selector.wakeup();
- }
- changed = true;
- }
- break;
- case 1:
- case 2:
- if (channel.getRawInterestOps() != interestOps) {
- if (Thread.currentThread() == thread) {
- key.interestOps(interestOps);
- changed = true;
- } else {
- selectorGuard.readLock().lock();
- try {
- if (wakenUp.compareAndSet(false, true)) {
- selector.wakeup();
- }
- key.interestOps(interestOps);
- changed = true;
- } finally {
- selectorGuard.readLock().unlock();
- }
- }
- }
- break;
- default:
- throw new Error();
- }
-
- if (changed) {
- channel.setRawInterestOpsNow(interestOps);
- }
- }
-
- future.setSuccess();
- if (changed) {
- fireChannelInterestChanged(channel);
- }
- } catch (CancelledKeyException e) {
- // setInterestOps() was called on a closed channel.
- ClosedChannelException cce = new ClosedChannelException();
- future.setFailure(cce);
- fireExceptionCaught(channel, cce);
- } catch (Throwable t) {
- future.setFailure(t);
- fireExceptionCaught(channel, t);
- }
- }
-
+
private final class RegisterTask implements Runnable {
private final NioSocketChannel channel;
private final ChannelFuture future;
@@ -745,6 +160,7 @@ class NioWorker implements Runnable {
public void run() {
SocketAddress localAddress = channel.getLocalAddress();
SocketAddress remoteAddress = channel.getRemoteAddress();
+
if (localAddress == null || remoteAddress == null) {
if (future != null) {
future.setFailure(new ClosedChannelException());
@@ -755,11 +171,11 @@ class NioWorker implements Runnable {
try {
if (server) {
- channel.socket.configureBlocking(false);
+ channel.channel.configureBlocking(false);
}
synchronized (channel.interestOpsLock) {
- channel.socket.register(
+ channel.channel.register(
selector, channel.getRawInterestOps(), channel);
}
if (future != null) {
@@ -783,4 +199,5 @@ class NioWorker implements Runnable {
fireChannelConnected(channel, remoteAddress);
}
}
+
}
diff --git a/transport/src/main/java/io/netty/channel/socket/nio/SelectorUtil.java b/transport/src/main/java/io/netty/channel/socket/nio/SelectorUtil.java
index 51c8bbaa8c..511f5dcf26 100644
--- a/transport/src/main/java/io/netty/channel/socket/nio/SelectorUtil.java
+++ b/transport/src/main/java/io/netty/channel/socket/nio/SelectorUtil.java
@@ -28,9 +28,28 @@ final class SelectorUtil {
static final int DEFAULT_IO_THREADS = Runtime.getRuntime().availableProcessors() * 2;
+ // Workaround for JDK NIO bug.
+ //
+ // See:
+ // - http://bugs.sun.com/view_bug.do?bug_id=6427854
+ // - https://github.com/netty/netty/issues/203
+ static {
+ String key = "sun.nio.ch.bugLevel";
+ try {
+ String buglevel = System.getProperty(key);
+ if (buglevel == null) {
+ System.setProperty(key, "");
+ }
+ } catch (SecurityException e) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Unable to get/set System Property '" + key + "'", e);
+ }
+ }
+ }
+
static void select(Selector selector) throws IOException {
try {
- selector.select(500);
+ selector.select(10);
} catch (CancelledKeyException e) {
if (logger.isDebugEnabled()) {
logger.debug(
diff --git a/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioChannel.java b/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioChannel.java
new file mode 100644
index 0000000000..2c7009050a
--- /dev/null
+++ b/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioChannel.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2011 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.channel.socket.oio;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+
+import io.netty.channel.AbstractChannel;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFactory;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.ChannelSink;
+import io.netty.channel.socket.Worker;
+
+abstract class AbstractOioChannel extends AbstractChannel {
+ private volatile InetSocketAddress localAddress;
+ volatile InetSocketAddress remoteAddress;
+ volatile Thread workerThread;
+ volatile Worker worker;
+
+ final Object interestOpsLock = new Object();
+
+ AbstractOioChannel(
+ Channel parent,
+ ChannelFactory factory,
+ ChannelPipeline pipeline,
+ ChannelSink sink) {
+ super(parent, factory, pipeline, sink);
+ }
+
+ @Override
+ protected boolean setClosed() {
+ return super.setClosed();
+ }
+
+ @Override
+ protected void setInterestOpsNow(int interestOps) {
+ super.setInterestOpsNow(interestOps);
+ }
+
+ @Override
+ public ChannelFuture write(Object message, SocketAddress remoteAddress) {
+ if (remoteAddress == null || remoteAddress.equals(getRemoteAddress())) {
+ return super.write(message, null);
+ } else {
+ return super.write(message, remoteAddress);
+ }
+ }
+
+ @Override
+ public boolean isBound() {
+ return isOpen() && isSocketBound();
+ }
+
+ @Override
+ public boolean isConnected() {
+ return isOpen() && isSocketConnected();
+ }
+
+
+ @Override
+ public InetSocketAddress getLocalAddress() {
+ InetSocketAddress localAddress = this.localAddress;
+ if (localAddress == null) {
+ try {
+ this.localAddress = localAddress =
+ (InetSocketAddress) getLocalSocketAddress();
+ } catch (Throwable t) {
+ // Sometimes fails on a closed socket in Windows.
+ return null;
+ }
+ }
+ return localAddress;
+ }
+
+ @Override
+ public InetSocketAddress getRemoteAddress() {
+ InetSocketAddress remoteAddress = this.remoteAddress;
+ if (remoteAddress == null) {
+ try {
+ this.remoteAddress = remoteAddress =
+ (InetSocketAddress) getRemoteSocketAddress();
+ } catch (Throwable t) {
+ // Sometimes fails on a closed socket in Windows.
+ return null;
+ }
+ }
+ return remoteAddress;
+ }
+
+ abstract boolean isSocketBound();
+
+ abstract boolean isSocketConnected();
+
+ abstract boolean isSocketClosed();
+
+ abstract InetSocketAddress getLocalSocketAddress() throws Exception;
+
+ abstract InetSocketAddress getRemoteSocketAddress() throws Exception;
+
+ abstract void closeSocket() throws IOException;
+
+}
diff --git a/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioChannelSink.java b/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioChannelSink.java
new file mode 100644
index 0000000000..d57c198534
--- /dev/null
+++ b/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioChannelSink.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2011 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package io.netty.channel.socket.oio;
+
+import io.netty.channel.AbstractChannelSink;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelEvent;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.socket.Worker;
+
+public abstract class AbstractOioChannelSink extends AbstractChannelSink {
+
+ @Override
+ public ChannelFuture execute(final ChannelPipeline pipeline, final Runnable task) {
+ Channel ch = pipeline.getChannel();
+ if (ch instanceof AbstractOioChannel) {
+ AbstractOioChannel channel = (AbstractOioChannel) ch;
+ Worker worker = channel.worker;
+ if (worker != null) {
+ return channel.worker.executeInIoThread(ch, task);
+ }
+ }
+
+ return super.execute(pipeline, task);
+
+
+ }
+
+ @Override
+ protected boolean isFireExceptionCaughtLater(ChannelEvent event, Throwable actualCause) {
+ Channel channel = event.getChannel();
+ boolean fireLater = false;
+ if (channel instanceof AbstractOioChannel) {
+ fireLater = !AbstractOioWorker.isIoThread((AbstractOioChannel) channel);
+ }
+ return fireLater;
+ }
+
+}
diff --git a/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioWorker.java b/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioWorker.java
new file mode 100644
index 0000000000..930abbce59
--- /dev/null
+++ b/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioWorker.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2011 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.channel.socket.oio;
+
+import static io.netty.channel.Channels.*;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.Channels;
+import io.netty.channel.socket.ChannelRunnableWrapper;
+import io.netty.channel.socket.Worker;
+import io.netty.util.internal.QueueFactory;
+
+import java.io.IOException;
+import java.util.Queue;
+import java.util.concurrent.RejectedExecutionException;
+
+/**
+ * Abstract base class for Oio-Worker implementations
+ *
+ * @param {@link AbstractOioChannel}
+ */
+abstract class AbstractOioWorker implements Worker {
+
+ private final Queue eventQueue = QueueFactory.createQueue(ChannelRunnableWrapper.class);
+
+ protected final C channel;
+
+ public AbstractOioWorker(C channel) {
+ this.channel = channel;
+ channel.worker = this;
+ }
+
+ @Override
+ public void run() {
+ channel.workerThread = Thread.currentThread();
+
+ while (channel.isOpen()) {
+ synchronized (channel.interestOpsLock) {
+ while (!channel.isReadable()) {
+ try {
+ // notify() is not called at all.
+ // close() and setInterestOps() calls Thread.interrupt()
+ channel.interestOpsLock.wait();
+ } catch (InterruptedException e) {
+ if (!channel.isOpen()) {
+ break;
+ }
+ }
+ }
+ }
+
+ try {
+ boolean cont = process();
+
+ processEventQueue();
+
+ if (!cont) {
+ break;
+ }
+ } catch (Throwable t) {
+ if (!channel.isSocketClosed()) {
+ fireExceptionCaught(channel, t);
+ }
+ break;
+ }
+ }
+
+ // Setting the workerThread to null will prevent any channel
+ // operations from interrupting this thread from now on.
+ channel.workerThread = null;
+
+ // Clean up.
+ close(channel, succeededFuture(channel), true);
+ }
+
+ static boolean isIoThread(AbstractOioChannel channel) {
+ return Thread.currentThread() == channel.workerThread;
+ }
+
+ @Override
+ public ChannelFuture executeInIoThread(Channel channel, Runnable task) {
+ if (channel instanceof AbstractOioChannel && isIoThread((AbstractOioChannel) channel)) {
+ try {
+ task.run();
+ return succeededFuture(channel);
+ } catch (Throwable t) {
+ return failedFuture(channel, t);
+ }
+ } else {
+ ChannelRunnableWrapper channelRunnable = new ChannelRunnableWrapper(channel, task);
+ boolean added = eventQueue.offer(channelRunnable);
+
+ if (added) {
+ // as we set the SO_TIMEOUT to 1 second this task will get picked up in 1 second at latest
+
+ } else {
+ channelRunnable.setFailure(new RejectedExecutionException("Unable to queue task " + task));
+ }
+ return channelRunnable;
+ }
+ }
+
+ private void processEventQueue() throws IOException {
+ for (;;) {
+ final ChannelRunnableWrapper task = eventQueue.poll();
+ if (task == null) {
+ break;
+ }
+ task.run();
+ }
+ }
+
+
+ /**
+ * Process the incoming messages and also is responsible for call {@link Channels#fireMessageReceived(Channel, Object)} once a message
+ * was processed without errors.
+ *
+ * @return continue returns true
as long as this worker should continue to try processing incoming messages
+ * @throws IOException
+ */
+ abstract boolean process() throws IOException;
+
+ static void setInterestOps(
+ AbstractOioChannel channel, ChannelFuture future, int interestOps) {
+ boolean iothread = isIoThread(channel);
+
+ // Override OP_WRITE flag - a user cannot change this flag.
+ interestOps &= ~Channel.OP_WRITE;
+ interestOps |= channel.getInterestOps() & Channel.OP_WRITE;
+
+ boolean changed = false;
+ try {
+ if (channel.getInterestOps() != interestOps) {
+ if ((interestOps & Channel.OP_READ) != 0) {
+ channel.setInterestOpsNow(Channel.OP_READ);
+ } else {
+ channel.setInterestOpsNow(Channel.OP_NONE);
+ }
+ changed = true;
+ }
+
+ future.setSuccess();
+ if (changed) {
+ synchronized (channel.interestOpsLock) {
+ channel.setInterestOpsNow(interestOps);
+
+ // Notify the worker so it stops or continues reading.
+ Thread currentThread = Thread.currentThread();
+ Thread workerThread = channel.workerThread;
+ if (workerThread != null && currentThread != workerThread) {
+ workerThread.interrupt();
+ }
+ }
+ if (iothread) {
+ fireChannelInterestChanged(channel);
+ } else {
+ fireChannelInterestChangedLater(channel);
+ }
+ }
+ } catch (Throwable t) {
+ future.setFailure(t);
+ if (iothread) {
+ fireExceptionCaught(channel, t);
+ } else {
+ fireExceptionCaughtLater(channel, t);
+ }
+ }
+ }
+
+ static void close(AbstractOioChannel channel, ChannelFuture future) {
+ close(channel, future, isIoThread(channel));
+ }
+
+ private static void close(AbstractOioChannel channel, ChannelFuture future, boolean iothread) {
+ boolean connected = channel.isConnected();
+ boolean bound = channel.isBound();
+
+ try {
+ channel.closeSocket();
+ if (channel.setClosed()) {
+ future.setSuccess();
+ if (connected) {
+ // Notify the worker so it stops reading.
+ Thread currentThread = Thread.currentThread();
+ Thread workerThread = channel.workerThread;
+ if (workerThread != null && currentThread != workerThread) {
+ workerThread.interrupt();
+ }
+ if (iothread) {
+ fireChannelDisconnected(channel);
+ } else {
+ fireChannelDisconnectedLater(channel);
+ }
+ }
+ if (bound) {
+ if (iothread) {
+ fireChannelUnbound(channel);
+ } else {
+ fireChannelUnboundLater(channel);
+ }
+ }
+ if (iothread) {
+ fireChannelClosed(channel);
+ } else {
+ fireChannelClosedLater(channel);
+ }
+ } else {
+ future.setSuccess();
+ }
+ } catch (Throwable t) {
+ future.setFailure(t);
+ if (iothread) {
+ fireExceptionCaught(channel, t);
+ } else {
+ fireExceptionCaughtLater(channel, t);
+ }
+ }
+ }
+}
diff --git a/transport/src/main/java/io/netty/channel/socket/oio/OioClientSocketPipelineSink.java b/transport/src/main/java/io/netty/channel/socket/oio/OioClientSocketPipelineSink.java
index e5cf415a02..e607d3282e 100644
--- a/transport/src/main/java/io/netty/channel/socket/oio/OioClientSocketPipelineSink.java
+++ b/transport/src/main/java/io/netty/channel/socket/oio/OioClientSocketPipelineSink.java
@@ -21,7 +21,6 @@ import java.io.PushbackInputStream;
import java.net.SocketAddress;
import java.util.concurrent.Executor;
-import io.netty.channel.AbstractChannelSink;
import io.netty.channel.ChannelEvent;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
@@ -31,7 +30,7 @@ import io.netty.channel.ChannelStateEvent;
import io.netty.channel.MessageEvent;
import io.netty.util.internal.DeadLockProofWorker;
-class OioClientSocketPipelineSink extends AbstractChannelSink {
+class OioClientSocketPipelineSink extends AbstractOioChannelSink {
private final Executor workerExecutor;
diff --git a/transport/src/main/java/io/netty/channel/socket/oio/OioDatagramChannel.java b/transport/src/main/java/io/netty/channel/socket/oio/OioDatagramChannel.java
index 841fd1b316..a73a24c503 100644
--- a/transport/src/main/java/io/netty/channel/socket/oio/OioDatagramChannel.java
+++ b/transport/src/main/java/io/netty/channel/socket/oio/OioDatagramChannel.java
@@ -22,28 +22,22 @@ import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MulticastSocket;
import java.net.NetworkInterface;
-import java.net.SocketAddress;
import java.net.SocketException;
-import io.netty.channel.AbstractChannel;
import io.netty.channel.ChannelException;
import io.netty.channel.ChannelFactory;
-import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelSink;
import io.netty.channel.socket.DatagramChannel;
import io.netty.channel.socket.DatagramChannelConfig;
import io.netty.channel.socket.DefaultDatagramChannelConfig;
-final class OioDatagramChannel extends AbstractChannel
+final class OioDatagramChannel extends AbstractOioChannel
implements DatagramChannel {
final MulticastSocket socket;
- final Object interestOpsLock = new Object();
private final DatagramChannelConfig config;
- volatile Thread workerThread;
- private volatile InetSocketAddress localAddress;
- volatile InetSocketAddress remoteAddress;
+
static OioDatagramChannel create(ChannelFactory factory,
ChannelPipeline pipeline, ChannelSink sink) {
@@ -81,65 +75,6 @@ final class OioDatagramChannel extends AbstractChannel
return config;
}
- @Override
- public InetSocketAddress getLocalAddress() {
- InetSocketAddress localAddress = this.localAddress;
- if (localAddress == null) {
- try {
- this.localAddress = localAddress =
- (InetSocketAddress) socket.getLocalSocketAddress();
- } catch (Throwable t) {
- // Sometimes fails on a closed socket in Windows.
- return null;
- }
- }
- return localAddress;
- }
-
- @Override
- public InetSocketAddress getRemoteAddress() {
- InetSocketAddress remoteAddress = this.remoteAddress;
- if (remoteAddress == null) {
- try {
- this.remoteAddress = remoteAddress =
- (InetSocketAddress) socket.getRemoteSocketAddress();
- } catch (Throwable t) {
- // Sometimes fails on a closed socket in Windows.
- return null;
- }
- }
- return remoteAddress;
- }
-
- @Override
- public boolean isBound() {
- return isOpen() && socket.isBound();
- }
-
- @Override
- public boolean isConnected() {
- return isOpen() && socket.isConnected();
- }
-
- @Override
- protected boolean setClosed() {
- return super.setClosed();
- }
-
- @Override
- protected void setInterestOpsNow(int interestOps) {
- super.setInterestOpsNow(interestOps);
- }
-
- @Override
- public ChannelFuture write(Object message, SocketAddress remoteAddress) {
- if (remoteAddress == null || remoteAddress.equals(getRemoteAddress())) {
- return super.write(message, null);
- } else {
- return super.write(message, remoteAddress);
- }
- }
-
@Override
public void joinGroup(InetAddress multicastAddress) {
ensureBound();
@@ -187,4 +122,36 @@ final class OioDatagramChannel extends AbstractChannel
throw new ChannelException(e);
}
}
+
+ @Override
+ boolean isSocketBound() {
+ return socket.isBound();
+ }
+
+ @Override
+ boolean isSocketConnected() {
+ return socket.isConnected();
+ }
+
+ @Override
+ InetSocketAddress getLocalSocketAddress() throws Exception {
+ return (InetSocketAddress) socket.getLocalSocketAddress();
+ }
+
+ @Override
+ InetSocketAddress getRemoteSocketAddress() throws Exception {
+ return (InetSocketAddress) socket.getRemoteSocketAddress();
+ }
+
+ @Override
+ void closeSocket() throws IOException {
+ socket.close();
+ }
+
+ @Override
+ boolean isSocketClosed() {
+ return socket.isClosed();
+ }
+
+
}
diff --git a/transport/src/main/java/io/netty/channel/socket/oio/OioDatagramPipelineSink.java b/transport/src/main/java/io/netty/channel/socket/oio/OioDatagramPipelineSink.java
index 3cf3e6baf6..2b198080b5 100644
--- a/transport/src/main/java/io/netty/channel/socket/oio/OioDatagramPipelineSink.java
+++ b/transport/src/main/java/io/netty/channel/socket/oio/OioDatagramPipelineSink.java
@@ -20,7 +20,6 @@ import static io.netty.channel.Channels.*;
import java.net.SocketAddress;
import java.util.concurrent.Executor;
-import io.netty.channel.AbstractChannelSink;
import io.netty.channel.ChannelEvent;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
@@ -30,7 +29,7 @@ import io.netty.channel.ChannelStateEvent;
import io.netty.channel.MessageEvent;
import io.netty.util.internal.DeadLockProofWorker;
-class OioDatagramPipelineSink extends AbstractChannelSink {
+class OioDatagramPipelineSink extends AbstractOioChannelSink {
private final Executor workerExecutor;
diff --git a/transport/src/main/java/io/netty/channel/socket/oio/OioDatagramWorker.java b/transport/src/main/java/io/netty/channel/socket/oio/OioDatagramWorker.java
index ab6ffbd277..2581425616 100644
--- a/transport/src/main/java/io/netty/channel/socket/oio/OioDatagramWorker.java
+++ b/transport/src/main/java/io/netty/channel/socket/oio/OioDatagramWorker.java
@@ -17,80 +17,54 @@ package io.netty.channel.socket.oio;
import static io.netty.channel.Channels.*;
+import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.DatagramPacket;
-import java.net.MulticastSocket;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import io.netty.buffer.ChannelBuffer;
-import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ReceiveBufferSizePredictor;
-class OioDatagramWorker implements Runnable {
-
- private final OioDatagramChannel channel;
+class OioDatagramWorker extends AbstractOioWorker {
OioDatagramWorker(OioDatagramChannel channel) {
- this.channel = channel;
+ super(channel);
}
+
+
@Override
- public void run() {
- channel.workerThread = Thread.currentThread();
- final MulticastSocket socket = channel.socket;
+ boolean process() throws IOException {
- while (channel.isOpen()) {
- synchronized (channel.interestOpsLock) {
- while (!channel.isReadable()) {
- try {
- // notify() is not called at all.
- // close() and setInterestOps() calls Thread.interrupt()
- channel.interestOpsLock.wait();
- } catch (InterruptedException e) {
- if (!channel.isOpen()) {
- break;
- }
- }
- }
- }
+ ReceiveBufferSizePredictor predictor =
+ channel.getConfig().getReceiveBufferSizePredictor();
- ReceiveBufferSizePredictor predictor =
- channel.getConfig().getReceiveBufferSizePredictor();
+ byte[] buf = new byte[predictor.nextReceiveBufferSize()];
+ DatagramPacket packet = new DatagramPacket(buf, buf.length);
+ try {
+ channel.socket.receive(packet);
+ } catch (InterruptedIOException e) {
+ // Can happen on interruption.
+ // Keep receiving unless the channel is closed.
+ return true;
+ }
- byte[] buf = new byte[predictor.nextReceiveBufferSize()];
- DatagramPacket packet = new DatagramPacket(buf, buf.length);
- try {
- socket.receive(packet);
- } catch (InterruptedIOException e) {
- // Can happen on interruption.
- // Keep receiving unless the channel is closed.
- continue;
- } catch (Throwable t) {
- if (!channel.socket.isClosed()) {
- fireExceptionCaught(channel, t);
- }
- break;
- }
-
- fireMessageReceived(
- channel,
- channel.getConfig().getBufferFactory().getBuffer(buf, 0, packet.getLength()),
- packet.getSocketAddress());
- }
-
- // Setting the workerThread to null will prevent any channel
- // operations from interrupting this thread from now on.
- channel.workerThread = null;
-
- // Clean up.
- close(channel, succeededFuture(channel));
+ fireMessageReceived(
+ channel,
+ channel.getConfig().getBufferFactory().getBuffer(buf, 0, packet.getLength()),
+ packet.getSocketAddress());
+ return true;
}
+
+
static void write(
OioDatagramChannel channel, ChannelFuture future,
Object message, SocketAddress remoteAddress) {
+ boolean iothread = isIoThread(channel);
+
try {
ChannelBuffer buf = (ChannelBuffer) message;
int offset = buf.readerIndex();
@@ -112,94 +86,46 @@ class OioDatagramWorker implements Runnable {
packet.setSocketAddress(remoteAddress);
}
channel.socket.send(packet);
- fireWriteComplete(channel, length);
+ if (iothread) {
+ fireWriteComplete(channel, length);
+ } else {
+ fireWriteCompleteLater(channel, length);
+ }
future.setSuccess();
} catch (Throwable t) {
future.setFailure(t);
- fireExceptionCaught(channel, t);
- }
- }
-
- static void setInterestOps(
- OioDatagramChannel channel, ChannelFuture future, int interestOps) {
-
- // Override OP_WRITE flag - a user cannot change this flag.
- interestOps &= ~Channel.OP_WRITE;
- interestOps |= channel.getInterestOps() & Channel.OP_WRITE;
-
- boolean changed = false;
- try {
- if (channel.getInterestOps() != interestOps) {
- if ((interestOps & Channel.OP_READ) != 0) {
- channel.setInterestOpsNow(Channel.OP_READ);
- } else {
- channel.setInterestOpsNow(Channel.OP_NONE);
- }
- changed = true;
- }
-
- future.setSuccess();
- if (changed) {
- synchronized (channel.interestOpsLock) {
- channel.setInterestOpsNow(interestOps);
-
- // Notify the worker so it stops or continues reading.
- Thread currentThread = Thread.currentThread();
- Thread workerThread = channel.workerThread;
- if (workerThread != null && currentThread != workerThread) {
- workerThread.interrupt();
- }
- }
-
- fireChannelInterestChanged(channel);
- }
- } catch (Throwable t) {
- future.setFailure(t);
- fireExceptionCaught(channel, t);
+ if (iothread) {
+ fireExceptionCaught(channel, t);
+ } else {
+ fireExceptionCaughtLater(channel, t);
+ }
}
}
+
static void disconnect(OioDatagramChannel channel, ChannelFuture future) {
boolean connected = channel.isConnected();
+ boolean iothread = isIoThread(channel);
+
try {
channel.socket.disconnect();
future.setSuccess();
if (connected) {
// Notify.
- fireChannelDisconnected(channel);
+ if (iothread) {
+ fireChannelDisconnected(channel);
+ } else {
+ fireChannelDisconnectedLater(channel);
+ }
}
} catch (Throwable t) {
future.setFailure(t);
- fireExceptionCaught(channel, t);
+ if (iothread) {
+ fireExceptionCaught(channel, t);
+ } else {
+ fireExceptionCaughtLater(channel, t);
+ }
}
}
- static void close(OioDatagramChannel channel, ChannelFuture future) {
- boolean connected = channel.isConnected();
- boolean bound = channel.isBound();
- try {
- channel.socket.close();
- if (channel.setClosed()) {
- future.setSuccess();
- if (connected) {
- // Notify the worker so it stops reading.
- Thread currentThread = Thread.currentThread();
- Thread workerThread = channel.workerThread;
- if (workerThread != null && currentThread != workerThread) {
- workerThread.interrupt();
- }
- fireChannelDisconnected(channel);
- }
- if (bound) {
- fireChannelUnbound(channel);
- }
- fireChannelClosed(channel);
- } else {
- future.setSuccess();
- }
- } catch (Throwable t) {
- future.setFailure(t);
- fireExceptionCaught(channel, t);
- }
- }
}
diff --git a/transport/src/main/java/io/netty/channel/socket/oio/OioServerSocketPipelineSink.java b/transport/src/main/java/io/netty/channel/socket/oio/OioServerSocketPipelineSink.java
index e8c4c3278f..5daad24afc 100644
--- a/transport/src/main/java/io/netty/channel/socket/oio/OioServerSocketPipelineSink.java
+++ b/transport/src/main/java/io/netty/channel/socket/oio/OioServerSocketPipelineSink.java
@@ -24,7 +24,6 @@ import java.net.SocketTimeoutException;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
-import io.netty.channel.AbstractChannelSink;
import io.netty.channel.Channel;
import io.netty.channel.ChannelEvent;
import io.netty.channel.ChannelFuture;
@@ -36,7 +35,7 @@ import io.netty.logging.InternalLogger;
import io.netty.logging.InternalLoggerFactory;
import io.netty.util.internal.DeadLockProofWorker;
-class OioServerSocketPipelineSink extends AbstractChannelSink {
+class OioServerSocketPipelineSink extends AbstractOioChannelSink {
static final InternalLogger logger =
InternalLoggerFactory.getInstance(OioServerSocketPipelineSink.class);
diff --git a/transport/src/main/java/io/netty/channel/socket/oio/OioSocketChannel.java b/transport/src/main/java/io/netty/channel/socket/oio/OioSocketChannel.java
index bd1ccdb588..2b3303e519 100644
--- a/transport/src/main/java/io/netty/channel/socket/oio/OioSocketChannel.java
+++ b/transport/src/main/java/io/netty/channel/socket/oio/OioSocketChannel.java
@@ -15,31 +15,25 @@
*/
package io.netty.channel.socket.oio;
+import java.io.IOException;
import java.io.OutputStream;
import java.io.PushbackInputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
-import java.net.SocketAddress;
-import io.netty.channel.AbstractChannel;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFactory;
-import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelSink;
import io.netty.channel.socket.DefaultSocketChannelConfig;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.SocketChannelConfig;
-abstract class OioSocketChannel extends AbstractChannel
+abstract class OioSocketChannel extends AbstractOioChannel
implements SocketChannel {
final Socket socket;
- final Object interestOpsLock = new Object();
private final SocketChannelConfig config;
- volatile Thread workerThread;
- private volatile InetSocketAddress localAddress;
- private volatile InetSocketAddress remoteAddress;
OioSocketChannel(
Channel parent,
@@ -59,65 +53,36 @@ abstract class OioSocketChannel extends AbstractChannel
return config;
}
- @Override
- public InetSocketAddress getLocalAddress() {
- InetSocketAddress localAddress = this.localAddress;
- if (localAddress == null) {
- try {
- this.localAddress = localAddress =
- (InetSocketAddress) socket.getLocalSocketAddress();
- } catch (Throwable t) {
- // Sometimes fails on a closed socket in Windows.
- return null;
- }
- }
- return localAddress;
- }
-
- @Override
- public InetSocketAddress getRemoteAddress() {
- InetSocketAddress remoteAddress = this.remoteAddress;
- if (remoteAddress == null) {
- try {
- this.remoteAddress = remoteAddress =
- (InetSocketAddress) socket.getRemoteSocketAddress();
- } catch (Throwable t) {
- // Sometimes fails on a closed socket in Windows.
- return null;
- }
- }
- return remoteAddress;
- }
-
- @Override
- public boolean isBound() {
- return isOpen() && socket.isBound();
- }
-
- @Override
- public boolean isConnected() {
- return isOpen() && socket.isConnected();
- }
-
- @Override
- protected boolean setClosed() {
- return super.setClosed();
- }
-
- @Override
- protected void setInterestOpsNow(int interestOps) {
- super.setInterestOpsNow(interestOps);
- }
-
abstract PushbackInputStream getInputStream();
abstract OutputStream getOutputStream();
@Override
- public ChannelFuture write(Object message, SocketAddress remoteAddress) {
- if (remoteAddress == null || remoteAddress.equals(getRemoteAddress())) {
- return super.write(message, null);
- } else {
- return getUnsupportedOperationFuture();
- }
+ boolean isSocketBound() {
+ return socket.isBound();
+ }
+
+ @Override
+ boolean isSocketConnected() {
+ return socket.isConnected();
+ }
+
+ @Override
+ InetSocketAddress getLocalSocketAddress() throws Exception {
+ return (InetSocketAddress) socket.getLocalSocketAddress();
+ }
+
+ @Override
+ InetSocketAddress getRemoteSocketAddress() throws Exception {
+ return (InetSocketAddress) socket.getRemoteSocketAddress();
+ }
+
+ @Override
+ void closeSocket() throws IOException {
+ socket.close();
+ }
+
+ @Override
+ boolean isSocketClosed() {
+ return socket.isClosed();
}
}
diff --git a/transport/src/main/java/io/netty/channel/socket/oio/OioWorker.java b/transport/src/main/java/io/netty/channel/socket/oio/OioWorker.java
index 9afe62ba62..5d4eb6aa3b 100644
--- a/transport/src/main/java/io/netty/channel/socket/oio/OioWorker.java
+++ b/transport/src/main/java/io/netty/channel/socket/oio/OioWorker.java
@@ -17,6 +17,7 @@ package io.netty.channel.socket.oio;
import static io.netty.channel.Channels.*;
+import java.io.IOException;
import java.io.OutputStream;
import java.io.PushbackInputStream;
import java.net.SocketException;
@@ -26,90 +27,54 @@ import java.nio.channels.WritableByteChannel;
import java.util.regex.Pattern;
import io.netty.buffer.ChannelBuffer;
-import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.FileRegion;
-class OioWorker implements Runnable {
+class OioWorker extends AbstractOioWorker {
private static final Pattern SOCKET_CLOSED_MESSAGE = Pattern.compile(
"^.*(?:Socket.*closed).*$", Pattern.CASE_INSENSITIVE);
- private final OioSocketChannel channel;
-
OioWorker(OioSocketChannel channel) {
- this.channel = channel;
+ super(channel);
}
@Override
- public void run() {
- channel.workerThread = Thread.currentThread();
- final PushbackInputStream in = channel.getInputStream();
- boolean fireOpen = channel instanceof OioAcceptedSocketChannel;
-
- while (channel.isOpen()) {
- if (fireOpen) {
- fireOpen = false;
- fireChannelConnected(channel, channel.getRemoteAddress());
+ boolean process() throws IOException {
+ byte[] buf;
+ int readBytes;
+ PushbackInputStream in = channel.getInputStream();
+ int bytesToRead = in.available();
+ if (bytesToRead > 0) {
+ buf = new byte[bytesToRead];
+ readBytes = in.read(buf);
+ } else {
+ int b = in.read();
+ if (b < 0) {
+ return false;
}
- synchronized (channel.interestOpsLock) {
- while (!channel.isReadable()) {
- try {
- // notify() is not called at all.
- // close() and setInterestOps() calls Thread.interrupt()
- channel.interestOpsLock.wait();
- } catch (InterruptedException e) {
- if (!channel.isOpen()) {
- break;
- }
- }
- }
- }
-
- byte[] buf;
- int readBytes;
- try {
- int bytesToRead = in.available();
- if (bytesToRead > 0) {
- buf = new byte[bytesToRead];
- readBytes = in.read(buf);
- } else {
- int b = in.read();
- if (b < 0) {
- break;
- }
- in.unread(b);
- continue;
- }
- } catch (Throwable t) {
- if (!channel.socket.isClosed()) {
- fireExceptionCaught(channel, t);
- }
- break;
- }
-
- fireMessageReceived(
- channel,
- channel.getConfig().getBufferFactory().getBuffer(buf, 0, readBytes));
+ in.unread(b);
+ return true;
}
-
- // Setting the workerThread to null will prevent any channel
- // operations from interrupting this thread from now on.
- channel.workerThread = null;
-
- // Clean up.
- close(channel, succeededFuture(channel));
+ fireMessageReceived(channel, channel.getConfig().getBufferFactory().getBuffer(buf, 0, readBytes));
+
+ return true;
}
static void write(
OioSocketChannel channel, ChannelFuture future,
Object message) {
+ boolean iothread = isIoThread(channel);
OutputStream out = channel.getOutputStream();
if (out == null) {
Exception e = new ClosedChannelException();
future.setFailure(e);
- fireExceptionCaught(channel, e);
+ if (iothread) {
+ fireExceptionCaught(channel, e);
+ } else {
+ fireExceptionCaughtLater(channel, e);
+ }
return;
}
@@ -146,7 +111,11 @@ class OioWorker implements Runnable {
}
}
- fireWriteComplete(channel, length);
+ if (iothread) {
+ fireWriteComplete(channel, length);
+ } else {
+ fireWriteCompleteLater(channel, length);
+ }
future.setSuccess();
} catch (Throwable t) {
@@ -158,75 +127,13 @@ class OioWorker implements Runnable {
t = new ClosedChannelException();
}
future.setFailure(t);
- fireExceptionCaught(channel, t);
- }
- }
-
- static void setInterestOps(
- OioSocketChannel channel, ChannelFuture future, int interestOps) {
-
- // Override OP_WRITE flag - a user cannot change this flag.
- interestOps &= ~Channel.OP_WRITE;
- interestOps |= channel.getInterestOps() & Channel.OP_WRITE;
-
- boolean changed = false;
- try {
- if (channel.getInterestOps() != interestOps) {
- if ((interestOps & Channel.OP_READ) != 0) {
- channel.setInterestOpsNow(Channel.OP_READ);
- } else {
- channel.setInterestOpsNow(Channel.OP_NONE);
- }
- changed = true;
- }
-
- future.setSuccess();
- if (changed) {
- synchronized (channel.interestOpsLock) {
- channel.setInterestOpsNow(interestOps);
-
- // Notify the worker so it stops or continues reading.
- Thread currentThread = Thread.currentThread();
- Thread workerThread = channel.workerThread;
- if (workerThread != null && currentThread != workerThread) {
- workerThread.interrupt();
- }
- }
-
- fireChannelInterestChanged(channel);
- }
- } catch (Throwable t) {
- future.setFailure(t);
- fireExceptionCaught(channel, t);
- }
- }
-
- static void close(OioSocketChannel channel, ChannelFuture future) {
- boolean connected = channel.isConnected();
- boolean bound = channel.isBound();
- try {
- channel.socket.close();
- if (channel.setClosed()) {
- future.setSuccess();
- if (connected) {
- // Notify the worker so it stops reading.
- Thread currentThread = Thread.currentThread();
- Thread workerThread = channel.workerThread;
- if (workerThread != null && currentThread != workerThread) {
- workerThread.interrupt();
- }
- fireChannelDisconnected(channel);
- }
- if (bound) {
- fireChannelUnbound(channel);
- }
- fireChannelClosed(channel);
+ if (iothread) {
+ fireExceptionCaught(channel, t);
} else {
- future.setSuccess();
+ fireExceptionCaughtLater(channel, t);
}
- } catch (Throwable t) {
- future.setFailure(t);
- fireExceptionCaught(channel, t);
}
}
+
+
}