From 65be9ebd44bb60b4921e11493f208407f64982e3 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Fri, 17 Feb 2012 20:33:18 +0100 Subject: [PATCH 01/66] Start to refactor oio transport to share more code. See #186 --- .../socket/oio/AbstractOioChannel.java | 115 +++++++++++++ .../channel/socket/oio/AbstractOioWorker.java | 158 ++++++++++++++++++ .../socket/oio/OioDatagramChannel.java | 101 ++++------- .../channel/socket/oio/OioDatagramWorker.java | 144 +++------------- .../channel/socket/oio/OioSocketChannel.java | 93 ++++------- .../netty/channel/socket/oio/OioWorker.java | 146 +++------------- 6 files changed, 381 insertions(+), 376 deletions(-) create mode 100644 transport/src/main/java/io/netty/channel/socket/oio/AbstractOioChannel.java create mode 100644 transport/src/main/java/io/netty/channel/socket/oio/AbstractOioWorker.java 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..1d7106b4c7 --- /dev/null +++ b/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioChannel.java @@ -0,0 +1,115 @@ +/* + * 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; + +abstract class AbstractOioChannel extends AbstractChannel{ + private volatile InetSocketAddress localAddress; + volatile InetSocketAddress remoteAddress; + volatile Thread workerThread; + 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/AbstractOioWorker.java b/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioWorker.java new file mode 100644 index 0000000000..898ef994c3 --- /dev/null +++ b/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioWorker.java @@ -0,0 +1,158 @@ +/* + * 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.fireChannelClosed; +import static io.netty.channel.Channels.fireChannelDisconnected; +import static io.netty.channel.Channels.fireChannelInterestChanged; +import static io.netty.channel.Channels.fireChannelUnbound; +import static io.netty.channel.Channels.fireExceptionCaught; +import static io.netty.channel.Channels.succeededFuture; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.Channels; + +import java.io.IOException; + +/** + * Abstract base class for Oio-Worker implementations + * + * @param {@link AbstractOioChannel} + */ +abstract class AbstractOioWorker implements Runnable { + + protected final C channel; + + public AbstractOioWorker(C channel) { + this.channel = channel; + } + + @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 { + if (!process()) { + 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)); + } + + /** + * 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) { + + // 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(AbstractOioChannel channel, ChannelFuture future) { + 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(); + } + 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/OioDatagramChannel.java b/transport/src/main/java/io/netty/channel/socket/oio/OioDatagramChannel.java index 841fd1b316..9f55095fa0 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/OioDatagramWorker.java b/transport/src/main/java/io/netty/channel/socket/oio/OioDatagramWorker.java index ab6ffbd277..f1b42b42f9 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,77 +17,49 @@ 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) { @@ -120,45 +92,7 @@ class OioDatagramWorker implements Runnable { } } - 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); - } - } - + static void disconnect(OioDatagramChannel channel, ChannelFuture future) { boolean connected = channel.isConnected(); try { @@ -174,32 +108,4 @@ class OioDatagramWorker implements Runnable { } } - 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/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..bb0f7148d9 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,79 +27,38 @@ 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( @@ -162,71 +122,5 @@ class OioWorker implements Runnable { } } - 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); - } else { - future.setSuccess(); - } - } catch (Throwable t) { - future.setFailure(t); - fireExceptionCaught(channel, t); - } - } } From 812a9026b88244ffb6f8c24ea827cc4454973f55 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Sat, 18 Feb 2012 23:02:56 +0100 Subject: [PATCH 02/66] Start to refactor nio transport to share more code. See #186 --- .../socket/nio/AbstractNioChannel.java | 394 ++++++++++ .../channel/socket/nio/AbstractNioWorker.java | 694 +++++++++++++++++ .../socket/nio/AbstractWriteRequestQueue.java | 149 ---- .../channel/socket/nio/NioChannelConfig.java | 2 +- .../nio/NioClientSocketPipelineSink.java | 10 +- .../socket/nio/NioDatagramChannel.java | 275 +------ .../channel/socket/nio/NioDatagramWorker.java | 705 +----------------- .../nio/NioServerSocketPipelineSink.java | 2 +- .../channel/socket/nio/NioSocketChannel.java | 192 +---- .../netty/channel/socket/nio/NioWorker.java | 624 +--------------- .../socket/oio/AbstractOioChannel.java | 2 +- .../socket/oio/OioDatagramChannel.java | 4 +- 12 files changed, 1181 insertions(+), 1872 deletions(-) create mode 100644 transport/src/main/java/io/netty/channel/socket/nio/AbstractNioChannel.java create mode 100644 transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java delete mode 100644 transport/src/main/java/io/netty/channel/socket/nio/AbstractWriteRequestQueue.java 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..501af6473d --- /dev/null +++ b/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioChannel.java @@ -0,0 +1,394 @@ +/* + * 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; + } + + 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 = ((NioChannelConfig) getConfig()).getWriteBufferLowWaterMark(); + if (writeBufferSize >= lowWaterMark) { + interestOps |= Channel.OP_WRITE; + } else { + interestOps &= ~Channel.OP_WRITE; + } + } else { + int highWaterMark = ((NioChannelConfig) 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 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 c) { + return queue.drainTo(c); + } + + @Override + public int drainTo(Collection 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 = ((NioChannelConfig) 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 = ((NioChannelConfig) 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/AbstractNioWorker.java b/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java new file mode 100644 index 0000000000..8faccf893a --- /dev/null +++ b/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java @@ -0,0 +1,694 @@ +/* + * 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.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.atomic.AtomicBoolean; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +abstract class AbstractNioWorker implements Runnable { + /** + * 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 + * {@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. + */ + 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 {@link ChannelRegistionTask}s + */ + private final Queue registerTaskQueue = QueueFactory.createQueue(Runnable.class); + + /** + * Queue of WriteTasks + */ + protected 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(); + + 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(); + 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. + } + } + } + } + + + 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 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; + + long writtenBytes = 0; + + final SocketSendBufferPool sendBufferPool = this.sendBufferPool; + final WritableByteChannel ch = channel.channel; + final Queue writeBuffer = channel.writeBufferQueue; + final int writeSpinCount = ((NioChannelConfig) 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); + } + + 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(); + try { + channel.channel.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(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) { + fireExceptionCaught(channel, cause); + } + } + + void setInterestOps(AbstractNioChannel 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.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) { + 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); + } + } + + /** + * 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. + */ + 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 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 c) { - return queue.drainTo(c); - } - - @Override - public int drainTo(Collection c, int maxElements) { - return queue.drainTo(c, maxElements); - } - -} diff --git a/transport/src/main/java/io/netty/channel/socket/nio/NioChannelConfig.java b/transport/src/main/java/io/netty/channel/socket/nio/NioChannelConfig.java index 65dd184993..251d38ddab 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/NioChannelConfig.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/NioChannelConfig.java @@ -25,7 +25,7 @@ import io.netty.channel.ChannelConfig; * Special {@link ChannelConfig} sub-type which offers extra methods which are useful for NIO. * */ -public interface NioChannelConfig extends ChannelConfig{ +public interface NioChannelConfig extends ChannelConfig { /** * Returns the high water mark of the write buffer. If the number of bytes 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 0b269857e7..14226a5ee6 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 @@ -113,7 +113,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 +123,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 +138,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() { @@ -392,7 +392,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); } @@ -422,7 +422,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/NioDatagramWorker.java b/transport/src/main/java/io/netty/channel/socket/nio/NioDatagramWorker.java index eff53e3433..54ed300b96 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,28 @@ */ 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.fireExceptionCaught; +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,246 +45,13 @@ 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) { - 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(); @@ -408,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)) { @@ -475,155 +125,6 @@ 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(); @@ -639,171 +140,12 @@ class NioDatagramWorker implements Runnable { } } - 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) { - fireChannelDisconnected(channel); - } - if (bound) { - fireChannelUnbound(channel); - } - - cleanUpWriteBuffer(channel); - fireChannelClosed(channel); - } else { - future.setSuccess(); - } - } catch (Throwable t) { - future.setFailure(t); - fireExceptionCaught(channel, t); - } + @Override + protected Runnable createRegisterTask(AbstractNioChannel channel, ChannelFuture future) { + return new ChannelRegistionTask((NioDatagramChannel) channel, future); } - - 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); - } - } - - 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. @@ -852,4 +194,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 7663886d52..9410102d2c 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 @@ -122,7 +122,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 942d0d133d..e22e614da4 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,279 +15,38 @@ */ 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; + super(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) { - 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(); - 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. - } - } - } - } - - 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(); @@ -343,47 +102,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) { @@ -417,310 +138,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; @@ -738,6 +162,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()); @@ -748,11 +173,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) { @@ -776,4 +201,5 @@ class NioWorker implements Runnable { fireChannelConnected(channel, remoteAddress); } } + } 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 index 1d7106b4c7..7447254cde 100644 --- a/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioChannel.java +++ b/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioChannel.java @@ -26,7 +26,7 @@ import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelSink; -abstract class AbstractOioChannel extends AbstractChannel{ +abstract class AbstractOioChannel extends AbstractChannel { private volatile InetSocketAddress localAddress; volatile InetSocketAddress remoteAddress; volatile Thread workerThread; 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 9f55095fa0..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 @@ -134,12 +134,12 @@ final class OioDatagramChannel extends AbstractOioChannel } @Override - InetSocketAddress getLocalSocketAddress() throws Exception{ + InetSocketAddress getLocalSocketAddress() throws Exception { return (InetSocketAddress) socket.getLocalSocketAddress(); } @Override - InetSocketAddress getRemoteSocketAddress() throws Exception{ + InetSocketAddress getRemoteSocketAddress() throws Exception { return (InetSocketAddress) socket.getRemoteSocketAddress(); } From 5557137dc033ea16e7b833d7d074e38c7fb400d8 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Sat, 18 Feb 2012 23:03:13 +0100 Subject: [PATCH 03/66] Fix checkstyle --- .../java/io/netty/handler/codec/http/QueryStringDecoder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/QueryStringDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/QueryStringDecoder.java index 62f210a112..b01228ce3d 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/QueryStringDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/QueryStringDecoder.java @@ -159,7 +159,7 @@ public class QueryStringDecoder { if (rawPath != null) { hasPath = true; } else { - rawPath =""; + rawPath = ""; hasPath = false; } // Also take care of cut of things like "http://localhost" From 6d8daabefe0b3ac874e3e50c546e9ac5deadef2a Mon Sep 17 00:00:00 2001 From: James Abley Date: Sat, 18 Feb 2012 23:19:45 +0000 Subject: [PATCH 04/66] Fix checkstyle warning --- .../java/io/netty/handler/codec/http/QueryStringDecoder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/QueryStringDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/QueryStringDecoder.java index 62f210a112..b01228ce3d 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/QueryStringDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/QueryStringDecoder.java @@ -159,7 +159,7 @@ public class QueryStringDecoder { if (rawPath != null) { hasPath = true; } else { - rawPath =""; + rawPath = ""; hasPath = false; } // Also take care of cut of things like "http://localhost" From f3c1e143c1d1303392e613a3a89a264e53fd8da9 Mon Sep 17 00:00:00 2001 From: James Abley Date: Sat, 18 Feb 2012 23:22:52 +0000 Subject: [PATCH 05/66] Fix checkstyle warning --- .../main/java/io/netty/channel/socket/nio/NioChannelConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transport/src/main/java/io/netty/channel/socket/nio/NioChannelConfig.java b/transport/src/main/java/io/netty/channel/socket/nio/NioChannelConfig.java index 65dd184993..251d38ddab 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/NioChannelConfig.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/NioChannelConfig.java @@ -25,7 +25,7 @@ import io.netty.channel.ChannelConfig; * Special {@link ChannelConfig} sub-type which offers extra methods which are useful for NIO. * */ -public interface NioChannelConfig extends ChannelConfig{ +public interface NioChannelConfig extends ChannelConfig { /** * Returns the high water mark of the write buffer. If the number of bytes From eafd8343ebc66c4d71b678d456b080449b5a42d9 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Sun, 19 Feb 2012 13:59:09 +0100 Subject: [PATCH 06/66] Remove some casting. See #186 --- .../netty/channel/socket/nio/AbstractNioChannel.java | 11 +++++++---- .../netty/channel/socket/nio/AbstractNioWorker.java | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) 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 index 501af6473d..27fe0a8e44 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioChannel.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioChannel.java @@ -145,6 +145,9 @@ abstract class AbstractNioChannel 0) { - int lowWaterMark = ((NioChannelConfig) getConfig()).getWriteBufferLowWaterMark(); + int lowWaterMark = getConfig().getWriteBufferLowWaterMark(); if (writeBufferSize >= lowWaterMark) { interestOps |= Channel.OP_WRITE; } else { interestOps &= ~Channel.OP_WRITE; } } else { - int highWaterMark = ((NioChannelConfig) getConfig()).getWriteBufferHighWaterMark(); + int highWaterMark = getConfig().getWriteBufferHighWaterMark(); if (writeBufferSize >= highWaterMark) { interestOps |= Channel.OP_WRITE; } else { @@ -333,7 +336,7 @@ abstract class AbstractNioChannel= highWaterMark) { if (newWriteBufferSize - messageSize < highWaterMark) { @@ -354,7 +357,7 @@ abstract class AbstractNioChannel= lowWaterMark) { 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 index 8faccf893a..2b96e04f40 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java @@ -380,7 +380,7 @@ abstract class AbstractNioWorker implements Runnable { final SocketSendBufferPool sendBufferPool = this.sendBufferPool; final WritableByteChannel ch = channel.channel; final Queue writeBuffer = channel.writeBufferQueue; - final int writeSpinCount = ((NioChannelConfig) channel.getConfig()).getWriteSpinCount(); + final int writeSpinCount = channel.getConfig().getWriteSpinCount(); synchronized (channel.writeLock) { channel.inWriteNowLoop = true; for (;;) { From bb6d1402200506db2145c52719fbc0bf769a21e2 Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Mon, 20 Feb 2012 10:28:40 +0100 Subject: [PATCH 07/66] Fix compilation issue due to jdk bug Inference fails for type variable return constraint, see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6302954 --- .../util/internal/LegacyLinkedTransferQueue.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/common/src/main/java/io/netty/util/internal/LegacyLinkedTransferQueue.java b/common/src/main/java/io/netty/util/internal/LegacyLinkedTransferQueue.java index f0168ea637..7e48c15ddf 100644 --- a/common/src/main/java/io/netty/util/internal/LegacyLinkedTransferQueue.java +++ b/common/src/main/java/io/netty/util/internal/LegacyLinkedTransferQueue.java @@ -612,6 +612,7 @@ public class LegacyLinkedTransferQueue extends AbstractQueue @SuppressWarnings("unchecked") static E cast(Object item) { // assert item == null || item.getClass() != Node.class; + // Explicit cast, see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6302954 return (E) item; } @@ -653,7 +654,8 @@ public class LegacyLinkedTransferQueue extends AbstractQueue } } LockSupport.unpark(p.waiter); - return LegacyLinkedTransferQueue.cast(item); + // Explicit cast, see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6302954 + return (E) LegacyLinkedTransferQueue.cast(item); } } Node n = p.next; @@ -737,7 +739,8 @@ public class LegacyLinkedTransferQueue extends AbstractQueue if (item != e) { // matched // assert item != s; s.forgetContents(); // avoid garbage - return LegacyLinkedTransferQueue.cast(item); + // Explicit cast, see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6302954 + return (E) LegacyLinkedTransferQueue.cast(item); } if ((w.isInterrupted() || timed && nanos <= 0) && s.casItem(e, s)) { // cancel @@ -825,7 +828,8 @@ public class LegacyLinkedTransferQueue extends AbstractQueue Object item = p.item; if (p.isData) { if (item != null && item != p) { - return LegacyLinkedTransferQueue.cast(item); + // Explicit cast, see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6302954 + return (E) LegacyLinkedTransferQueue.cast(item); } } else if (item == null) { @@ -878,7 +882,8 @@ public class LegacyLinkedTransferQueue extends AbstractQueue Object item = p.item; if (p.isData) { if (item != null && item != p) { - nextItem = LegacyLinkedTransferQueue.cast(item); + // Explicit cast, see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6302954 + nextItem = (E) LegacyLinkedTransferQueue.cast(item); nextNode = p; return; } From 97b4876c08207ba8f7c16bcb7c1bae7864c88898 Mon Sep 17 00:00:00 2001 From: Trustin Lee Date: Mon, 20 Feb 2012 16:01:28 -0800 Subject: [PATCH 08/66] Fix build errors with m2e --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6ac479d717..d704137bb5 100644 --- a/pom.xml +++ b/pom.xml @@ -217,7 +217,7 @@ [1.6.0,) - [3.0.3,) + [3.0.2,) From a715220556128b0bfee09ffcbf06ea19222a4ee9 Mon Sep 17 00:00:00 2001 From: Trustin Lee Date: Mon, 20 Feb 2012 16:04:46 -0800 Subject: [PATCH 09/66] Fix a compilation error with the SCTP module in Mac --- transport-sctp/src/main/java/com/sun/nio/sctp/SctpChannel.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/transport-sctp/src/main/java/com/sun/nio/sctp/SctpChannel.java b/transport-sctp/src/main/java/com/sun/nio/sctp/SctpChannel.java index 01ad77c0e3..4d0c906d0e 100644 --- a/transport-sctp/src/main/java/com/sun/nio/sctp/SctpChannel.java +++ b/transport-sctp/src/main/java/com/sun/nio/sctp/SctpChannel.java @@ -52,4 +52,6 @@ public abstract class SctpChannel extends AbstractSelectableChannel { public abstract MessageInfo receive(ByteBuffer dst, T attachment, NotificationHandler handler) throws IOException; public abstract int send(ByteBuffer src, MessageInfo messageInfo) throws IOException; + + public abstract Set> supportedOptions(); } From b3cc3055784c783f706a6277c18db299bc316415 Mon Sep 17 00:00:00 2001 From: Dennis Boldt Date: Tue, 21 Feb 2012 03:06:26 +0100 Subject: [PATCH 10/66] Organized imports. --- .../http/websocketx/WebSocketClientHandshaker.java | 10 ++-------- .../http/websocketx/WebSocketServerHandshaker.java | 14 ++++---------- .../http/websocketx/autobahn/AutobahnServer.java | 3 --- .../http/websocketx/server/WebSocketServer.java | 11 ++++------- .../websocketx/sslserver/WebSocketSslServer.java | 11 ++++------- .../netty/channel/sctp/NioSctpChannelConfig.java | 1 - .../java/io/netty/channel/sctp/SctpChannel.java | 3 --- .../AbstractSocketClientBootstrapTest.java | 2 -- .../transport/sctp/SctpMultiStreamingEchoTest.java | 2 -- .../transport/sctp/SctpServerBootstrapTest.java | 1 - 10 files changed, 14 insertions(+), 44 deletions(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java index 18ad9ec051..246db85bda 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java @@ -16,17 +16,11 @@ package io.netty.handler.codec.http.websocketx; import java.net.URI; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.util.Map; -import io.netty.buffer.ChannelBuffer; -import io.netty.buffer.ChannelBuffers; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; -import io.netty.handler.codec.base64.Base64; import io.netty.handler.codec.http.HttpResponse; -import io.netty.util.CharsetUtil; /** * Base class for web socket client handshake implementations @@ -111,7 +105,7 @@ public abstract class WebSocketClientHandshaker { /** * Begins the opening handshake - * + * * @param channel * Channel */ @@ -119,7 +113,7 @@ public abstract class WebSocketClientHandshaker { /** * Validates and finishes the opening handshake initiated by {@link #handshake}}. - * + * * @param channel * Channel * @param response diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker.java index 5cbe843429..e22561bc88 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker.java @@ -15,18 +15,12 @@ */ package io.netty.handler.codec.http.websocketx; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.util.LinkedHashSet; import java.util.Set; -import io.netty.buffer.ChannelBuffer; -import io.netty.buffer.ChannelBuffers; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; -import io.netty.handler.codec.base64.Base64; import io.netty.handler.codec.http.HttpRequest; -import io.netty.util.CharsetUtil; /** * Base class for server side web socket opening and closing handshakes @@ -41,7 +35,7 @@ public abstract class WebSocketServerHandshaker { /** * Constructor specifying the destination web socket location - * + * * @param version * the protocol version * @param webSocketUrl @@ -92,7 +86,7 @@ public abstract class WebSocketServerHandshaker { /** * Performs the opening handshake - * + * * @param channel * Channel * @param req @@ -102,7 +96,7 @@ public abstract class WebSocketServerHandshaker { /** * Performs the closing handshake - * + * * @param channel * Channel * @param frame @@ -112,7 +106,7 @@ public abstract class WebSocketServerHandshaker { /** * Selects the first matching supported sub protocol - * + * * @param requestedSubprotocols * CSV of protocols to be supported. e.g. "chat, superchat" * @return First matching supported sub protocol. Null if not found. diff --git a/example/src/main/java/io/netty/example/http/websocketx/autobahn/AutobahnServer.java b/example/src/main/java/io/netty/example/http/websocketx/autobahn/AutobahnServer.java index 2f2f5bab4e..c4a012ca59 100644 --- a/example/src/main/java/io/netty/example/http/websocketx/autobahn/AutobahnServer.java +++ b/example/src/main/java/io/netty/example/http/websocketx/autobahn/AutobahnServer.java @@ -17,9 +17,6 @@ package io.netty.example.http.websocketx.autobahn; import java.net.InetSocketAddress; import java.util.concurrent.Executors; -import java.util.logging.ConsoleHandler; -import java.util.logging.Level; -import java.util.logging.Logger; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.socket.nio.NioServerSocketChannelFactory; diff --git a/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServer.java b/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServer.java index b2899692dd..d0918c4ec1 100644 --- a/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServer.java +++ b/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServer.java @@ -17,23 +17,20 @@ package io.netty.example.http.websocketx.server; import java.net.InetSocketAddress; import java.util.concurrent.Executors; -import java.util.logging.ConsoleHandler; -import java.util.logging.Level; -import java.util.logging.Logger; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.socket.nio.NioServerSocketChannelFactory; /** * A HTTP server which serves Web Socket requests at: - * + * * http://localhost:8080/websocket - * + * * Open your browser at http://localhost:8080/, then the demo page will be loaded and a Web Socket connection will be * made automatically. - * + * * This server illustrates support for the different web socket specification versions and will work with: - * + * *
    *
  • Safari 5+ (draft-ietf-hybi-thewebsocketprotocol-00) *
  • Chrome 6-13 (draft-ietf-hybi-thewebsocketprotocol-00) diff --git a/example/src/main/java/io/netty/example/http/websocketx/sslserver/WebSocketSslServer.java b/example/src/main/java/io/netty/example/http/websocketx/sslserver/WebSocketSslServer.java index def9595f90..e84c3dd93c 100644 --- a/example/src/main/java/io/netty/example/http/websocketx/sslserver/WebSocketSslServer.java +++ b/example/src/main/java/io/netty/example/http/websocketx/sslserver/WebSocketSslServer.java @@ -17,23 +17,20 @@ package io.netty.example.http.websocketx.sslserver; import java.net.InetSocketAddress; import java.util.concurrent.Executors; -import java.util.logging.ConsoleHandler; -import java.util.logging.Level; -import java.util.logging.Logger; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.socket.nio.NioServerSocketChannelFactory; /** * A HTTP server which serves Web Socket requests at: - * + * * https://localhost:8081/websocket - * + * * Open your browser at https://localhost:8081/, then the demo page will be loaded and a Web Socket connection will be * made automatically. - * + * * This server illustrates support for the different web socket specification versions and will work with: - * + * *
      *
    • Safari 5+ (draft-ietf-hybi-thewebsocketprotocol-00) *
    • Chrome 6-13 (draft-ietf-hybi-thewebsocketprotocol-00) diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/NioSctpChannelConfig.java b/transport-sctp/src/main/java/io/netty/channel/sctp/NioSctpChannelConfig.java index 69f8fe791d..c2344793b1 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/NioSctpChannelConfig.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/NioSctpChannelConfig.java @@ -17,7 +17,6 @@ package io.netty.channel.sctp; import io.netty.channel.ReceiveBufferSizePredictor; import io.netty.channel.ReceiveBufferSizePredictorFactory; -import io.netty.channel.socket.SocketChannelConfig; /** * A {@link io.netty.channel.sctp.SctpChannelConfig} for a NIO SCTP/IP {@link io.netty.channel.sctp.SctpChannel}. diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpChannel.java b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpChannel.java index 0c9231588a..fd0d70c15b 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpChannel.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpChannel.java @@ -18,9 +18,6 @@ package io.netty.channel.sctp; import com.sun.nio.sctp.Association; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; -import io.netty.channel.socket.SocketChannel; -import io.netty.channel.socket.SocketChannelConfig; -import io.netty.channel.socket.nio.NioSocketChannelConfig; import java.net.InetAddress; import java.net.InetSocketAddress; 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; From 5fdd2dea12e4eb4f3254886335509449796e087b Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Fri, 24 Feb 2012 20:26:50 +0100 Subject: [PATCH 11/66] Make it possible to schedule upstream events to get fired later in the io-thread. This is the first part of #140 and #187 --- .../codec/embedder/AbstractCodecEmbedder.java | 5 ++ .../socket/http/AbstractHttpChannelSink.java | 30 +++++++++++ .../http/HttpTunnelAcceptedChannelSink.java | 3 +- .../http/HttpTunnelClientChannelSink.java | 3 +- .../http/HttpTunnelServerChannelSink.java | 3 +- .../channel/socket/http/FakeChannelSink.java | 3 +- .../netty/channel/rxtx/RxtxChannelSink.java | 8 +++ .../channel/sctp/AbstractScptChannelSink.java | 43 +++++++++++++++ .../channel/sctp/SctpClientPipelineSink.java | 3 +- .../channel/sctp/SctpServerPipelineSink.java | 3 +- .../io/netty/channel/sctp/SctpWorker.java | 27 +++++++++- .../io/netty/channel/ChannelPipeline.java | 10 ++++ .../java/io/netty/channel/ChannelSink.java | 2 + .../netty/channel/DefaultChannelPipeline.java | 16 ++++++ .../channel/iostream/IoStreamChannelSink.java | 8 +++ .../channel/local/LocalClientChannelSink.java | 8 +++ .../channel/local/LocalServerChannelSink.java | 8 +++ .../java/io/netty/channel/socket/Worker.java | 22 ++++++++ .../socket/nio/AbstractNioChannelSink.java | 44 ++++++++++++++++ .../channel/socket/nio/AbstractNioWorker.java | 27 +++++++++- .../nio/NioClientSocketPipelineSink.java | 3 +- .../socket/nio/NioDatagramPipelineSink.java | 3 +- .../nio/NioServerSocketPipelineSink.java | 3 +- .../socket/oio/AbstractOioChannel.java | 3 ++ .../socket/oio/AbstractOioChannelSink.java | 52 +++++++++++++++++++ .../channel/socket/oio/AbstractOioWorker.java | 36 +++++++++++-- .../oio/OioClientSocketPipelineSink.java | 3 +- .../socket/oio/OioDatagramPipelineSink.java | 3 +- .../oio/OioServerSocketPipelineSink.java | 3 +- 29 files changed, 353 insertions(+), 32 deletions(-) create mode 100644 transport-http/src/main/java/io/netty/channel/socket/http/AbstractHttpChannelSink.java create mode 100644 transport-sctp/src/main/java/io/netty/channel/sctp/AbstractScptChannelSink.java create mode 100644 transport/src/main/java/io/netty/channel/socket/Worker.java create mode 100644 transport/src/main/java/io/netty/channel/socket/nio/AbstractNioChannelSink.java create mode 100644 transport/src/main/java/io/netty/channel/socket/oio/AbstractOioChannelSink.java diff --git a/codec/src/main/java/io/netty/handler/codec/embedder/AbstractCodecEmbedder.java b/codec/src/main/java/io/netty/handler/codec/embedder/AbstractCodecEmbedder.java index 4e85a1d01c..206c8278f7 100644 --- a/codec/src/main/java/io/netty/handler/codec/embedder/AbstractCodecEmbedder.java +++ b/codec/src/main/java/io/netty/handler/codec/embedder/AbstractCodecEmbedder.java @@ -224,6 +224,11 @@ abstract class AbstractCodecEmbedder implements CodecEmbedder { throw new CodecEmbedderException(actualCause); } + + @Override + public void fireEventLater(ChannelPipeline pipeline, ChannelEvent e) throws Exception { + handleEvent(e); + } } private static final class EmbeddedChannelPipeline extends DefaultChannelPipeline { diff --git a/transport-http/src/main/java/io/netty/channel/socket/http/AbstractHttpChannelSink.java b/transport-http/src/main/java/io/netty/channel/socket/http/AbstractHttpChannelSink.java new file mode 100644 index 0000000000..a89a8aa837 --- /dev/null +++ b/transport-http/src/main/java/io/netty/channel/socket/http/AbstractHttpChannelSink.java @@ -0,0 +1,30 @@ +/* + * 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.http; + +import io.netty.channel.AbstractChannelSink; +import io.netty.channel.ChannelEvent; +import io.netty.channel.ChannelPipeline; + +public abstract class AbstractHttpChannelSink extends AbstractChannelSink{ + + @Override + public void fireEventLater(ChannelPipeline pipeline, ChannelEvent e) throws Exception { + pipeline.sendUpstream(e); + } + +} diff --git a/transport-http/src/main/java/io/netty/channel/socket/http/HttpTunnelAcceptedChannelSink.java b/transport-http/src/main/java/io/netty/channel/socket/http/HttpTunnelAcceptedChannelSink.java index 591d20ac38..ff0be867c6 100644 --- a/transport-http/src/main/java/io/netty/channel/socket/http/HttpTunnelAcceptedChannelSink.java +++ b/transport-http/src/main/java/io/netty/channel/socket/http/HttpTunnelAcceptedChannelSink.java @@ -18,7 +18,6 @@ package io.netty.channel.socket.http; import java.util.concurrent.atomic.AtomicBoolean; import io.netty.buffer.ChannelBuffer; -import io.netty.channel.AbstractChannelSink; import io.netty.channel.Channel; import io.netty.channel.ChannelEvent; import io.netty.channel.ChannelFuture; @@ -33,7 +32,7 @@ import io.netty.channel.MessageEvent; * from here to the ServerMessageSwitch, which queues the data awaiting a poll request from the * client end of the tunnel. */ -class HttpTunnelAcceptedChannelSink extends AbstractChannelSink { +class HttpTunnelAcceptedChannelSink extends AbstractHttpChannelSink { final SaturationManager saturationManager; diff --git a/transport-http/src/main/java/io/netty/channel/socket/http/HttpTunnelClientChannelSink.java b/transport-http/src/main/java/io/netty/channel/socket/http/HttpTunnelClientChannelSink.java index 5f1ecce1c5..6a55acf7c1 100644 --- a/transport-http/src/main/java/io/netty/channel/socket/http/HttpTunnelClientChannelSink.java +++ b/transport-http/src/main/java/io/netty/channel/socket/http/HttpTunnelClientChannelSink.java @@ -17,7 +17,6 @@ package io.netty.channel.socket.http; import java.net.InetSocketAddress; -import io.netty.channel.AbstractChannelSink; import io.netty.channel.ChannelEvent; import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelStateEvent; @@ -27,7 +26,7 @@ import io.netty.channel.MessageEvent; * Sink of a client channel, deals with sunk events and then makes appropriate calls * on the channel itself to push data. */ -class HttpTunnelClientChannelSink extends AbstractChannelSink { +class HttpTunnelClientChannelSink extends AbstractHttpChannelSink { @Override public void eventSunk(ChannelPipeline pipeline, ChannelEvent e) diff --git a/transport-http/src/main/java/io/netty/channel/socket/http/HttpTunnelServerChannelSink.java b/transport-http/src/main/java/io/netty/channel/socket/http/HttpTunnelServerChannelSink.java index e755229674..a7ed9ee1bb 100644 --- a/transport-http/src/main/java/io/netty/channel/socket/http/HttpTunnelServerChannelSink.java +++ b/transport-http/src/main/java/io/netty/channel/socket/http/HttpTunnelServerChannelSink.java @@ -17,7 +17,6 @@ package io.netty.channel.socket.http; import java.net.SocketAddress; -import io.netty.channel.AbstractChannelSink; import io.netty.channel.ChannelEvent; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; @@ -27,7 +26,7 @@ import io.netty.channel.socket.ServerSocketChannel; /** */ -class HttpTunnelServerChannelSink extends AbstractChannelSink { +class HttpTunnelServerChannelSink extends AbstractHttpChannelSink { private ChannelFutureListener closeHook; diff --git a/transport-http/src/test/java/io/netty/channel/socket/http/FakeChannelSink.java b/transport-http/src/test/java/io/netty/channel/socket/http/FakeChannelSink.java index 0798eb94ad..0906755de0 100644 --- a/transport-http/src/test/java/io/netty/channel/socket/http/FakeChannelSink.java +++ b/transport-http/src/test/java/io/netty/channel/socket/http/FakeChannelSink.java @@ -19,14 +19,13 @@ package io.netty.channel.socket.http; import java.util.LinkedList; import java.util.Queue; -import io.netty.channel.AbstractChannelSink; import io.netty.channel.ChannelEvent; import io.netty.channel.ChannelPipeline; /** * A fake channel sink for use in testing */ -public class FakeChannelSink extends AbstractChannelSink { +public class FakeChannelSink extends AbstractHttpChannelSink { public Queue events = new LinkedList(); diff --git a/transport-rxtx/src/main/java/io/netty/channel/rxtx/RxtxChannelSink.java b/transport-rxtx/src/main/java/io/netty/channel/rxtx/RxtxChannelSink.java index fd1c34c0a0..a1692c2718 100644 --- a/transport-rxtx/src/main/java/io/netty/channel/rxtx/RxtxChannelSink.java +++ b/transport-rxtx/src/main/java/io/netty/channel/rxtx/RxtxChannelSink.java @@ -329,4 +329,12 @@ public class RxtxChannelSink extends AbstractChannelSink { } } } + + /** + * Just fire the event now by calling {@link ChannelPipeline#sendUpstream(ChannelEvent)} as this implementation does not support it otherwise + */ + @Override + public void fireEventLater(ChannelPipeline pipeline, ChannelEvent event) throws Exception { + pipeline.sendUpstream(event); + } } diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/AbstractScptChannelSink.java b/transport-sctp/src/main/java/io/netty/channel/sctp/AbstractScptChannelSink.java new file mode 100644 index 0000000000..ebb42a2a93 --- /dev/null +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/AbstractScptChannelSink.java @@ -0,0 +1,43 @@ +/* + * 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.sctp; + +import io.netty.channel.AbstractChannelSink; +import io.netty.channel.Channel; +import io.netty.channel.ChannelEvent; +import io.netty.channel.ChannelPipeline; + +public abstract class AbstractScptChannelSink extends AbstractChannelSink{ + + @Override + public void fireEventLater(final ChannelPipeline pipeline, final ChannelEvent e) throws Exception { + Channel ch = e.getChannel(); + if (ch instanceof SctpChannelImpl) { + SctpChannelImpl channel = (SctpChannelImpl) ch; + channel.worker.fireEventLater(new Runnable() { + + @Override + public void run() { + pipeline.sendUpstream(e); + } + }); + } else { + throw new UnsupportedOperationException(); + } + + } +} diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpClientPipelineSink.java b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpClientPipelineSink.java index 363407f7d5..bd65b89e57 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpClientPipelineSink.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpClientPipelineSink.java @@ -32,7 +32,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; @@ -48,7 +47,7 @@ import io.netty.util.internal.QueueFactory; /** */ -class SctpClientPipelineSink extends AbstractChannelSink { +class SctpClientPipelineSink extends AbstractScptChannelSink { static final InternalLogger logger = InternalLoggerFactory.getInstance(SctpClientPipelineSink.class); diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpServerPipelineSink.java b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpServerPipelineSink.java index 3a0f86bb16..ce34643315 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpServerPipelineSink.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpServerPipelineSink.java @@ -31,7 +31,6 @@ import java.util.concurrent.atomic.AtomicInteger; import com.sun.nio.sctp.SctpChannel; -import io.netty.channel.AbstractChannelSink; import io.netty.channel.Channel; import io.netty.channel.ChannelEvent; import io.netty.channel.ChannelFuture; @@ -45,7 +44,7 @@ import io.netty.util.internal.DeadLockProofWorker; /** */ -class SctpServerPipelineSink extends AbstractChannelSink { +class SctpServerPipelineSink extends AbstractScptChannelSink { static final InternalLogger logger = InternalLoggerFactory.getInstance(SctpServerPipelineSink.class); diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpWorker.java b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpWorker.java index a7878d77ba..ea6f9710a7 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpWorker.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpWorker.java @@ -45,6 +45,7 @@ import io.netty.channel.ChannelFuture; import io.netty.channel.MessageEvent; import io.netty.channel.ReceiveBufferSizePredictor; import io.netty.channel.sctp.SctpSendBufferPool.SendBuffer; +import io.netty.channel.socket.Worker; import io.netty.logging.InternalLogger; import io.netty.logging.InternalLoggerFactory; import io.netty.util.internal.DeadLockProofWorker; @@ -53,7 +54,7 @@ import io.netty.util.internal.QueueFactory; /** */ @SuppressWarnings("unchecked") -class SctpWorker implements Runnable { +class SctpWorker implements Worker { private static final InternalLogger logger = InternalLoggerFactory.getInstance(SctpWorker.class); @@ -71,6 +72,8 @@ class SctpWorker implements Runnable { private final Object startStopLock = new Object(); private final Queue registerTaskQueue = QueueFactory.createQueue(Runnable.class); private 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 SctpReceiveBufferPool recvBufferPool = new SctpReceiveBufferPool(); @@ -188,6 +191,7 @@ class SctpWorker implements Runnable { cancelledKeys = 0; processRegisterTaskQueue(); + processEventQueue(); processWriteTaskQueue(); processSelectedKeys(selector.selectedKeys()); @@ -240,7 +244,14 @@ class SctpWorker implements Runnable { } } } - + + public void fireEventLater(Runnable eventRunnable) { + assert eventQueue.offer(eventRunnable); + + // wake up the selector to speed things + selector.wakeup(); + } + private void processRegisterTaskQueue() throws IOException { for (; ;) { final Runnable task = registerTaskQueue.poll(); @@ -264,7 +275,19 @@ class SctpWorker implements Runnable { cleanUpCancelledKeys(); } } + + private void processEventQueue() throws IOException { + for (;;) { + final Runnable task = eventQueue.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(); diff --git a/transport/src/main/java/io/netty/channel/ChannelPipeline.java b/transport/src/main/java/io/netty/channel/ChannelPipeline.java index cbdeeab097..6a74e5033c 100644 --- a/transport/src/main/java/io/netty/channel/ChannelPipeline.java +++ b/transport/src/main/java/io/netty/channel/ChannelPipeline.java @@ -442,6 +442,16 @@ public interface ChannelPipeline { */ void sendUpstream(ChannelEvent e); + + /** + * Sends the specified {@link ChannelEvent} to the first + * {@link ChannelUpstreamHandler} in this pipeline when the next IO-Worker operation is performed. + * + * @throws NullPointerException + * if the specified event is {@code null} + */ + void sendUpstreamLater(ChannelEvent e); + /** * Sends the specified {@link ChannelEvent} to the last * {@link ChannelDownstreamHandler} in this pipeline. diff --git a/transport/src/main/java/io/netty/channel/ChannelSink.java b/transport/src/main/java/io/netty/channel/ChannelSink.java index 86d042e56f..16ebb8c52e 100644 --- a/transport/src/main/java/io/netty/channel/ChannelSink.java +++ b/transport/src/main/java/io/netty/channel/ChannelSink.java @@ -37,4 +37,6 @@ public interface ChannelSink { * one of its {@link ChannelHandler}s process a {@link ChannelEvent}. */ void exceptionCaught(ChannelPipeline pipeline, ChannelEvent e, ChannelPipelineException cause) throws Exception; + + void fireEventLater(ChannelPipeline pipeline, ChannelEvent e) throws Exception; } diff --git a/transport/src/main/java/io/netty/channel/DefaultChannelPipeline.java b/transport/src/main/java/io/netty/channel/DefaultChannelPipeline.java index 50487ae36e..b8379b1b98 100644 --- a/transport/src/main/java/io/netty/channel/DefaultChannelPipeline.java +++ b/transport/src/main/java/io/netty/channel/DefaultChannelPipeline.java @@ -583,6 +583,15 @@ public class DefaultChannelPipeline implements ChannelPipeline { } } + @Override + public void sendUpstreamLater(ChannelEvent e) { + try { + getSink().fireEventLater(this, e); + } catch (Throwable t) { + notifyHandlerException(e, t); + } + } + @Override public void sendDownstream(ChannelEvent e) { DefaultChannelHandlerContext tail = getActualDownstreamContext(this.tail); @@ -832,5 +841,12 @@ public class DefaultChannelPipeline implements ChannelPipeline { ChannelEvent e, ChannelPipelineException cause) throws Exception { throw cause; } + + @Override + public void fireEventLater(ChannelPipeline pipeline, ChannelEvent e) throws Exception { + if (logger.isWarnEnabled()) { + logger.warn("Not attached yet; discarding: " + e); + } + } } } diff --git a/transport/src/main/java/io/netty/channel/iostream/IoStreamChannelSink.java b/transport/src/main/java/io/netty/channel/iostream/IoStreamChannelSink.java index a0d75ceef7..5596540da2 100755 --- a/transport/src/main/java/io/netty/channel/iostream/IoStreamChannelSink.java +++ b/transport/src/main/java/io/netty/channel/iostream/IoStreamChannelSink.java @@ -177,4 +177,12 @@ public class IoStreamChannelSink extends AbstractChannelSink { } } } + + /** + * This just calls {@link ChannelPipeline#sendUpstream(ChannelEvent)} as the transport does not support it + */ + @Override + public void fireEventLater(ChannelPipeline pipeline, ChannelEvent e) throws Exception { + pipeline.sendUpstream(e); + } } diff --git a/transport/src/main/java/io/netty/channel/local/LocalClientChannelSink.java b/transport/src/main/java/io/netty/channel/local/LocalClientChannelSink.java index 9b6bd455de..066bf27cc4 100644 --- a/transport/src/main/java/io/netty/channel/local/LocalClientChannelSink.java +++ b/transport/src/main/java/io/netty/channel/local/LocalClientChannelSink.java @@ -85,6 +85,14 @@ final class LocalClientChannelSink extends AbstractChannelSink { } } + /** + * Just fire the event now by calling {@link ChannelPipeline#sendUpstream(ChannelEvent)} as this implementation does not support it otherwise + */ + @Override + public void fireEventLater(ChannelPipeline pipeline, ChannelEvent event) throws Exception { + pipeline.sendUpstream(event); + } + private void bind(DefaultLocalChannel channel, ChannelFuture future, LocalAddress localAddress) { try { if (!LocalChannelRegistry.register(localAddress, channel)) { diff --git a/transport/src/main/java/io/netty/channel/local/LocalServerChannelSink.java b/transport/src/main/java/io/netty/channel/local/LocalServerChannelSink.java index 6bfa780f28..29f5b9c2d0 100644 --- a/transport/src/main/java/io/netty/channel/local/LocalServerChannelSink.java +++ b/transport/src/main/java/io/netty/channel/local/LocalServerChannelSink.java @@ -42,6 +42,14 @@ final class LocalServerChannelSink extends AbstractChannelSink { } } + /** + * Just fire the event now by calling {@link ChannelPipeline#sendUpstream(ChannelEvent)} as this implementation does not support it otherwise + */ + @Override + public void fireEventLater(ChannelPipeline pipeline, ChannelEvent event) throws Exception { + pipeline.sendUpstream(event); + } + private void handleServerChannel(ChannelEvent e) { if (!(e instanceof ChannelStateEvent)) { return; 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..8a2bf10424 --- /dev/null +++ b/transport/src/main/java/io/netty/channel/socket/Worker.java @@ -0,0 +1,22 @@ +/* + * 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; + +public interface Worker extends Runnable{ + + void fireEventLater(Runnable eventRunnable); +} 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..879a5d433a --- /dev/null +++ b/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioChannelSink.java @@ -0,0 +1,44 @@ +/* + * 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.ChannelPipeline; + +public abstract class AbstractNioChannelSink extends AbstractChannelSink{ + + @Override + public void fireEventLater(final ChannelPipeline pipeline, final ChannelEvent e) throws Exception { + Channel ch = e.getChannel(); + if (ch instanceof AbstractNioChannel) { + AbstractNioChannel channel = (AbstractNioChannel) ch; + channel.worker.fireEventLater(new Runnable() { + + @Override + public void run() { + pipeline.sendUpstream(e); + } + }); + } else { + throw new UnsupportedOperationException(); + } + + } + +} 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 index 2b96e04f40..708b0ee343 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java @@ -21,6 +21,7 @@ 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.Worker; import io.netty.channel.socket.nio.SocketSendBufferPool.SendBuffer; import io.netty.logging.InternalLogger; import io.netty.logging.InternalLoggerFactory; @@ -44,7 +45,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; -abstract class AbstractNioWorker implements Runnable { +abstract class AbstractNioWorker implements Worker { /** * Internal Netty logger. */ @@ -106,6 +107,9 @@ abstract class AbstractNioWorker implements Runnable { */ 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(); @@ -216,6 +220,7 @@ abstract class AbstractNioWorker implements Runnable { cancelledKeys = 0; processRegisterTaskQueue(); + processEventQueue(); processWriteTaskQueue(); processSelectedKeys(selector.selectedKeys()); @@ -266,7 +271,13 @@ abstract class AbstractNioWorker implements Runnable { } } - + public void fireEventLater(Runnable eventRunnable) { + assert eventQueue.offer(eventRunnable); + + // wake up the selector to speed things + selector.wakeup(); + } + private void processRegisterTaskQueue() throws IOException { for (;;) { final Runnable task = registerTaskQueue.poll(); @@ -291,6 +302,18 @@ abstract class AbstractNioWorker implements Runnable { } } + 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(); 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 ca2333659f..0cd8f1b124 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); 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/NioServerSocketPipelineSink.java b/transport/src/main/java/io/netty/channel/socket/nio/NioServerSocketPipelineSink.java index 5de57a6f01..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); 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 index 7447254cde..2c7009050a 100644 --- a/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioChannel.java +++ b/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioChannel.java @@ -25,11 +25,14 @@ 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( 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..00e480a446 --- /dev/null +++ b/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioChannelSink.java @@ -0,0 +1,52 @@ +/* + * 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.ChannelPipeline; +import io.netty.channel.socket.Worker; + +public abstract class AbstractOioChannelSink extends AbstractChannelSink{ + + @Override + public void fireEventLater(final ChannelPipeline pipeline, final ChannelEvent e) throws Exception { + Channel ch = e.getChannel(); + if (ch instanceof AbstractOioChannel) { + AbstractOioChannel channel = (AbstractOioChannel) ch; + Worker worker = channel.worker; + if (worker != null) { + channel.worker.fireEventLater(new Runnable() { + + @Override + public void run() { + pipeline.sendUpstream(e); + } + }); + } else { + // no worker thread yet so just fire the event now + pipeline.sendUpstream(e); + } + + } else { + throw new UnsupportedOperationException(); + } + + } + +} 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 index 898ef994c3..b20b24a926 100644 --- a/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioWorker.java +++ b/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioWorker.java @@ -24,20 +24,26 @@ import static io.netty.channel.Channels.succeededFuture; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.Channels; +import io.netty.channel.socket.Worker; +import io.netty.util.internal.QueueFactory; import java.io.IOException; +import java.util.Queue; /** * Abstract base class for Oio-Worker implementations * * @param {@link AbstractOioChannel} */ -abstract class AbstractOioWorker implements Runnable { +abstract class AbstractOioWorker implements Worker{ + private final Queue eventQueue = QueueFactory.createQueue(Runnable.class); + protected final C channel; public AbstractOioWorker(C channel) { this.channel = channel; + channel.worker = this; } @Override @@ -60,9 +66,13 @@ abstract class AbstractOioWorker implements Runnab } try { - if (!process()) { - break; - } + boolean cont = process(); + + processEventQueue(); + + if (!cont) { + break; + } } catch (Throwable t) { if (!channel.isSocketClosed()) { fireExceptionCaught(channel, t); @@ -79,6 +89,24 @@ abstract class AbstractOioWorker implements Runnab close(channel, succeededFuture(channel)); } + + @Override + public void fireEventLater(Runnable eventRunnable) { + assert eventQueue.offer(eventRunnable); + } + + private void processEventQueue() throws IOException { + for (;;) { + final Runnable 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. 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/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/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); From c2bc463d611ee2f15ce6205cf7fecd0c4c69cb2b Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Fri, 24 Feb 2012 22:03:32 +0100 Subject: [PATCH 12/66] Optimize the handling of fireEventLater if the current thread is the worker thread. See #187 and #140 --- .../channel/sctp/AbstractScptChannelSink.java | 17 +++++++++++------ .../java/io/netty/channel/sctp/SctpWorker.java | 2 +- .../socket/nio/AbstractNioChannelSink.java | 17 +++++++++++------ .../socket/oio/AbstractOioChannelSink.java | 4 ++-- 4 files changed, 25 insertions(+), 15 deletions(-) diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/AbstractScptChannelSink.java b/transport-sctp/src/main/java/io/netty/channel/sctp/AbstractScptChannelSink.java index ebb42a2a93..642cd891b1 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/AbstractScptChannelSink.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/AbstractScptChannelSink.java @@ -28,13 +28,18 @@ public abstract class AbstractScptChannelSink extends AbstractChannelSink{ Channel ch = e.getChannel(); if (ch instanceof SctpChannelImpl) { SctpChannelImpl channel = (SctpChannelImpl) ch; - channel.worker.fireEventLater(new Runnable() { + // check if the current thread is a worker thread, and only fire the event later if thats not the case + if (channel.worker.thread != Thread.currentThread()) { + channel.worker.fireEventLater(new Runnable() { - @Override - public void run() { - pipeline.sendUpstream(e); - } - }); + @Override + public void run() { + pipeline.sendUpstream(e); + } + }); + } else { + pipeline.sendUpstream(e); + } } else { throw new UnsupportedOperationException(); } diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpWorker.java b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpWorker.java index ea6f9710a7..e546019da3 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpWorker.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpWorker.java @@ -65,7 +65,7 @@ class SctpWorker implements Worker { private final Executor executor; private boolean started; - private volatile Thread thread; + volatile Thread thread; volatile Selector selector; private final AtomicBoolean wakenUp = new AtomicBoolean(); private final ReadWriteLock selectorGuard = new ReentrantReadWriteLock(); 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 index 879a5d433a..6812b59a58 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioChannelSink.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioChannelSink.java @@ -28,13 +28,18 @@ public abstract class AbstractNioChannelSink extends AbstractChannelSink{ Channel ch = e.getChannel(); if (ch instanceof AbstractNioChannel) { AbstractNioChannel channel = (AbstractNioChannel) ch; - channel.worker.fireEventLater(new Runnable() { + // check if the current thread is a worker thread if so we can send the event now + if (channel.worker.thread != Thread.currentThread()) { + channel.worker.fireEventLater(new Runnable() { - @Override - public void run() { - pipeline.sendUpstream(e); - } - }); + @Override + public void run() { + pipeline.sendUpstream(e); + } + }); + } else { + pipeline.sendUpstream(e); + } } else { throw new UnsupportedOperationException(); } 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 index 00e480a446..4a7a1a998c 100644 --- a/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioChannelSink.java +++ b/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioChannelSink.java @@ -30,7 +30,7 @@ public abstract class AbstractOioChannelSink extends AbstractChannelSink{ if (ch instanceof AbstractOioChannel) { AbstractOioChannel channel = (AbstractOioChannel) ch; Worker worker = channel.worker; - if (worker != null) { + if (worker != null && channel.workerThread != Thread.currentThread()) { channel.worker.fireEventLater(new Runnable() { @Override @@ -39,7 +39,7 @@ public abstract class AbstractOioChannelSink extends AbstractChannelSink{ } }); } else { - // no worker thread yet so just fire the event now + // no worker thread yet or the current thread is a worker thread so just fire the event now pipeline.sendUpstream(e); } From 301a17c029dd9f0a6539342ac8494781c4efd028 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Sat, 25 Feb 2012 14:19:11 +0100 Subject: [PATCH 13/66] Rename method to better reflect its usage and update some javadocs. See #187 and #140 --- .../handler/codec/embedder/AbstractCodecEmbedder.java | 2 +- .../channel/socket/http/AbstractHttpChannelSink.java | 2 +- .../java/io/netty/channel/rxtx/RxtxChannelSink.java | 2 +- .../netty/channel/sctp/AbstractScptChannelSink.java | 4 ++-- .../main/java/io/netty/channel/sctp/SctpWorker.java | 3 ++- .../src/main/java/io/netty/channel/ChannelSink.java | 5 ++++- .../java/io/netty/channel/DefaultChannelPipeline.java | 4 ++-- .../netty/channel/iostream/IoStreamChannelSink.java | 2 +- .../netty/channel/local/LocalClientChannelSink.java | 2 +- .../netty/channel/local/LocalServerChannelSink.java | 2 +- .../src/main/java/io/netty/channel/socket/Worker.java | 11 ++++++++++- .../channel/socket/nio/AbstractNioChannelSink.java | 4 ++-- .../netty/channel/socket/nio/AbstractNioWorker.java | 3 ++- .../channel/socket/oio/AbstractOioChannelSink.java | 4 ++-- .../netty/channel/socket/oio/AbstractOioWorker.java | 4 +++- 15 files changed, 35 insertions(+), 19 deletions(-) diff --git a/codec/src/main/java/io/netty/handler/codec/embedder/AbstractCodecEmbedder.java b/codec/src/main/java/io/netty/handler/codec/embedder/AbstractCodecEmbedder.java index 206c8278f7..b0796cd994 100644 --- a/codec/src/main/java/io/netty/handler/codec/embedder/AbstractCodecEmbedder.java +++ b/codec/src/main/java/io/netty/handler/codec/embedder/AbstractCodecEmbedder.java @@ -226,7 +226,7 @@ abstract class AbstractCodecEmbedder implements CodecEmbedder { } @Override - public void fireEventLater(ChannelPipeline pipeline, ChannelEvent e) throws Exception { + public void fireUpstreamEventLater(ChannelPipeline pipeline, ChannelEvent e) throws Exception { handleEvent(e); } } diff --git a/transport-http/src/main/java/io/netty/channel/socket/http/AbstractHttpChannelSink.java b/transport-http/src/main/java/io/netty/channel/socket/http/AbstractHttpChannelSink.java index a89a8aa837..e578a2dcfe 100644 --- a/transport-http/src/main/java/io/netty/channel/socket/http/AbstractHttpChannelSink.java +++ b/transport-http/src/main/java/io/netty/channel/socket/http/AbstractHttpChannelSink.java @@ -23,7 +23,7 @@ import io.netty.channel.ChannelPipeline; public abstract class AbstractHttpChannelSink extends AbstractChannelSink{ @Override - public void fireEventLater(ChannelPipeline pipeline, ChannelEvent e) throws Exception { + public void fireUpstreamEventLater(ChannelPipeline pipeline, ChannelEvent e) throws Exception { pipeline.sendUpstream(e); } diff --git a/transport-rxtx/src/main/java/io/netty/channel/rxtx/RxtxChannelSink.java b/transport-rxtx/src/main/java/io/netty/channel/rxtx/RxtxChannelSink.java index a1692c2718..5009dc1427 100644 --- a/transport-rxtx/src/main/java/io/netty/channel/rxtx/RxtxChannelSink.java +++ b/transport-rxtx/src/main/java/io/netty/channel/rxtx/RxtxChannelSink.java @@ -334,7 +334,7 @@ public class RxtxChannelSink extends AbstractChannelSink { * Just fire the event now by calling {@link ChannelPipeline#sendUpstream(ChannelEvent)} as this implementation does not support it otherwise */ @Override - public void fireEventLater(ChannelPipeline pipeline, ChannelEvent event) throws Exception { + public void fireUpstreamEventLater(ChannelPipeline pipeline, ChannelEvent event) throws Exception { pipeline.sendUpstream(event); } } diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/AbstractScptChannelSink.java b/transport-sctp/src/main/java/io/netty/channel/sctp/AbstractScptChannelSink.java index 642cd891b1..5daba78839 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/AbstractScptChannelSink.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/AbstractScptChannelSink.java @@ -24,13 +24,13 @@ import io.netty.channel.ChannelPipeline; public abstract class AbstractScptChannelSink extends AbstractChannelSink{ @Override - public void fireEventLater(final ChannelPipeline pipeline, final ChannelEvent e) throws Exception { + public void fireUpstreamEventLater(final ChannelPipeline pipeline, final ChannelEvent e) throws Exception { Channel ch = e.getChannel(); if (ch instanceof SctpChannelImpl) { SctpChannelImpl channel = (SctpChannelImpl) ch; // check if the current thread is a worker thread, and only fire the event later if thats not the case if (channel.worker.thread != Thread.currentThread()) { - channel.worker.fireEventLater(new Runnable() { + channel.worker.executeInIoThread(new Runnable() { @Override public void run() { diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpWorker.java b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpWorker.java index e546019da3..1cc3a764ac 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpWorker.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpWorker.java @@ -245,7 +245,8 @@ class SctpWorker implements Worker { } } - public void fireEventLater(Runnable eventRunnable) { + @Override + public void executeInIoThread(Runnable eventRunnable) { assert eventQueue.offer(eventRunnable); // wake up the selector to speed things diff --git a/transport/src/main/java/io/netty/channel/ChannelSink.java b/transport/src/main/java/io/netty/channel/ChannelSink.java index 16ebb8c52e..1a086ef982 100644 --- a/transport/src/main/java/io/netty/channel/ChannelSink.java +++ b/transport/src/main/java/io/netty/channel/ChannelSink.java @@ -38,5 +38,8 @@ public interface ChannelSink { */ void exceptionCaught(ChannelPipeline pipeline, ChannelEvent e, ChannelPipelineException cause) throws Exception; - void fireEventLater(ChannelPipeline pipeline, ChannelEvent e) throws Exception; + /** + * Schedule the given {@link ChannelEvent} for later execution (in the io-thread). Some implementation may not support his and just fire it directly + */ + void fireUpstreamEventLater(ChannelPipeline pipeline, ChannelEvent e) throws Exception; } diff --git a/transport/src/main/java/io/netty/channel/DefaultChannelPipeline.java b/transport/src/main/java/io/netty/channel/DefaultChannelPipeline.java index b8379b1b98..4e4499e814 100644 --- a/transport/src/main/java/io/netty/channel/DefaultChannelPipeline.java +++ b/transport/src/main/java/io/netty/channel/DefaultChannelPipeline.java @@ -586,7 +586,7 @@ public class DefaultChannelPipeline implements ChannelPipeline { @Override public void sendUpstreamLater(ChannelEvent e) { try { - getSink().fireEventLater(this, e); + getSink().fireUpstreamEventLater(this, e); } catch (Throwable t) { notifyHandlerException(e, t); } @@ -843,7 +843,7 @@ public class DefaultChannelPipeline implements ChannelPipeline { } @Override - public void fireEventLater(ChannelPipeline pipeline, ChannelEvent e) throws Exception { + public void fireUpstreamEventLater(ChannelPipeline pipeline, ChannelEvent e) throws Exception { if (logger.isWarnEnabled()) { logger.warn("Not attached yet; discarding: " + e); } diff --git a/transport/src/main/java/io/netty/channel/iostream/IoStreamChannelSink.java b/transport/src/main/java/io/netty/channel/iostream/IoStreamChannelSink.java index 5596540da2..51de5d3311 100755 --- a/transport/src/main/java/io/netty/channel/iostream/IoStreamChannelSink.java +++ b/transport/src/main/java/io/netty/channel/iostream/IoStreamChannelSink.java @@ -182,7 +182,7 @@ public class IoStreamChannelSink extends AbstractChannelSink { * This just calls {@link ChannelPipeline#sendUpstream(ChannelEvent)} as the transport does not support it */ @Override - public void fireEventLater(ChannelPipeline pipeline, ChannelEvent e) throws Exception { + public void fireUpstreamEventLater(ChannelPipeline pipeline, ChannelEvent e) throws Exception { pipeline.sendUpstream(e); } } diff --git a/transport/src/main/java/io/netty/channel/local/LocalClientChannelSink.java b/transport/src/main/java/io/netty/channel/local/LocalClientChannelSink.java index 066bf27cc4..d882725de0 100644 --- a/transport/src/main/java/io/netty/channel/local/LocalClientChannelSink.java +++ b/transport/src/main/java/io/netty/channel/local/LocalClientChannelSink.java @@ -89,7 +89,7 @@ final class LocalClientChannelSink extends AbstractChannelSink { * Just fire the event now by calling {@link ChannelPipeline#sendUpstream(ChannelEvent)} as this implementation does not support it otherwise */ @Override - public void fireEventLater(ChannelPipeline pipeline, ChannelEvent event) throws Exception { + public void fireUpstreamEventLater(ChannelPipeline pipeline, ChannelEvent event) throws Exception { pipeline.sendUpstream(event); } diff --git a/transport/src/main/java/io/netty/channel/local/LocalServerChannelSink.java b/transport/src/main/java/io/netty/channel/local/LocalServerChannelSink.java index 29f5b9c2d0..6ead0ad3a2 100644 --- a/transport/src/main/java/io/netty/channel/local/LocalServerChannelSink.java +++ b/transport/src/main/java/io/netty/channel/local/LocalServerChannelSink.java @@ -46,7 +46,7 @@ final class LocalServerChannelSink extends AbstractChannelSink { * Just fire the event now by calling {@link ChannelPipeline#sendUpstream(ChannelEvent)} as this implementation does not support it otherwise */ @Override - public void fireEventLater(ChannelPipeline pipeline, ChannelEvent event) throws Exception { + public void fireUpstreamEventLater(ChannelPipeline pipeline, ChannelEvent event) throws Exception { pipeline.sendUpstream(event); } diff --git a/transport/src/main/java/io/netty/channel/socket/Worker.java b/transport/src/main/java/io/netty/channel/socket/Worker.java index 8a2bf10424..eebfc74d40 100644 --- a/transport/src/main/java/io/netty/channel/socket/Worker.java +++ b/transport/src/main/java/io/netty/channel/socket/Worker.java @@ -16,7 +16,16 @@ package io.netty.channel.socket; +/** + * A {@link Worker} is responsible to dispatch IO operations + * + */ public interface Worker extends Runnable{ - void fireEventLater(Runnable eventRunnable); + /** + * 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 + */ + void executeInIoThread(Runnable task); } 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 index 6812b59a58..85a8d03d3a 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioChannelSink.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioChannelSink.java @@ -24,13 +24,13 @@ import io.netty.channel.ChannelPipeline; public abstract class AbstractNioChannelSink extends AbstractChannelSink{ @Override - public void fireEventLater(final ChannelPipeline pipeline, final ChannelEvent e) throws Exception { + public void fireUpstreamEventLater(final ChannelPipeline pipeline, final ChannelEvent e) throws Exception { Channel ch = e.getChannel(); if (ch instanceof AbstractNioChannel) { AbstractNioChannel channel = (AbstractNioChannel) ch; // check if the current thread is a worker thread if so we can send the event now if (channel.worker.thread != Thread.currentThread()) { - channel.worker.fireEventLater(new Runnable() { + channel.worker.executeInIoThread(new Runnable() { @Override public void run() { 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 index 708b0ee343..43ad21ad9c 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java @@ -271,7 +271,8 @@ abstract class AbstractNioWorker implements Worker { } } - public void fireEventLater(Runnable eventRunnable) { + @Override + public void executeInIoThread(Runnable eventRunnable) { assert eventQueue.offer(eventRunnable); // wake up the selector to speed things 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 index 4a7a1a998c..889a08aa26 100644 --- a/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioChannelSink.java +++ b/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioChannelSink.java @@ -25,13 +25,13 @@ import io.netty.channel.socket.Worker; public abstract class AbstractOioChannelSink extends AbstractChannelSink{ @Override - public void fireEventLater(final ChannelPipeline pipeline, final ChannelEvent e) throws Exception { + public void fireUpstreamEventLater(final ChannelPipeline pipeline, final ChannelEvent e) throws Exception { Channel ch = e.getChannel(); if (ch instanceof AbstractOioChannel) { AbstractOioChannel channel = (AbstractOioChannel) ch; Worker worker = channel.worker; if (worker != null && channel.workerThread != Thread.currentThread()) { - channel.worker.fireEventLater(new Runnable() { + channel.worker.executeInIoThread(new Runnable() { @Override public void run() { 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 index b20b24a926..5dd3e8b14d 100644 --- a/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioWorker.java +++ b/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioWorker.java @@ -91,8 +91,10 @@ abstract class AbstractOioWorker implements Worker @Override - public void fireEventLater(Runnable eventRunnable) { + public void executeInIoThread(Runnable eventRunnable) { assert eventQueue.offer(eventRunnable); + + // as we set the SO_TIMEOUT to 1 second this task will get picked up in 1 second at latest } private void processEventQueue() throws IOException { From 04a6ff92afe2c590a465065135798c438d3f5511 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Sat, 25 Feb 2012 14:28:43 +0100 Subject: [PATCH 14/66] Add static helper methods to fire upstream events later. See #187 and #140 --- .../main/java/io/netty/channel/Channels.java | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/transport/src/main/java/io/netty/channel/Channels.java b/transport/src/main/java/io/netty/channel/Channels.java index e5852d2ced..879758c347 100644 --- a/transport/src/main/java/io/netty/channel/Channels.java +++ b/transport/src/main/java/io/netty/channel/Channels.java @@ -321,6 +321,20 @@ 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 void fireChannelInterestChangedLater(Channel channel) { + channel.getPipeline().sendUpstreamLater( + new UpstreamChannelStateEvent( + channel, ChannelState.INTEREST_OPS, Channel.OP_READ)); + } + /** * Sends a {@code "channelInterestChanged"} event to the first * {@link ChannelUpstreamHandler} in the {@link ChannelPipeline} of @@ -346,6 +360,17 @@ 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 void fireChannelDisconnectedLater(Channel channel) { + channel.getPipeline().sendUpstreamLater( + new UpstreamChannelStateEvent( + channel, ChannelState.CONNECTED, null)); + } + /** * Sends a {@code "channelDisconnected"} event to the first * {@link ChannelUpstreamHandler} in the {@link ChannelPipeline} of @@ -368,6 +393,18 @@ 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 void fireChannelUnboundLater(Channel channel) { + channel.getPipeline().sendUpstreamLater(new UpstreamChannelStateEvent( + channel, ChannelState.BOUND, null)); + } + /** * Sends a {@code "channelUnbound"} event to the first * {@link ChannelUpstreamHandler} in the {@link ChannelPipeline} of @@ -390,6 +427,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 void fireChannelClosedLater(Channel channel) { + channel.getPipeline().sendUpstream( + new UpstreamChannelStateEvent( + channel, ChannelState.OPEN, Boolean.FALSE)); + + // Notify the parent handler. + if (channel.getParent() != null) { + fireChildChannelStateChangedLater(channel.getParent(), channel); + } + } + /** * Sends a {@code "channelClosed"} event to the first * {@link ChannelUpstreamHandler} in the {@link ChannelPipeline} of @@ -418,6 +473,19 @@ 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 void fireExceptionCaughtLater(Channel channel, Throwable cause) { + channel.getPipeline().sendUpstream( + new DefaultExceptionEvent(channel, cause)); + } + + /** * Sends a {@code "exceptionCaught"} event to the first * {@link ChannelUpstreamHandler} in the {@link ChannelPipeline} of @@ -444,6 +512,13 @@ public final class Channels { new DefaultChildChannelStateEvent(channel, childChannel)); } + private static void fireChildChannelStateChangedLater( + Channel channel, Channel childChannel) { + channel.getPipeline().sendUpstreamLater( + new DefaultChildChannelStateEvent(channel, childChannel)); + } + + /** * Sends a {@code "bind"} request to the last * {@link ChannelDownstreamHandler} in the {@link ChannelPipeline} of From c4a437e16b42e88eb9947c65d3dd2413bd9ef5ac Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Sat, 25 Feb 2012 14:30:10 +0100 Subject: [PATCH 15/66] Fix later sending of exceptionCaught events. See #187 and #140 --- transport/src/main/java/io/netty/channel/Channels.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transport/src/main/java/io/netty/channel/Channels.java b/transport/src/main/java/io/netty/channel/Channels.java index 879758c347..c70978987d 100644 --- a/transport/src/main/java/io/netty/channel/Channels.java +++ b/transport/src/main/java/io/netty/channel/Channels.java @@ -481,7 +481,7 @@ public final class Channels { * the specified {@link Channel} once the io-thread runs again. */ public static void fireExceptionCaughtLater(Channel channel, Throwable cause) { - channel.getPipeline().sendUpstream( + channel.getPipeline().sendUpstreamLater( new DefaultExceptionEvent(channel, cause)); } From ef64e8c332717257642583d48ccca6be82aa575e Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Sat, 25 Feb 2012 15:12:58 +0100 Subject: [PATCH 16/66] oio and nio transport now make sure that a upstream event get only executed from an io thread. See #140 and #187 --- .../main/java/io/netty/channel/Channels.java | 15 ++++ .../channel/socket/nio/AbstractNioWorker.java | 69 ++++++++++++++++--- .../channel/socket/nio/NioDatagramWorker.java | 15 +++- .../channel/socket/oio/AbstractOioWorker.java | 52 ++++++++++---- .../channel/socket/oio/OioDatagramWorker.java | 28 ++++++-- .../netty/channel/socket/oio/OioWorker.java | 19 ++++- 6 files changed, 164 insertions(+), 34 deletions(-) diff --git a/transport/src/main/java/io/netty/channel/Channels.java b/transport/src/main/java/io/netty/channel/Channels.java index c70978987d..c24aa19399 100644 --- a/transport/src/main/java/io/netty/channel/Channels.java +++ b/transport/src/main/java/io/netty/channel/Channels.java @@ -298,6 +298,21 @@ 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 void fireWriteCompleteLater(Channel channel, long amount) { + if (amount == 0) { + return; + } + + channel.getPipeline().sendUpstreamLater( + new DefaultWriteCompletionEvent(channel, amount)); + } + + /** * Sends a {@code "writeComplete"} event to the first * {@link ChannelUpstreamHandler} in the {@link ChannelPipeline} of 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 index 43ad21ad9c..14ffdc6d99 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java @@ -398,6 +398,7 @@ abstract class AbstractNioWorker implements Worker { boolean open = true; boolean addOpWrite = false; boolean removeOpWrite = false; + boolean iothread = isIoThread(channel); long writtenBytes = 0; @@ -468,7 +469,11 @@ abstract class AbstractNioWorker implements Worker { buf = null; evt = null; future.setFailure(t); - fireExceptionCaught(channel, t); + if (iothread) { + fireExceptionCaught(channel, t); + } else { + fireExceptionCaughtLater(channel, t); + } if (t instanceof IOException) { open = false; close(channel, succeededFuture(channel)); @@ -491,10 +496,17 @@ abstract class AbstractNioWorker implements Worker { } } } - - fireWriteComplete(channel, writtenBytes); + 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); @@ -545,6 +557,8 @@ abstract class AbstractNioWorker implements Worker { void close(AbstractNioChannel channel, ChannelFuture future) { boolean connected = channel.isConnected(); boolean bound = channel.isBound(); + boolean iothread = isIoThread(channel); + try { channel.channel.close(); cancelledKeys ++; @@ -552,20 +566,36 @@ abstract class AbstractNioWorker implements Worker { if (channel.setClosed()) { future.setSuccess(); if (connected) { - fireChannelDisconnected(channel); + if (iothread) { + fireChannelDisconnected(channel); + } else { + fireChannelDisconnectedLater(channel); + } } if (bound) { - fireChannelUnbound(channel); + if (iothread) { + fireChannelUnbound(channel); + } else { + fireChannelUnboundLater(channel); + } } cleanUpWriteBuffer(channel); - fireChannelClosed(channel); + if (iothread) { + fireChannelClosed(channel); + } else { + fireChannelClosedLater(channel); + } } else { future.setSuccess(); } } catch (Throwable t) { future.setFailure(t); - fireExceptionCaught(channel, t); + if (iothread) { + fireExceptionCaught(channel, t); + } else { + fireExceptionCaughtLater(channel, t); + } } } @@ -618,12 +648,17 @@ abstract class AbstractNioWorker implements Worker { } if (fireExceptionCaught) { - fireExceptionCaught(channel, cause); + 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. @@ -684,16 +719,28 @@ abstract class AbstractNioWorker implements Worker { future.setSuccess(); if (changed) { - fireChannelInterestChanged(channel); + if (iothread) { + fireChannelInterestChanged(channel); + } else { + fireChannelInterestChangedLater(channel); + } } } catch (CancelledKeyException e) { // setInterestOps() was called on a closed channel. ClosedChannelException cce = new ClosedChannelException(); future.setFailure(cce); - fireExceptionCaught(channel, cce); + if (iothread) { + fireExceptionCaught(channel, cce); + } else { + fireExceptionCaughtLater(channel, cce); + } } catch (Throwable t) { future.setFailure(t); - fireExceptionCaught(channel, t); + if (iothread) { + fireExceptionCaught(channel, t); + } else { + fireExceptionCaughtLater(channel, t); + } } } 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 fb7ddde380..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 @@ -16,7 +16,9 @@ package io.netty.channel.socket.nio; 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; @@ -126,15 +128,24 @@ class NioDatagramWorker extends AbstractNioWorker { 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); + 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); + } } } 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 index 5dd3e8b14d..97b20149a9 100644 --- a/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioWorker.java +++ b/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioWorker.java @@ -15,12 +15,7 @@ */ package io.netty.channel.socket.oio; -import static io.netty.channel.Channels.fireChannelClosed; -import static io.netty.channel.Channels.fireChannelDisconnected; -import static io.netty.channel.Channels.fireChannelInterestChanged; -import static io.netty.channel.Channels.fireChannelUnbound; -import static io.netty.channel.Channels.fireExceptionCaught; -import static io.netty.channel.Channels.succeededFuture; +import static io.netty.channel.Channels.*; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.Channels; @@ -89,6 +84,9 @@ abstract class AbstractOioWorker implements Worker close(channel, succeededFuture(channel)); } + static boolean isIoThead(AbstractOioChannel channel) { + return Thread.currentThread() == channel.workerThread; + } @Override public void executeInIoThread(Runnable eventRunnable) { @@ -120,7 +118,8 @@ abstract class AbstractOioWorker implements Worker static void setInterestOps( AbstractOioChannel channel, ChannelFuture future, int interestOps) { - + boolean iothread = isIoThead(channel); + // Override OP_WRITE flag - a user cannot change this flag. interestOps &= ~Channel.OP_WRITE; interestOps |= channel.getInterestOps() & Channel.OP_WRITE; @@ -148,18 +147,27 @@ abstract class AbstractOioWorker implements Worker workerThread.interrupt(); } } - - fireChannelInterestChanged(channel); + if (iothread) { + fireChannelInterestChanged(channel); + } else { + fireChannelInterestChangedLater(channel); + } } } catch (Throwable t) { future.setFailure(t); - fireExceptionCaught(channel, t); + if (iothread) { + fireExceptionCaught(channel, t); + } else { + fireExceptionCaughtLater(channel, t); + } } } static void close(AbstractOioChannel channel, ChannelFuture future) { boolean connected = channel.isConnected(); boolean bound = channel.isBound(); + boolean iothread = isIoThead(channel); + try { channel.closeSocket(); if (channel.setClosed()) { @@ -171,18 +179,34 @@ abstract class AbstractOioWorker implements Worker if (workerThread != null && currentThread != workerThread) { workerThread.interrupt(); } - fireChannelDisconnected(channel); + if (iothread) { + fireChannelDisconnected(channel); + } else { + fireChannelDisconnectedLater(channel); + } } if (bound) { - fireChannelUnbound(channel); + if (iothread) { + fireChannelUnbound(channel); + } else { + fireChannelUnboundLater(channel); + } + } + if (iothread) { + fireChannelClosed(channel); + } else { + fireChannelClosedLater(channel); } - fireChannelClosed(channel); } else { future.setSuccess(); } } catch (Throwable t) { future.setFailure(t); - fireExceptionCaught(channel, t); + if (iothread) { + fireExceptionCaught(channel, t); + } else { + fireExceptionCaughtLater(channel, t); + } } } } 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 f1b42b42f9..8ce169b90a 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 @@ -63,6 +63,8 @@ class OioDatagramWorker extends AbstractOioWorker { static void write( OioDatagramChannel channel, ChannelFuture future, Object message, SocketAddress remoteAddress) { + boolean iothread = isIoThead(channel); + try { ChannelBuffer buf = (ChannelBuffer) message; int offset = buf.readerIndex(); @@ -84,27 +86,45 @@ class OioDatagramWorker extends AbstractOioWorker { 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); + if (iothread) { + fireExceptionCaught(channel, t); + } else { + fireExceptionCaughtLater(channel, t); + } } } static void disconnect(OioDatagramChannel channel, ChannelFuture future) { boolean connected = channel.isConnected(); + boolean iothread = isIoThead(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); + } } } 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 bb0f7148d9..180d756d36 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 @@ -65,11 +65,16 @@ class OioWorker extends AbstractOioWorker { OioSocketChannel channel, ChannelFuture future, Object message) { + boolean iothread = isIoThead(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; } @@ -106,7 +111,11 @@ class OioWorker extends AbstractOioWorker { } } - fireWriteComplete(channel, length); + if (iothread) { + fireWriteComplete(channel, length); + } else { + fireWriteCompleteLater(channel, length); + } future.setSuccess(); } catch (Throwable t) { @@ -118,7 +127,11 @@ class OioWorker extends AbstractOioWorker { t = new ClosedChannelException(); } future.setFailure(t); - fireExceptionCaught(channel, t); + if (iothread) { + fireExceptionCaught(channel, t); + } else { + fireExceptionCaughtLater(channel, t); + } } } From f2d1f1e8ad00ad2c6f81b8c63ec1d37a0d9bdbb2 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Sat, 25 Feb 2012 15:54:33 +0100 Subject: [PATCH 17/66] Also fix the exception handling if a ChannelHandler throws an Exception based of if its a io thread or not. See #187 and #140 --- .../socket/http/AbstractHttpChannelSink.java | 30 ------------------- .../http/HttpTunnelAcceptedChannelSink.java | 3 +- .../http/HttpTunnelClientChannelSink.java | 3 +- .../http/HttpTunnelServerChannelSink.java | 3 +- .../channel/socket/http/FakeChannelSink.java | 3 +- .../netty/channel/rxtx/RxtxChannelSink.java | 8 ----- .../channel/sctp/AbstractScptChannelSink.java | 4 +-- .../io/netty/channel/AbstractChannelSink.java | 21 +++++++++++-- .../channel/iostream/IoStreamChannelSink.java | 8 ----- .../channel/local/LocalClientChannelSink.java | 8 ----- .../channel/local/LocalServerChannelSink.java | 8 ----- .../java/io/netty/channel/socket/Worker.java | 2 +- .../socket/nio/AbstractNioChannelSink.java | 14 +++++++-- .../channel/socket/nio/AbstractNioWorker.java | 2 +- .../socket/oio/AbstractOioChannelSink.java | 14 +++++++-- .../channel/socket/oio/AbstractOioWorker.java | 4 +-- 16 files changed, 57 insertions(+), 78 deletions(-) delete mode 100644 transport-http/src/main/java/io/netty/channel/socket/http/AbstractHttpChannelSink.java diff --git a/transport-http/src/main/java/io/netty/channel/socket/http/AbstractHttpChannelSink.java b/transport-http/src/main/java/io/netty/channel/socket/http/AbstractHttpChannelSink.java deleted file mode 100644 index e578a2dcfe..0000000000 --- a/transport-http/src/main/java/io/netty/channel/socket/http/AbstractHttpChannelSink.java +++ /dev/null @@ -1,30 +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.http; - -import io.netty.channel.AbstractChannelSink; -import io.netty.channel.ChannelEvent; -import io.netty.channel.ChannelPipeline; - -public abstract class AbstractHttpChannelSink extends AbstractChannelSink{ - - @Override - public void fireUpstreamEventLater(ChannelPipeline pipeline, ChannelEvent e) throws Exception { - pipeline.sendUpstream(e); - } - -} diff --git a/transport-http/src/main/java/io/netty/channel/socket/http/HttpTunnelAcceptedChannelSink.java b/transport-http/src/main/java/io/netty/channel/socket/http/HttpTunnelAcceptedChannelSink.java index ff0be867c6..591d20ac38 100644 --- a/transport-http/src/main/java/io/netty/channel/socket/http/HttpTunnelAcceptedChannelSink.java +++ b/transport-http/src/main/java/io/netty/channel/socket/http/HttpTunnelAcceptedChannelSink.java @@ -18,6 +18,7 @@ package io.netty.channel.socket.http; import java.util.concurrent.atomic.AtomicBoolean; import io.netty.buffer.ChannelBuffer; +import io.netty.channel.AbstractChannelSink; import io.netty.channel.Channel; import io.netty.channel.ChannelEvent; import io.netty.channel.ChannelFuture; @@ -32,7 +33,7 @@ import io.netty.channel.MessageEvent; * from here to the ServerMessageSwitch, which queues the data awaiting a poll request from the * client end of the tunnel. */ -class HttpTunnelAcceptedChannelSink extends AbstractHttpChannelSink { +class HttpTunnelAcceptedChannelSink extends AbstractChannelSink { final SaturationManager saturationManager; diff --git a/transport-http/src/main/java/io/netty/channel/socket/http/HttpTunnelClientChannelSink.java b/transport-http/src/main/java/io/netty/channel/socket/http/HttpTunnelClientChannelSink.java index 6a55acf7c1..5f1ecce1c5 100644 --- a/transport-http/src/main/java/io/netty/channel/socket/http/HttpTunnelClientChannelSink.java +++ b/transport-http/src/main/java/io/netty/channel/socket/http/HttpTunnelClientChannelSink.java @@ -17,6 +17,7 @@ package io.netty.channel.socket.http; import java.net.InetSocketAddress; +import io.netty.channel.AbstractChannelSink; import io.netty.channel.ChannelEvent; import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelStateEvent; @@ -26,7 +27,7 @@ import io.netty.channel.MessageEvent; * Sink of a client channel, deals with sunk events and then makes appropriate calls * on the channel itself to push data. */ -class HttpTunnelClientChannelSink extends AbstractHttpChannelSink { +class HttpTunnelClientChannelSink extends AbstractChannelSink { @Override public void eventSunk(ChannelPipeline pipeline, ChannelEvent e) diff --git a/transport-http/src/main/java/io/netty/channel/socket/http/HttpTunnelServerChannelSink.java b/transport-http/src/main/java/io/netty/channel/socket/http/HttpTunnelServerChannelSink.java index a7ed9ee1bb..e755229674 100644 --- a/transport-http/src/main/java/io/netty/channel/socket/http/HttpTunnelServerChannelSink.java +++ b/transport-http/src/main/java/io/netty/channel/socket/http/HttpTunnelServerChannelSink.java @@ -17,6 +17,7 @@ package io.netty.channel.socket.http; import java.net.SocketAddress; +import io.netty.channel.AbstractChannelSink; import io.netty.channel.ChannelEvent; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; @@ -26,7 +27,7 @@ import io.netty.channel.socket.ServerSocketChannel; /** */ -class HttpTunnelServerChannelSink extends AbstractHttpChannelSink { +class HttpTunnelServerChannelSink extends AbstractChannelSink { private ChannelFutureListener closeHook; diff --git a/transport-http/src/test/java/io/netty/channel/socket/http/FakeChannelSink.java b/transport-http/src/test/java/io/netty/channel/socket/http/FakeChannelSink.java index 0906755de0..0798eb94ad 100644 --- a/transport-http/src/test/java/io/netty/channel/socket/http/FakeChannelSink.java +++ b/transport-http/src/test/java/io/netty/channel/socket/http/FakeChannelSink.java @@ -19,13 +19,14 @@ package io.netty.channel.socket.http; import java.util.LinkedList; import java.util.Queue; +import io.netty.channel.AbstractChannelSink; import io.netty.channel.ChannelEvent; import io.netty.channel.ChannelPipeline; /** * A fake channel sink for use in testing */ -public class FakeChannelSink extends AbstractHttpChannelSink { +public class FakeChannelSink extends AbstractChannelSink { public Queue events = new LinkedList(); diff --git a/transport-rxtx/src/main/java/io/netty/channel/rxtx/RxtxChannelSink.java b/transport-rxtx/src/main/java/io/netty/channel/rxtx/RxtxChannelSink.java index 5009dc1427..fd1c34c0a0 100644 --- a/transport-rxtx/src/main/java/io/netty/channel/rxtx/RxtxChannelSink.java +++ b/transport-rxtx/src/main/java/io/netty/channel/rxtx/RxtxChannelSink.java @@ -329,12 +329,4 @@ public class RxtxChannelSink extends AbstractChannelSink { } } } - - /** - * Just fire the event now by calling {@link ChannelPipeline#sendUpstream(ChannelEvent)} as this implementation does not support it otherwise - */ - @Override - public void fireUpstreamEventLater(ChannelPipeline pipeline, ChannelEvent event) throws Exception { - pipeline.sendUpstream(event); - } } diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/AbstractScptChannelSink.java b/transport-sctp/src/main/java/io/netty/channel/sctp/AbstractScptChannelSink.java index 5daba78839..f7f458639c 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/AbstractScptChannelSink.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/AbstractScptChannelSink.java @@ -21,7 +21,7 @@ import io.netty.channel.Channel; import io.netty.channel.ChannelEvent; import io.netty.channel.ChannelPipeline; -public abstract class AbstractScptChannelSink extends AbstractChannelSink{ +public abstract class AbstractScptChannelSink extends AbstractChannelSink { @Override public void fireUpstreamEventLater(final ChannelPipeline pipeline, final ChannelEvent e) throws Exception { @@ -41,7 +41,7 @@ public abstract class AbstractScptChannelSink extends AbstractChannelSink{ pipeline.sendUpstream(e); } } else { - throw new UnsupportedOperationException(); + super.fireUpstreamEventLater(pipeline, e); } } diff --git a/transport/src/main/java/io/netty/channel/AbstractChannelSink.java b/transport/src/main/java/io/netty/channel/AbstractChannelSink.java index a1c27839ff..69b199aeb7 100644 --- a/transport/src/main/java/io/netty/channel/AbstractChannelSink.java +++ b/transport/src/main/java/io/netty/channel/AbstractChannelSink.java @@ -43,7 +43,24 @@ public abstract class AbstractChannelSink implements ChannelSink { if (actualCause == null) { actualCause = cause; } - - fireExceptionCaught(event.getChannel(), actualCause); + if (isFireExceptionCaughtLater(event, actualCause)) { + fireExceptionCaughtLater(event.getChannel(), actualCause); + } else { + fireExceptionCaught(event.getChannel(), actualCause); + } } + + protected boolean isFireExceptionCaughtLater(ChannelEvent event, Throwable actualCause) { + return false; + } + + /** + * This implementation just send the event now via {@link ChannelPipeline#sendUpstream(ChannelEvent)}. Sub-classes should override this if they can handle it + * in a better way + */ + @Override + public void fireUpstreamEventLater(ChannelPipeline pipeline, ChannelEvent e) throws Exception { + pipeline.sendUpstream(e); + } + } diff --git a/transport/src/main/java/io/netty/channel/iostream/IoStreamChannelSink.java b/transport/src/main/java/io/netty/channel/iostream/IoStreamChannelSink.java index 51de5d3311..a0d75ceef7 100755 --- a/transport/src/main/java/io/netty/channel/iostream/IoStreamChannelSink.java +++ b/transport/src/main/java/io/netty/channel/iostream/IoStreamChannelSink.java @@ -177,12 +177,4 @@ public class IoStreamChannelSink extends AbstractChannelSink { } } } - - /** - * This just calls {@link ChannelPipeline#sendUpstream(ChannelEvent)} as the transport does not support it - */ - @Override - public void fireUpstreamEventLater(ChannelPipeline pipeline, ChannelEvent e) throws Exception { - pipeline.sendUpstream(e); - } } diff --git a/transport/src/main/java/io/netty/channel/local/LocalClientChannelSink.java b/transport/src/main/java/io/netty/channel/local/LocalClientChannelSink.java index d882725de0..9b6bd455de 100644 --- a/transport/src/main/java/io/netty/channel/local/LocalClientChannelSink.java +++ b/transport/src/main/java/io/netty/channel/local/LocalClientChannelSink.java @@ -85,14 +85,6 @@ final class LocalClientChannelSink extends AbstractChannelSink { } } - /** - * Just fire the event now by calling {@link ChannelPipeline#sendUpstream(ChannelEvent)} as this implementation does not support it otherwise - */ - @Override - public void fireUpstreamEventLater(ChannelPipeline pipeline, ChannelEvent event) throws Exception { - pipeline.sendUpstream(event); - } - private void bind(DefaultLocalChannel channel, ChannelFuture future, LocalAddress localAddress) { try { if (!LocalChannelRegistry.register(localAddress, channel)) { diff --git a/transport/src/main/java/io/netty/channel/local/LocalServerChannelSink.java b/transport/src/main/java/io/netty/channel/local/LocalServerChannelSink.java index 6ead0ad3a2..6bfa780f28 100644 --- a/transport/src/main/java/io/netty/channel/local/LocalServerChannelSink.java +++ b/transport/src/main/java/io/netty/channel/local/LocalServerChannelSink.java @@ -42,14 +42,6 @@ final class LocalServerChannelSink extends AbstractChannelSink { } } - /** - * Just fire the event now by calling {@link ChannelPipeline#sendUpstream(ChannelEvent)} as this implementation does not support it otherwise - */ - @Override - public void fireUpstreamEventLater(ChannelPipeline pipeline, ChannelEvent event) throws Exception { - pipeline.sendUpstream(event); - } - private void handleServerChannel(ChannelEvent e) { if (!(e instanceof ChannelStateEvent)) { return; diff --git a/transport/src/main/java/io/netty/channel/socket/Worker.java b/transport/src/main/java/io/netty/channel/socket/Worker.java index eebfc74d40..64dc433038 100644 --- a/transport/src/main/java/io/netty/channel/socket/Worker.java +++ b/transport/src/main/java/io/netty/channel/socket/Worker.java @@ -20,7 +20,7 @@ package io.netty.channel.socket; * A {@link Worker} is responsible to dispatch IO operations * */ -public interface Worker extends Runnable{ +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. 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 index 85a8d03d3a..9ecdcb0699 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioChannelSink.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioChannelSink.java @@ -21,7 +21,7 @@ import io.netty.channel.Channel; import io.netty.channel.ChannelEvent; import io.netty.channel.ChannelPipeline; -public abstract class AbstractNioChannelSink extends AbstractChannelSink{ +public abstract class AbstractNioChannelSink extends AbstractChannelSink { @Override public void fireUpstreamEventLater(final ChannelPipeline pipeline, final ChannelEvent e) throws Exception { @@ -41,9 +41,19 @@ public abstract class AbstractNioChannelSink extends AbstractChannelSink{ pipeline.sendUpstream(e); } } else { - throw new UnsupportedOperationException(); + super.fireUpstreamEventLater(pipeline, e); } } + @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 index 14ffdc6d99..8f6dafd468 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java @@ -504,7 +504,7 @@ abstract class AbstractNioWorker implements Worker { } static boolean isIoThread(AbstractNioChannel channel) { - return Thread.currentThread() == channel.worker.thread; + return channel.worker.thread == null || Thread.currentThread() == channel.worker.thread; } private void setOpWrite(AbstractNioChannel channel) { 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 index 889a08aa26..633818ec01 100644 --- a/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioChannelSink.java +++ b/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioChannelSink.java @@ -22,7 +22,7 @@ import io.netty.channel.ChannelEvent; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.Worker; -public abstract class AbstractOioChannelSink extends AbstractChannelSink{ +public abstract class AbstractOioChannelSink extends AbstractChannelSink { @Override public void fireUpstreamEventLater(final ChannelPipeline pipeline, final ChannelEvent e) throws Exception { @@ -44,9 +44,19 @@ public abstract class AbstractOioChannelSink extends AbstractChannelSink{ } } else { - throw new UnsupportedOperationException(); + super.fireUpstreamEventLater(pipeline, e); } } + @Override + protected boolean isFireExceptionCaughtLater(ChannelEvent event, Throwable actualCause) { + Channel channel = event.getChannel(); + boolean fireLater = false; + if (channel instanceof AbstractOioChannel) { + fireLater = !AbstractOioWorker.isIoThead((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 index 97b20149a9..167b58309d 100644 --- a/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioWorker.java +++ b/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioWorker.java @@ -30,7 +30,7 @@ import java.util.Queue; * * @param {@link AbstractOioChannel} */ -abstract class AbstractOioWorker implements Worker{ +abstract class AbstractOioWorker implements Worker { private final Queue eventQueue = QueueFactory.createQueue(Runnable.class); @@ -85,7 +85,7 @@ abstract class AbstractOioWorker implements Worker } static boolean isIoThead(AbstractOioChannel channel) { - return Thread.currentThread() == channel.workerThread; + return channel.workerThread == null || Thread.currentThread() == channel.workerThread; } @Override From 68066c5e4b53df40a35d439a3f0b49d51a449738 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Sat, 25 Feb 2012 16:05:41 +0100 Subject: [PATCH 18/66] Make sure that ChannelDownstreamHandler impl fire exception caughts later via the io-worker. See #140 and #187 --- .../handler/stream/ChunkedWriteHandler.java | 30 ++++++++++++------- .../handler/timeout/WriteTimeoutHandler.java | 2 +- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/handler/src/main/java/io/netty/handler/stream/ChunkedWriteHandler.java b/handler/src/main/java/io/netty/handler/stream/ChunkedWriteHandler.java index c5387a33e9..6fc29eaebd 100644 --- a/handler/src/main/java/io/netty/handler/stream/ChunkedWriteHandler.java +++ b/handler/src/main/java/io/netty/handler/stream/ChunkedWriteHandler.java @@ -90,7 +90,7 @@ public class ChunkedWriteHandler implements ChannelUpstreamHandler, ChannelDowns } try { - flush(ctx); + flush(ctx, false); } catch (Exception e) { if (logger.isWarnEnabled()) { logger.warn("Unexpected exception while sending chunks.", e); @@ -112,10 +112,10 @@ public class ChunkedWriteHandler implements ChannelUpstreamHandler, ChannelDowns final Channel channel = ctx.getChannel(); if (channel.isWritable()) { this.ctx = ctx; - flush(ctx); + flush(ctx, false); } else if (!channel.isConnected()) { this.ctx = ctx; - discard(ctx); + discard(ctx, false); } } @@ -127,12 +127,12 @@ public class ChunkedWriteHandler implements ChannelUpstreamHandler, ChannelDowns switch (cse.getState()) { case INTEREST_OPS: // Continue writing when the channel becomes writable. - flush(ctx); + flush(ctx, true); break; case OPEN: if (!Boolean.TRUE.equals(cse.getValue())) { // Fail all pending writes - discard(ctx); + discard(ctx, true); } break; } @@ -140,7 +140,7 @@ public class ChunkedWriteHandler implements ChannelUpstreamHandler, ChannelDowns ctx.sendUpstream(e); } - private void discard(ChannelHandlerContext ctx) { + private void discard(ChannelHandlerContext ctx, boolean fireNow) { ClosedChannelException cause = null; boolean fireExceptionCaught = false; @@ -175,14 +175,18 @@ public class ChunkedWriteHandler implements ChannelUpstreamHandler, ChannelDowns if (fireExceptionCaught) { - Channels.fireExceptionCaught(ctx.getChannel(), cause); + if (fireNow) { + fireExceptionCaught(ctx.getChannel(), cause); + } else { + fireExceptionCaughtLater(ctx.getChannel(), cause); + } } } - private synchronized void flush(ChannelHandlerContext ctx) throws Exception { + private synchronized void flush(ChannelHandlerContext ctx, boolean fireNow) throws Exception { final Channel channel = ctx.getChannel(); if (!channel.isConnected()) { - discard(ctx); + discard(ctx, fireNow); } while (channel.isWritable()) { @@ -220,7 +224,11 @@ public class ChunkedWriteHandler implements ChannelUpstreamHandler, ChannelDowns this.currentEvent = null; currentEvent.getFuture().setFailure(t); - fireExceptionCaught(ctx, t); + if (fireNow) { + fireExceptionCaught(ctx, t); + } else { + fireExceptionCaughtLater(ctx.getChannel(), t); + } closeInput(chunks); break; @@ -262,7 +270,7 @@ public class ChunkedWriteHandler implements ChannelUpstreamHandler, ChannelDowns } if (!channel.isConnected()) { - discard(ctx); + discard(ctx, fireNow); break; } } diff --git a/handler/src/main/java/io/netty/handler/timeout/WriteTimeoutHandler.java b/handler/src/main/java/io/netty/handler/timeout/WriteTimeoutHandler.java index b5af55d670..5dd2b4e19c 100644 --- a/handler/src/main/java/io/netty/handler/timeout/WriteTimeoutHandler.java +++ b/handler/src/main/java/io/netty/handler/timeout/WriteTimeoutHandler.java @@ -154,7 +154,7 @@ public class WriteTimeoutHandler extends SimpleChannelDownstreamHandler } protected void writeTimedOut(ChannelHandlerContext ctx) throws Exception { - Channels.fireExceptionCaught(ctx, EXCEPTION); + Channels.fireExceptionCaughtLater(ctx.getChannel(), EXCEPTION); } private final class WriteTimeoutTask implements TimerTask { From cfe7b4959475f8690a5a0b3357230a6a539a916d Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Sat, 25 Feb 2012 17:11:14 +0100 Subject: [PATCH 19/66] Cleaner impl of AbstractNioChannelSink and AbstractOioChannelSink. See #140 and #187 --- .../netty/channel/socket/nio/AbstractNioChannelSink.java | 2 +- .../io/netty/channel/socket/nio/AbstractNioWorker.java | 2 +- .../netty/channel/socket/oio/AbstractOioChannelSink.java | 2 +- .../io/netty/channel/socket/oio/AbstractOioWorker.java | 9 ++++++--- 4 files changed, 9 insertions(+), 6 deletions(-) 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 index 9ecdcb0699..bf034eca3a 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioChannelSink.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioChannelSink.java @@ -29,7 +29,7 @@ public abstract class AbstractNioChannelSink extends AbstractChannelSink { if (ch instanceof AbstractNioChannel) { AbstractNioChannel channel = (AbstractNioChannel) ch; // check if the current thread is a worker thread if so we can send the event now - if (channel.worker.thread != Thread.currentThread()) { + if (!AbstractNioWorker.isIoThread(channel)) { channel.worker.executeInIoThread(new Runnable() { @Override 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 index 8f6dafd468..14ffdc6d99 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java @@ -504,7 +504,7 @@ abstract class AbstractNioWorker implements Worker { } static boolean isIoThread(AbstractNioChannel channel) { - return channel.worker.thread == null || Thread.currentThread() == channel.worker.thread; + return Thread.currentThread() == channel.worker.thread; } private void setOpWrite(AbstractNioChannel channel) { 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 index 633818ec01..485e5cb452 100644 --- a/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioChannelSink.java +++ b/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioChannelSink.java @@ -30,7 +30,7 @@ public abstract class AbstractOioChannelSink extends AbstractChannelSink { if (ch instanceof AbstractOioChannel) { AbstractOioChannel channel = (AbstractOioChannel) ch; Worker worker = channel.worker; - if (worker != null && channel.workerThread != Thread.currentThread()) { + if (worker != null && !AbstractOioWorker.isIoThead(channel)) { channel.worker.executeInIoThread(new Runnable() { @Override 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 index 167b58309d..ea773bc442 100644 --- a/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioWorker.java +++ b/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioWorker.java @@ -81,11 +81,11 @@ abstract class AbstractOioWorker implements Worker channel.workerThread = null; // Clean up. - close(channel, succeededFuture(channel)); + close(channel, succeededFuture(channel), true); } static boolean isIoThead(AbstractOioChannel channel) { - return channel.workerThread == null || Thread.currentThread() == channel.workerThread; + return Thread.currentThread() == channel.workerThread; } @Override @@ -164,9 +164,12 @@ abstract class AbstractOioWorker implements Worker } static void close(AbstractOioChannel channel, ChannelFuture future) { + close(channel, future, isIoThead(channel)); + } + + private static void close(AbstractOioChannel channel, ChannelFuture future, boolean iothread) { boolean connected = channel.isConnected(); boolean bound = channel.isBound(); - boolean iothread = isIoThead(channel); try { channel.closeSocket(); From 5e43e879f276d2f2e75d16c936063d932bdcf420 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Sun, 26 Feb 2012 11:00:30 +0100 Subject: [PATCH 20/66] Add OrderedDownstreamThreadPoolExecutor which can be used when using the new feature of ExecutionHandler to also handle downstream events. This is mainly useful for SEDA like stuff. See #173 --- .../OrderedDownstreamThreadPoolExecutor.java | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 handler/src/main/java/io/netty/handler/execution/OrderedDownstreamThreadPoolExecutor.java diff --git a/handler/src/main/java/io/netty/handler/execution/OrderedDownstreamThreadPoolExecutor.java b/handler/src/main/java/io/netty/handler/execution/OrderedDownstreamThreadPoolExecutor.java new file mode 100644 index 0000000000..b2df7192b8 --- /dev/null +++ b/handler/src/main/java/io/netty/handler/execution/OrderedDownstreamThreadPoolExecutor.java @@ -0,0 +1,157 @@ +/* + * 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.handler.execution; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelEvent; + +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; + +/** + * {@link Executor} which should be used for downstream {@link ChannelEvent}'s. This implementation will take care of preserve the order of the events in a {@link Channel}. + * If you don't need to preserve the order just use one of the {@link Executor} implementations provided by the static methods of {@link Executors}. + *
      + *
      + * + * For more informations about how the order is preserved see {@link OrderedMemoryAwareThreadPoolExecutor} + * + */ +public final class OrderedDownstreamThreadPoolExecutor extends OrderedMemoryAwareThreadPoolExecutor { + + /** + * Creates a new instance. + * + * @param corePoolSize the maximum number of active threads + */ + public OrderedDownstreamThreadPoolExecutor(int corePoolSize) { + super(corePoolSize, 0L, 0L); + } + + /** + * Creates a new instance. + * + * @param corePoolSize the maximum number of active threads + * @param keepAliveTime the amount of time for an inactive thread to shut itself down + * @param unit the {@link TimeUnit} of {@code keepAliveTime} + */ + public OrderedDownstreamThreadPoolExecutor( + int corePoolSize, long keepAliveTime, TimeUnit unit) { + super(corePoolSize, 0L, 0L, keepAliveTime, unit); + } + + /** + * Creates a new instance. + * + * @param corePoolSize the maximum number of active threads + * @param keepAliveTime the amount of time for an inactive thread to shut itself down + * @param unit the {@link TimeUnit} of {@code keepAliveTime} + * @param threadFactory the {@link ThreadFactory} of this pool + */ + public OrderedDownstreamThreadPoolExecutor( + int corePoolSize, long keepAliveTime, TimeUnit unit, ThreadFactory threadFactory) { + super(corePoolSize, 0L, 0L, + keepAliveTime, unit, threadFactory); + } + + /** + * Creates a new instance. + * + * @param corePoolSize the maximum number of active threads + * @param keepAliveTime the amount of time for an inactive thread to shut itself down + * @param unit the {@link TimeUnit} of {@code keepAliveTime} + * @param threadFactory the {@link ThreadFactory} of this pool + * @param objectSizeEstimator the {@link ObjectSizeEstimator} of this pool + */ + public OrderedDownstreamThreadPoolExecutor( + int corePoolSize, + long keepAliveTime, TimeUnit unit, + ObjectSizeEstimator objectSizeEstimator, ThreadFactory threadFactory) { + super(corePoolSize, 0L, 0L, + keepAliveTime, unit, objectSizeEstimator, threadFactory); + } + + + /** + * Return null + */ + @Override + public ObjectSizeEstimator getObjectSizeEstimator() { + return null; + } + + /** + * Throws {@link UnsupportedOperationException} as there is not support for limit the memory size in this implementation + */ + @Override + public void setObjectSizeEstimator(ObjectSizeEstimator objectSizeEstimator) { + throw new UnsupportedOperationException("Not supported by this implementation"); + } + + /** + * Returns 0L + */ + @Override + public long getMaxChannelMemorySize() { + return 0L; + } + + /** + * Throws {@link UnsupportedOperationException} as there is not support for limit the memory size in this implementation + */ + @Override + public void setMaxChannelMemorySize(long maxChannelMemorySize) { + throw new UnsupportedOperationException("Not supported by this implementation"); + } + + /** + * Returns 0L + */ + @Override + public long getMaxTotalMemorySize() { + return 0L; + } + + /** + * Throws {@link UnsupportedOperationException} as there is not support for limit the memory size in this implementation + */ + @Override + public void setMaxTotalMemorySize(long maxTotalMemorySize) { + throw new UnsupportedOperationException("Not supported by this implementation"); + } + + /** + * Return false as we not need to cound the memory in this implementation + */ + @Override + protected boolean shouldCount(Runnable task) { + return false; + } + + @Override + public void execute(Runnable command) { + + // check if the Runnable was of an unsupported type + if (command instanceof ChannelUpstreamEventRunnable) { + throw new RejectedExecutionException("command must be enclosed with an downstream event."); + } + doExecute(command); + } + +} From 16fada5c233fa7f5e6d534f960d55eb19a244e0c Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Sun, 26 Feb 2012 11:06:14 +0100 Subject: [PATCH 21/66] Remove bogus constructor. See #173 --- .../OrderedDownstreamThreadPoolExecutor.java | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/handler/src/main/java/io/netty/handler/execution/OrderedDownstreamThreadPoolExecutor.java b/handler/src/main/java/io/netty/handler/execution/OrderedDownstreamThreadPoolExecutor.java index b2df7192b8..452af75506 100644 --- a/handler/src/main/java/io/netty/handler/execution/OrderedDownstreamThreadPoolExecutor.java +++ b/handler/src/main/java/io/netty/handler/execution/OrderedDownstreamThreadPoolExecutor.java @@ -70,24 +70,6 @@ public final class OrderedDownstreamThreadPoolExecutor extends OrderedMemoryAwar keepAliveTime, unit, threadFactory); } - /** - * Creates a new instance. - * - * @param corePoolSize the maximum number of active threads - * @param keepAliveTime the amount of time for an inactive thread to shut itself down - * @param unit the {@link TimeUnit} of {@code keepAliveTime} - * @param threadFactory the {@link ThreadFactory} of this pool - * @param objectSizeEstimator the {@link ObjectSizeEstimator} of this pool - */ - public OrderedDownstreamThreadPoolExecutor( - int corePoolSize, - long keepAliveTime, TimeUnit unit, - ObjectSizeEstimator objectSizeEstimator, ThreadFactory threadFactory) { - super(corePoolSize, 0L, 0L, - keepAliveTime, unit, objectSizeEstimator, threadFactory); - } - - /** * Return null */ From d0a962422ea0a620c41bb2d3971fc9ecccdc26b3 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Sun, 26 Feb 2012 18:14:05 +0100 Subject: [PATCH 22/66] Add back missing HttpTunnelingServlet. See #148 --- transport-http/pom.xml | 8 + .../socket/http/HttpTunnelingServlet.java | 240 ++++++++++++++++++ 2 files changed, 248 insertions(+) create mode 100644 transport-http/src/main/java/io/netty/channel/socket/http/HttpTunnelingServlet.java diff --git a/transport-http/pom.xml b/transport-http/pom.xml index 258604e89d..eb28818dd9 100644 --- a/transport-http/pom.xml +++ b/transport-http/pom.xml @@ -35,6 +35,14 @@ netty-codec-http ${project.version} + + + + + javax.servlet + servlet-api + true + diff --git a/transport-http/src/main/java/io/netty/channel/socket/http/HttpTunnelingServlet.java b/transport-http/src/main/java/io/netty/channel/socket/http/HttpTunnelingServlet.java new file mode 100644 index 0000000000..9cbca58bc8 --- /dev/null +++ b/transport-http/src/main/java/io/netty/channel/socket/http/HttpTunnelingServlet.java @@ -0,0 +1,240 @@ +/* + * 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.http; + +import java.io.EOFException; +import java.io.IOException; +import java.io.PushbackInputStream; +import java.net.SocketAddress; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import io.netty.buffer.ChannelBuffer; +import io.netty.buffer.ChannelBuffers; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFactory; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.Channels; +import io.netty.channel.ExceptionEvent; +import io.netty.channel.MessageEvent; +import io.netty.channel.SimpleChannelUpstreamHandler; +import io.netty.channel.local.DefaultLocalClientChannelFactory; +import io.netty.channel.local.LocalAddress; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; + +/** + * An {@link HttpServlet} that proxies an incoming data to the actual server + * and vice versa. Please refer to the + * package summary for + * the detailed usage. + * @apiviz.landmark + */ +public class HttpTunnelingServlet extends HttpServlet { + + private static final long serialVersionUID = 4259910275899756070L; + + private static final String ENDPOINT = "endpoint"; + + static final InternalLogger logger = InternalLoggerFactory.getInstance(HttpTunnelingServlet.class); + + private volatile SocketAddress remoteAddress; + private volatile ChannelFactory channelFactory; + + @Override + public void init() throws ServletException { + ServletConfig config = getServletConfig(); + String endpoint = config.getInitParameter(ENDPOINT); + if (endpoint == null) { + throw new ServletException("init-param '" + ENDPOINT + "' must be specified."); + } + + try { + remoteAddress = parseEndpoint(endpoint.trim()); + } catch (ServletException e) { + throw e; + } catch (Exception e) { + throw new ServletException("Failed to parse an endpoint.", e); + } + + try { + channelFactory = createChannelFactory(remoteAddress); + } catch (ServletException e) { + throw e; + } catch (Exception e) { + throw new ServletException("Failed to create a channel factory.", e); + } + + // Stuff for testing purpose + //ServerBootstrap b = new ServerBootstrap(new DefaultLocalServerChannelFactory()); + //b.getPipeline().addLast("logger", new LoggingHandler(getClass(), InternalLogLevel.INFO, true)); + //b.getPipeline().addLast("handler", new EchoHandler()); + //b.bind(remoteAddress); + } + + protected SocketAddress parseEndpoint(String endpoint) throws Exception { + if (endpoint.startsWith("local:")) { + return new LocalAddress(endpoint.substring(6).trim()); + } else { + throw new ServletException( + "Invalid or unknown endpoint: " + endpoint); + } + } + + protected ChannelFactory createChannelFactory(SocketAddress remoteAddress) throws Exception { + if (remoteAddress instanceof LocalAddress) { + return new DefaultLocalClientChannelFactory(); + } else { + throw new ServletException( + "Unsupported remote address type: " + + remoteAddress.getClass().getName()); + } + } + + @Override + public void destroy() { + try { + destroyChannelFactory(channelFactory); + } catch (Exception e) { + logger.warn("Failed to destroy a channel factory.", e); + } + } + + protected void destroyChannelFactory(ChannelFactory factory) throws Exception { + factory.releaseExternalResources(); + } + + @Override + protected void service(HttpServletRequest req, HttpServletResponse res) + throws ServletException, IOException { + if (!"POST".equalsIgnoreCase(req.getMethod())) { + logger.warn("Unallowed method: " + req.getMethod()); + res.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); + return; + } + + final ChannelPipeline pipeline = Channels.pipeline(); + final ServletOutputStream out = res.getOutputStream(); + final OutboundConnectionHandler handler = new OutboundConnectionHandler(out); + pipeline.addLast("handler", handler); + + Channel channel = channelFactory.newChannel(pipeline); + ChannelFuture future = channel.connect(remoteAddress).awaitUninterruptibly(); + if (!future.isSuccess()) { + Throwable cause = future.getCause(); + logger.warn("Endpoint unavailable: " + cause.getMessage(), cause); + res.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE); + return; + } + + ChannelFuture lastWriteFuture = null; + try { + res.setStatus(HttpServletResponse.SC_OK); + res.setHeader(HttpHeaders.Names.CONTENT_TYPE, "application/octet-stream"); + res.setHeader(HttpHeaders.Names.CONTENT_TRANSFER_ENCODING, HttpHeaders.Values.BINARY); + + // Initiate chunked encoding by flushing the headers. + out.flush(); + + PushbackInputStream in = + new PushbackInputStream(req.getInputStream()); + while (channel.isConnected()) { + ChannelBuffer buffer; + try { + buffer = read(in); + } catch (EOFException e) { + break; + } + if (buffer == null) { + break; + } + lastWriteFuture = channel.write(buffer); + } + } finally { + if (lastWriteFuture == null) { + channel.close(); + } else { + lastWriteFuture.addListener(ChannelFutureListener.CLOSE); + } + } + } + + private static ChannelBuffer read(PushbackInputStream in) throws IOException { + byte[] buf; + int readBytes; + + int bytesToRead = in.available(); + if (bytesToRead > 0) { + buf = new byte[bytesToRead]; + readBytes = in.read(buf); + } else if (bytesToRead == 0) { + int b = in.read(); + if (b < 0 || in.available() < 0) { + return null; + } + in.unread(b); + bytesToRead = in.available(); + buf = new byte[bytesToRead]; + readBytes = in.read(buf); + } else { + return null; + } + + assert readBytes > 0; + + ChannelBuffer buffer; + if (readBytes == buf.length) { + buffer = ChannelBuffers.wrappedBuffer(buf); + } else { + // A rare case, but it sometimes happen. + buffer = ChannelBuffers.wrappedBuffer(buf, 0, readBytes); + } + return buffer; + } + + private static final class OutboundConnectionHandler extends SimpleChannelUpstreamHandler { + + private final ServletOutputStream out; + + public OutboundConnectionHandler(ServletOutputStream out) { + this.out = out; + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { + ChannelBuffer buffer = (ChannelBuffer) e.getMessage(); + synchronized (this) { + buffer.readBytes(out, buffer.readableBytes()); + out.flush(); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { + logger.warn("Unexpected exception while HTTP tunneling", e.getCause()); + e.getChannel().close(); + } + } +} From 03cb43140c70287e6b454d0d6561e0699eddfefb Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Sun, 26 Feb 2012 18:15:42 +0100 Subject: [PATCH 23/66] Only log if loglevel is enabled. --- .../socket/http/HttpTunnelingServlet.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/transport-http/src/main/java/io/netty/channel/socket/http/HttpTunnelingServlet.java b/transport-http/src/main/java/io/netty/channel/socket/http/HttpTunnelingServlet.java index 9cbca58bc8..2bf840bc48 100644 --- a/transport-http/src/main/java/io/netty/channel/socket/http/HttpTunnelingServlet.java +++ b/transport-http/src/main/java/io/netty/channel/socket/http/HttpTunnelingServlet.java @@ -118,7 +118,9 @@ public class HttpTunnelingServlet extends HttpServlet { try { destroyChannelFactory(channelFactory); } catch (Exception e) { - logger.warn("Failed to destroy a channel factory.", e); + if (logger.isWarnEnabled()) { + logger.warn("Failed to destroy a channel factory.", e); + } } } @@ -130,7 +132,9 @@ public class HttpTunnelingServlet extends HttpServlet { protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { if (!"POST".equalsIgnoreCase(req.getMethod())) { - logger.warn("Unallowed method: " + req.getMethod()); + if (logger.isWarnEnabled()) { + logger.warn("Unallowed method: " + req.getMethod()); + } res.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); return; } @@ -144,7 +148,9 @@ public class HttpTunnelingServlet extends HttpServlet { ChannelFuture future = channel.connect(remoteAddress).awaitUninterruptibly(); if (!future.isSuccess()) { Throwable cause = future.getCause(); - logger.warn("Endpoint unavailable: " + cause.getMessage(), cause); + if (logger.isWarnEnabled()) { + logger.warn("Endpoint unavailable: " + cause.getMessage(), cause); + } res.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE); return; } @@ -233,7 +239,9 @@ public class HttpTunnelingServlet extends HttpServlet { @Override public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { - logger.warn("Unexpected exception while HTTP tunneling", e.getCause()); + if (logger.isWarnEnabled()) { + logger.warn("Unexpected exception while HTTP tunneling", e.getCause()); + } e.getChannel().close(); } } From 2b9df060dd916abb7774b65ce43e2d60fde0052e Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Sun, 26 Feb 2012 20:51:53 +0100 Subject: [PATCH 24/66] Add support to wrap primitives via ChannelBuffers.wrap*(..) easily. See #167 --- .../java/io/netty/buffer/ChannelBuffers.java | 75 +++++++++++++++++++ .../io/netty/buffer/ChannelBuffersTest.java | 66 ++++++++++++++++ 2 files changed, 141 insertions(+) diff --git a/buffer/src/main/java/io/netty/buffer/ChannelBuffers.java b/buffer/src/main/java/io/netty/buffer/ChannelBuffers.java index 5f540b0a8f..299114539f 100644 --- a/buffer/src/main/java/io/netty/buffer/ChannelBuffers.java +++ b/buffer/src/main/java/io/netty/buffer/ChannelBuffers.java @@ -821,6 +821,81 @@ public final class ChannelBuffers { return new ReadOnlyChannelBuffer(buffer); } + /** + * Create a {@link ChannelBuffer} that holds all the given values as int's + * + */ + public static ChannelBuffer wrapInt(int... values) { + if (values == null || values.length == 0) { + return EMPTY_BUFFER; + } + ChannelBuffer buffer = buffer(values.length * 4); + for (int v: values) { + buffer.writeInt(v); + } + return buffer; + } + + /** + * Create a {@link ChannelBuffer} that holds all the given values as short's + * + */ + public static ChannelBuffer wrapShort(int... values) { + if (values == null || values.length == 0) { + return EMPTY_BUFFER; + } + ChannelBuffer buffer = buffer(values.length * 2); + for (int v: values) { + buffer.writeShort(v); + } + return buffer; + } + + /** + * Create a {@link ChannelBuffer} that holds all the given values as medium's + * + */ + public static ChannelBuffer wrapMedium(int... values) { + if (values == null || values.length == 0) { + return EMPTY_BUFFER; + } + ChannelBuffer buffer = buffer(values.length * 3); + for (int v: values) { + buffer.writeMedium(v); + } + return buffer; + } + + /** + * Create a {@link ChannelBuffer} that holds all the given values as long's + * + */ + public static ChannelBuffer wrapLong(long... values) { + if (values == null || values.length == 0) { + return EMPTY_BUFFER; + } + ChannelBuffer buffer = buffer(values.length * 8); + for (long v: values) { + buffer.writeLong(v); + } + return buffer; + } + + /** + * Create a {@link ChannelBuffer} that holds all the given values as boolean's + * + */ + public static ChannelBuffer wrapBoolean(boolean... values) { + if (values == null || values.length == 0) { + return EMPTY_BUFFER; + } + ChannelBuffer buffer = buffer(values.length); + for (boolean v: values) { + buffer.writeBoolean(v); + } + return buffer; + } + /** * Returns a hex dump * of the specified buffer's readable bytes. diff --git a/buffer/src/test/java/io/netty/buffer/ChannelBuffersTest.java b/buffer/src/test/java/io/netty/buffer/ChannelBuffersTest.java index b0b4280c66..0d0511ff56 100644 --- a/buffer/src/test/java/io/netty/buffer/ChannelBuffersTest.java +++ b/buffer/src/test/java/io/netty/buffer/ChannelBuffersTest.java @@ -425,4 +425,70 @@ public class ChannelBuffersTest { // Expected } } + + @Test + public void testWrapInt() { + ChannelBuffer buffer = ChannelBuffers.wrapInt(1,4); + assertEquals(8, buffer.capacity()); + assertEquals(1, buffer.readInt()); + assertEquals(4, buffer.readInt()); + assertFalse(buffer.readable()); + + assertEquals(0, ChannelBuffers.wrapInt(null).capacity()); + assertEquals(0, ChannelBuffers.wrapInt(new int[0]).capacity()); + + } + + @Test + public void testWrapShort() { + ChannelBuffer buffer = ChannelBuffers.wrapShort(1,4); + assertEquals(4, buffer.capacity()); + assertEquals(1, buffer.readShort()); + assertEquals(4, buffer.readShort()); + assertFalse(buffer.readable()); + + assertEquals(0, ChannelBuffers.wrapShort(null).capacity()); + assertEquals(0, ChannelBuffers.wrapShort(new int[0]).capacity()); + + } + + @Test + public void testWrapMedium() { + ChannelBuffer buffer = ChannelBuffers.wrapMedium(1,4); + assertEquals(6, buffer.capacity()); + assertEquals(1, buffer.readMedium()); + assertEquals(4, buffer.readMedium()); + assertFalse(buffer.readable()); + + assertEquals(0, ChannelBuffers.wrapMedium(null).capacity()); + assertEquals(0, ChannelBuffers.wrapMedium(new int[0]).capacity()); + + } + + + @Test + public void testWrapLong() { + ChannelBuffer buffer = ChannelBuffers.wrapLong(1,4); + assertEquals(16, buffer.capacity()); + assertEquals(1, buffer.readLong()); + assertEquals(4, buffer.readLong()); + assertFalse(buffer.readable()); + + assertEquals(0, ChannelBuffers.wrapLong(null).capacity()); + assertEquals(0, ChannelBuffers.wrapLong(new long[0]).capacity()); + + } + + @Test + public void testWrapBoolean() { + ChannelBuffer buffer = ChannelBuffers.wrapBoolean(true, false); + assertEquals(2, buffer.capacity()); + assertEquals(true, buffer.readBoolean()); + assertEquals(false, buffer.readBoolean()); + assertFalse(buffer.readable()); + + assertEquals(0, ChannelBuffers.wrapBoolean(null).capacity()); + assertEquals(0, ChannelBuffers.wrapBoolean(new boolean[0]).capacity()); + + } } From 0beaa107b4be5e68928349c4131d01a655003641 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Mon, 27 Feb 2012 20:45:46 +0100 Subject: [PATCH 25/66] Fix assert usage. Thanks Trustin for review --- .../java/io/netty/channel/socket/nio/AbstractNioWorker.java | 4 +++- .../java/io/netty/channel/socket/oio/AbstractOioWorker.java | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) 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 index 14ffdc6d99..a5a109d9c1 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/AbstractNioWorker.java @@ -273,7 +273,9 @@ abstract class AbstractNioWorker implements Worker { @Override public void executeInIoThread(Runnable eventRunnable) { - assert eventQueue.offer(eventRunnable); + boolean added = eventQueue.offer(eventRunnable); + + assert added; // wake up the selector to speed things selector.wakeup(); 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 index ea773bc442..fb1b403894 100644 --- a/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioWorker.java +++ b/transport/src/main/java/io/netty/channel/socket/oio/AbstractOioWorker.java @@ -90,8 +90,9 @@ abstract class AbstractOioWorker implements Worker @Override public void executeInIoThread(Runnable eventRunnable) { - assert eventQueue.offer(eventRunnable); + boolean added = eventQueue.offer(eventRunnable); + assert added; // as we set the SO_TIMEOUT to 1 second this task will get picked up in 1 second at latest } From b6700fbe58894c59f1caae1fe97cf962ac066016 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Mon, 27 Feb 2012 20:46:40 +0100 Subject: [PATCH 26/66] Fix naming of class. Thanks Trustin for review --- ...bstractScptChannelSink.java => AbstractSctpChannelSink.java} | 2 +- .../main/java/io/netty/channel/sctp/SctpClientPipelineSink.java | 2 +- .../main/java/io/netty/channel/sctp/SctpServerPipelineSink.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename transport-sctp/src/main/java/io/netty/channel/sctp/{AbstractScptChannelSink.java => AbstractSctpChannelSink.java} (93%) diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/AbstractScptChannelSink.java b/transport-sctp/src/main/java/io/netty/channel/sctp/AbstractSctpChannelSink.java similarity index 93% rename from transport-sctp/src/main/java/io/netty/channel/sctp/AbstractScptChannelSink.java rename to transport-sctp/src/main/java/io/netty/channel/sctp/AbstractSctpChannelSink.java index f7f458639c..fcc0c9f1aa 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/AbstractScptChannelSink.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/AbstractSctpChannelSink.java @@ -21,7 +21,7 @@ import io.netty.channel.Channel; import io.netty.channel.ChannelEvent; import io.netty.channel.ChannelPipeline; -public abstract class AbstractScptChannelSink extends AbstractChannelSink { +public abstract class AbstractSctpChannelSink extends AbstractChannelSink { @Override public void fireUpstreamEventLater(final ChannelPipeline pipeline, final ChannelEvent e) throws Exception { diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpClientPipelineSink.java b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpClientPipelineSink.java index bd65b89e57..7005422e80 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpClientPipelineSink.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpClientPipelineSink.java @@ -47,7 +47,7 @@ import io.netty.util.internal.QueueFactory; /** */ -class SctpClientPipelineSink extends AbstractScptChannelSink { +class SctpClientPipelineSink extends AbstractSctpChannelSink { static final InternalLogger logger = InternalLoggerFactory.getInstance(SctpClientPipelineSink.class); diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpServerPipelineSink.java b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpServerPipelineSink.java index ce34643315..c4001af990 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpServerPipelineSink.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpServerPipelineSink.java @@ -44,7 +44,7 @@ import io.netty.util.internal.DeadLockProofWorker; /** */ -class SctpServerPipelineSink extends AbstractScptChannelSink { +class SctpServerPipelineSink extends AbstractSctpChannelSink { static final InternalLogger logger = InternalLoggerFactory.getInstance(SctpServerPipelineSink.class); From 46125686874f5b69bcdb6f4c9bef3f900a7057c6 Mon Sep 17 00:00:00 2001 From: Trustin Lee Date: Mon, 27 Feb 2012 12:56:18 -0800 Subject: [PATCH 27/66] Fix #204 - Increate the granularity of connect timeout in NIO * Changed the Selector timeout from 500 to 10 so that the timeout is checked every 10 milliseconds --- .../java/io/netty/channel/sctp/SctpClientPipelineSink.java | 6 +++--- .../channel/socket/nio/NioClientSocketPipelineSink.java | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpClientPipelineSink.java b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpClientPipelineSink.java index 363407f7d5..b72c4f5cb6 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpClientPipelineSink.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpClientPipelineSink.java @@ -262,7 +262,7 @@ class SctpClientPipelineSink 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 @@ -302,9 +302,9 @@ class SctpClientPipelineSink 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); } 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 ca2333659f..39b2a73ed4 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 @@ -242,7 +242,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 +282,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); } From 2984f26f978bc6fd74bb3c31a7e66b034798017a Mon Sep 17 00:00:00 2001 From: Trustin Lee Date: Mon, 27 Feb 2012 13:02:42 -0800 Subject: [PATCH 28/66] Decreased all selector timeout from 500 ms to 10 ms See #204 --- .../src/main/java/io/netty/channel/socket/nio/SelectorUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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..049559c377 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 @@ -30,7 +30,7 @@ final class SelectorUtil { static void select(Selector selector) throws IOException { try { - selector.select(500); + selector.select(10); } catch (CancelledKeyException e) { if (logger.isDebugEnabled()) { logger.debug( From 19358ee2469a815d6e93aea2bd6ce6fc1d7a8b76 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Tue, 28 Feb 2012 14:19:29 +0100 Subject: [PATCH 29/66] Workaround for JDK NIO bug. See #203 --- .../netty/channel/socket/nio/SelectorUtil.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) 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..2e7f501ed1 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,6 +28,24 @@ 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); From 40771f6faf30ddb7e6c6776338b7bb6d3fae3d62 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Tue, 28 Feb 2012 14:21:47 +0100 Subject: [PATCH 30/66] add empty line --- .../src/main/java/io/netty/channel/socket/nio/SelectorUtil.java | 1 + 1 file changed, 1 insertion(+) 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 2e7f501ed1..2ba8fee35d 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 @@ -46,6 +46,7 @@ final class SelectorUtil { } } } + static void select(Selector selector) throws IOException { try { selector.select(500); From 2f6d02da6039039c3d49ff98d50f67f524dd8d7b Mon Sep 17 00:00:00 2001 From: Trustin Lee Date: Tue, 28 Feb 2012 10:38:45 -0800 Subject: [PATCH 31/66] Run tests in random order --- pom.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pom.xml b/pom.xml index d704137bb5..d4daaae9f8 100644 --- a/pom.xml +++ b/pom.xml @@ -264,6 +264,18 @@ + + maven-surefire-plugin + 2.12 + + never + + **/Abstract* + **/TestUtil* + + random + + - - [1.6.0,) + + + [1.7.0,) [3.0.2,) @@ -237,6 +237,32 @@ true + + + org.codehaus.mojo + animal-sniffer-maven-plugin + 1.7 + + + org.codehaus.mojo.signature + java16 + 1.0 + + + sun.misc.Unsafe + java.util.zip.Deflater + + + + + process-classes + + check + + + + maven-checkstyle-plugin 2.8 diff --git a/src/main/java/org/jboss/netty/handler/codec/spdy/SpdyZlibEncoder.java b/src/main/java/org/jboss/netty/handler/codec/spdy/SpdyZlibEncoder.java deleted file mode 100644 index 4b78a49ca5..0000000000 --- a/src/main/java/org/jboss/netty/handler/codec/spdy/SpdyZlibEncoder.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2012 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. - */ -/* - * Copyright 2012 Twitter, Inc. - * - * Licensed 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 org.jboss.netty.handler.codec.spdy; - -import java.util.zip.Deflater; - -import org.jboss.netty.buffer.ChannelBuffer; - -import static org.jboss.netty.handler.codec.spdy.SpdyCodecUtil.*; - -class SpdyZlibEncoder { - - private final byte[] out = new byte[8192]; - private final Deflater compressor; - - public SpdyZlibEncoder(int compressionLevel) { - if (compressionLevel < 0 || compressionLevel > 9) { - throw new IllegalArgumentException( - "compressionLevel: " + compressionLevel + " (expected: 0-9)"); - } - compressor = new Deflater(compressionLevel); - compressor.setDictionary(SPDY_DICT); - } - - public void setInput(ChannelBuffer decompressed) { - byte[] in = new byte[decompressed.readableBytes()]; - decompressed.readBytes(in); - compressor.setInput(in); - } - - public void encode(ChannelBuffer compressed) { - while (!compressor.needsInput()) { - int numBytes = compressor.deflate(out, 0, out.length, Deflater.SYNC_FLUSH); - compressed.writeBytes(out, 0, numBytes); - } - } - - public void end() { - compressor.end(); - } -} From 469cb8b80be6a44ea66b723b7ded0cca327aef81 Mon Sep 17 00:00:00 2001 From: norman Date: Fri, 2 Mar 2012 07:37:43 +0100 Subject: [PATCH 52/66] Remove Twitter license. See #202 --- .../netty/handler/codec/spdy/SpdyZlibDecoder.java | 15 --------------- .../netty/handler/codec/spdy/SpdyZlibEncoder.java | 15 --------------- 2 files changed, 30 deletions(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyZlibDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyZlibDecoder.java index 33f2ed4b84..7ae7a260b1 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyZlibDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyZlibDecoder.java @@ -13,21 +13,6 @@ * License for the specific language governing permissions and limitations * under the License. */ -/* - * Copyright 2012 Twitter, Inc. - * - * Licensed 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.handler.codec.spdy; import static io.netty.handler.codec.spdy.SpdyCodecUtil.*; diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyZlibEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyZlibEncoder.java index b455a64531..a57b9b7bf0 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyZlibEncoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyZlibEncoder.java @@ -13,21 +13,6 @@ * License for the specific language governing permissions and limitations * under the License. */ -/* - * Copyright 2012 Twitter, Inc. - * - * Licensed 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.handler.codec.spdy; import java.util.zip.Deflater; From 985fc448dc52b0878acdce45edbc8043a95707d4 Mon Sep 17 00:00:00 2001 From: norman Date: Fri, 2 Mar 2012 07:38:05 +0100 Subject: [PATCH 53/66] Remove unused imports --- .../main/java/io/netty/handler/codec/spdy/SpdyFrameDecoder.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameDecoder.java index d202f19bbf..5d0c5140e9 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameDecoder.java @@ -19,8 +19,6 @@ import io.netty.buffer.ChannelBuffer; import io.netty.buffer.ChannelBuffers; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.compression.ZlibDecoder; -import io.netty.handler.codec.embedder.DecoderEmbedder; import io.netty.handler.codec.frame.FrameDecoder; import static io.netty.handler.codec.spdy.SpdyCodecUtil.*; From 8eec6938834c38c9dd51a43df3adc2b029872d3f Mon Sep 17 00:00:00 2001 From: norman Date: Fri, 2 Mar 2012 07:46:56 +0100 Subject: [PATCH 54/66] Remove twitter license headers. See #202 --- .../handler/codec/spdy/SpdyJZlibDecoder.java | 15 --------------- .../handler/codec/spdy/SpdyJZlibEncoder.java | 15 --------------- 2 files changed, 30 deletions(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyJZlibDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyJZlibDecoder.java index 38bd2aaee0..30063d5285 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyJZlibDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyJZlibDecoder.java @@ -13,21 +13,6 @@ * License for the specific language governing permissions and limitations * under the License. */ -/* - * Copyright 2012 Twitter, Inc. - * - * Licensed 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.handler.codec.spdy; import io.netty.buffer.ChannelBuffer; diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyJZlibEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyJZlibEncoder.java index b3ea627c1f..c76d08b13b 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyJZlibEncoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyJZlibEncoder.java @@ -13,21 +13,6 @@ * License for the specific language governing permissions and limitations * under the License. */ -/* - * Copyright 2012 Twitter, Inc. - * - * Licensed 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.handler.codec.spdy; import static io.netty.handler.codec.spdy.SpdyCodecUtil.*; From 529156028326efe51338990bce57666895f31a1b Mon Sep 17 00:00:00 2001 From: norman Date: Fri, 2 Mar 2012 08:03:20 +0100 Subject: [PATCH 55/66] Remove left-over of refactoring. See #211 --- .../handler/codec/spdy/SpdyZlibDecoder.java | 65 ------------------- 1 file changed, 65 deletions(-) delete mode 100644 codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyZlibDecoder.java diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyZlibDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyZlibDecoder.java deleted file mode 100644 index 7ae7a260b1..0000000000 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyZlibDecoder.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2012 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.handler.codec.spdy; - -import static io.netty.handler.codec.spdy.SpdyCodecUtil.*; - -import io.netty.buffer.ChannelBuffer; -import io.netty.handler.codec.compression.CompressionException; -import io.netty.util.internal.jzlib.JZlib; -import io.netty.util.internal.jzlib.ZStream; - -class SpdyZlibDecoder { - - private final byte[] out = new byte[8192]; - private final ZStream z = new ZStream(); - - public SpdyZlibDecoder() { - int resultCode; - resultCode = z.inflateInit(JZlib.W_ZLIB); - if (resultCode != JZlib.Z_OK) { - throw new CompressionException("ZStream initialization failure"); - } - z.next_out = out; - } - - public void setInput(ChannelBuffer compressed) { - byte[] in = new byte[compressed.readableBytes()]; - compressed.readBytes(in); - z.next_in = in; - z.next_in_index = 0; - z.avail_in = in.length; - } - - public void decode(ChannelBuffer decompressed) { - z.next_out_index = 0; - z.avail_out = out.length; - - int resultCode = z.inflate(JZlib.Z_SYNC_FLUSH); - - if (resultCode == JZlib.Z_NEED_DICT) { - resultCode = z.inflateSetDictionary(SPDY_DICT, SPDY_DICT.length); - if (resultCode != JZlib.Z_OK) { - throw new CompressionException("ZStream dictionary failure"); - } - z.inflate(JZlib.Z_SYNC_FLUSH); - } - - if (z.next_out_index > 0) { - decompressed.writeBytes(out, 0, z.next_out_index); - } - } -} From 0c46db317a0310923e5112577b759db9bdd844fb Mon Sep 17 00:00:00 2001 From: norman Date: Fri, 2 Mar 2012 09:28:43 +0100 Subject: [PATCH 56/66] Allow to handle only downstream events via the ExecutionHandler. See #173 --- .../handler/execution/ExecutionHandler.java | 39 ++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/handler/src/main/java/io/netty/handler/execution/ExecutionHandler.java b/handler/src/main/java/io/netty/handler/execution/ExecutionHandler.java index de2f435666..8ccc3278e9 100644 --- a/handler/src/main/java/io/netty/handler/execution/ExecutionHandler.java +++ b/handler/src/main/java/io/netty/handler/execution/ExecutionHandler.java @@ -110,25 +110,40 @@ public class ExecutionHandler implements ChannelUpstreamHandler, ChannelDownstre private final Executor executor; private final boolean handleDownstream; + private final boolean handleUpstream; + + /** + * Creates a new instance with the specified {@link Executor} which only handles upstream events. + * Specify an {@link OrderedMemoryAwareThreadPoolExecutor} if unsure. + */ + public ExecutionHandler(Executor executor) { + this(executor, false, true); + } + + /** + * Use {@link #ExecutionHandler(Executor, boolean, boolean)} + * + * {@link Deprecated} + */ + @Deprecated + public ExecutionHandler(Executor executor, boolean handleDownstream) { + this(executor, handleDownstream, true); + } /** * Creates a new instance with the specified {@link Executor}. * Specify an {@link OrderedMemoryAwareThreadPoolExecutor} if unsure. */ - public ExecutionHandler(Executor executor) { - this(executor, false); - } - - /** - * Creates a new instance with the specified {@link Executor}. - * Specify an {@link OrderedMemoryAwareThreadPoolExecutor} if unsure. - */ - public ExecutionHandler(Executor executor, boolean handleDownstream) { + public ExecutionHandler(Executor executor, boolean handleDownstream, boolean handleUpstream) { if (executor == null) { throw new NullPointerException("executor"); } + if (!handleDownstream && !handleUpstream) { + throw new IllegalArgumentException("You must handle at least handle one event type"); + } this.executor = executor; this.handleDownstream = handleDownstream; + this.handleUpstream = handleUpstream; } /** @@ -154,7 +169,11 @@ public class ExecutionHandler implements ChannelUpstreamHandler, ChannelDownstre @Override public void handleUpstream( ChannelHandlerContext context, ChannelEvent e) throws Exception { - executor.execute(new ChannelUpstreamEventRunnable(context, e)); + if (handleUpstream) { + executor.execute(new ChannelUpstreamEventRunnable(context, e)); + } else { + context.sendUpstream(e); + } } @Override From a071759575289fb85cdc2a07b116c2a3deea286b Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Fri, 2 Mar 2012 09:35:16 +0100 Subject: [PATCH 57/66] Fix formatting --- .../main/java/io/netty/handler/execution/ExecutionHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handler/src/main/java/io/netty/handler/execution/ExecutionHandler.java b/handler/src/main/java/io/netty/handler/execution/ExecutionHandler.java index 8ccc3278e9..391a59b797 100644 --- a/handler/src/main/java/io/netty/handler/execution/ExecutionHandler.java +++ b/handler/src/main/java/io/netty/handler/execution/ExecutionHandler.java @@ -110,7 +110,7 @@ public class ExecutionHandler implements ChannelUpstreamHandler, ChannelDownstre private final Executor executor; private final boolean handleDownstream; - private final boolean handleUpstream; + private final boolean handleUpstream; /** * Creates a new instance with the specified {@link Executor} which only handles upstream events. From 5d99d57cab6d90d2c91594d8755164261b8283b4 Mon Sep 17 00:00:00 2001 From: norman Date: Fri, 2 Mar 2012 13:28:54 +0100 Subject: [PATCH 58/66] Fix checkstyle problems --- .../io/netty/util/internal/jzlib/Adler32.java | 4 + .../io/netty/util/internal/jzlib/CRC32.java | 4 + .../io/netty/util/internal/jzlib/Deflate.java | 10 +- .../netty/util/internal/jzlib/InfBlocks.java | 108 ++++++++---------- .../netty/util/internal/jzlib/InfCodes.java | 2 +- .../io/netty/util/internal/jzlib/InfTree.java | 6 +- .../io/netty/util/internal/jzlib/Inflate.java | 4 +- .../io/netty/util/internal/jzlib/JZlib.java | 4 + .../util/internal/jzlib/package-info.java | 21 ++++ 9 files changed, 92 insertions(+), 71 deletions(-) create mode 100644 common/src/main/java/io/netty/util/internal/jzlib/package-info.java diff --git a/common/src/main/java/io/netty/util/internal/jzlib/Adler32.java b/common/src/main/java/io/netty/util/internal/jzlib/Adler32.java index 8e5f00c834..234d25404d 100644 --- a/common/src/main/java/io/netty/util/internal/jzlib/Adler32.java +++ b/common/src/main/java/io/netty/util/internal/jzlib/Adler32.java @@ -112,4 +112,8 @@ final class Adler32 { } return s2 << 16 | s1; } + + private Adler32() { + // Utility Class + } } diff --git a/common/src/main/java/io/netty/util/internal/jzlib/CRC32.java b/common/src/main/java/io/netty/util/internal/jzlib/CRC32.java index c6b17f0876..6af6a37545 100644 --- a/common/src/main/java/io/netty/util/internal/jzlib/CRC32.java +++ b/common/src/main/java/io/netty/util/internal/jzlib/CRC32.java @@ -92,4 +92,8 @@ final class CRC32 { crc32 ^= 0xffffffff; return crc32; } + + private CRC32() { + // Utility Class + } } diff --git a/common/src/main/java/io/netty/util/internal/jzlib/Deflate.java b/common/src/main/java/io/netty/util/internal/jzlib/Deflate.java index 4d45145b86..ca714f06d3 100644 --- a/common/src/main/java/io/netty/util/internal/jzlib/Deflate.java +++ b/common/src/main/java/io/netty/util/internal/jzlib/Deflate.java @@ -96,7 +96,7 @@ final class Deflate { "data error", // Z_DATA_ERROR (-3) "insufficient memory", // Z_MEM_ERROR (-4) "buffer error", // Z_BUF_ERROR (-5) - "incompatible version",// Z_VERSION_ERROR (-6) + "incompatible version", // Z_VERSION_ERROR (-6) "" }; // block not completed, need more input or more output @@ -342,7 +342,7 @@ final class Deflate { // Scan a literal or distance tree to determine the frequencies of the codes // in the bit length tree. - private void scan_tree(short[] tree,// the tree to be scanned + private void scan_tree(short[] tree, // the tree to be scanned int max_code // and its largest code of non zero frequency ) { int n; // iterates over all tree elements @@ -437,7 +437,7 @@ final class Deflate { // Send a literal or distance tree in compressed form, using the codes in // bl_tree. - private void send_tree(short[] tree,// the tree to be sent + private void send_tree(short[] tree, // the tree to be sent int max_code // and its largest code of non zero frequency ) { int n; // iterates over all tree elements @@ -514,7 +514,7 @@ final class Deflate { private void send_code(int c, short[] tree) { int c2 = c * 2; - send_bits((tree[c2] & 0xffff), (tree[c2 + 1] & 0xffff)); + send_bits(tree[c2] & 0xffff, tree[c2 + 1] & 0xffff); } private void send_bits(int value, int length) { @@ -804,7 +804,7 @@ final class Deflate { int stored_len, // length of input block boolean eof // true if this is the last block for a file ) { - int opt_lenb, static_lenb;// opt_len and static_len in bytes + int opt_lenb, static_lenb; // opt_len and static_len in bytes int max_blindex = 0; // index of last bit length code of non zero freq // Build the Huffman trees unless a stored block is forced diff --git a/common/src/main/java/io/netty/util/internal/jzlib/InfBlocks.java b/common/src/main/java/io/netty/util/internal/jzlib/InfBlocks.java index b387ffbb1c..e51d5ee454 100644 --- a/common/src/main/java/io/netty/util/internal/jzlib/InfBlocks.java +++ b/common/src/main/java/io/netty/util/internal/jzlib/InfBlocks.java @@ -61,7 +61,7 @@ final class InfBlocks { private static final int TYPE = 0; // get type bits (3, including end bit) private static final int LENS = 1; // get lengths for stored - private static final int STORED = 2;// processing stored block + private static final int STORED = 2; // processing stored block private static final int TABLE = 3; // get table lengths private static final int BTREE = 4; // get bit lengths tree for a dynamic block private static final int DTREE = 5; // get length, distance trees for a dynamic block @@ -124,16 +124,15 @@ final class InfBlocks { int m; // bytes to end of window or read pointer // copy input/output information to locals (UPDATE macro restores) - { - p = z.next_in_index; - n = z.avail_in; - b = bitb; - k = bitk; - } - { - q = write; - m = q < read? read - q - 1 : end - q; - } + + p = z.next_in_index; + n = z.avail_in; + b = bitb; + k = bitk; + + q = write; + m = q < read? read - q - 1 : end - q; + // process input based on current state while (true) { @@ -161,20 +160,17 @@ final class InfBlocks { switch (t >>> 1) { case 0: // stored - { + b >>>= 3; k -= 3; - } + t = k & 7; // go to byte boundary - { - b >>>= t; - k -= t; - } + b >>>= t; + k -= t; mode = LENS; // get length of stored block break; case 1: // fixed - { int[] bl = new int[1]; int[] bd = new int[1]; int[][] tl = new int[1][]; @@ -182,30 +178,24 @@ final class InfBlocks { InfTree.inflate_trees_fixed(bl, bd, tl, td); codes.init(bl[0], bd[0], tl[0], 0, td[0], 0); - } - { - b >>>= 3; - k -= 3; - } + b >>>= 3; + k -= 3; mode = CODES; break; case 2: // dynamic - { b >>>= 3; k -= 3; - } mode = TABLE; break; case 3: // illegal - { b >>>= 3; k -= 3; - } + mode = BAD; z.msg = "invalid block type"; r = JZlib.Z_DATA_ERROR; @@ -352,10 +342,9 @@ final class InfBlocks { } } - { - b >>>= 14; - k -= 14; - } + + b >>>= 14; + k -= 14; index = 0; mode = BTREE; @@ -380,10 +369,9 @@ final class InfBlocks { blens[border[index ++]] = b & 7; - { - b >>>= 3; - k -= 3; - } + b >>>= 3; + k -= 3; + } while (index < 19) { @@ -505,36 +493,36 @@ final class InfBlocks { } tb[0] = -1; - { - int[] bl = new int[1]; - int[] bd = new int[1]; - int[] tl = new int[1]; - int[] td = new int[1]; - bl[0] = 9; // must be <= 9 for lookahead assumptions - bd[0] = 6; // must be <= 9 for lookahead assumptions - t = table; - t = inftree.inflate_trees_dynamic(257 + (t & 0x1f), - 1 + (t >> 5 & 0x1f), blens, bl, bd, tl, td, hufts, - z); + int[] bl = new int[1]; + int[] bd = new int[1]; + int[] tl = new int[1]; + int[] td = new int[1]; + bl[0] = 9; // must be <= 9 for lookahead assumptions + bd[0] = 6; // must be <= 9 for lookahead assumptions - if (t != JZlib.Z_OK) { - if (t == JZlib.Z_DATA_ERROR) { - blens = null; - mode = BAD; - } - r = t; + t = table; + t = inftree.inflate_trees_dynamic(257 + (t & 0x1f), + 1 + (t >> 5 & 0x1f), blens, bl, bd, tl, td, hufts, + z); - bitb = b; - bitk = k; - z.avail_in = n; - z.total_in += p - z.next_in_index; - z.next_in_index = p; - write = q; - return inflate_flush(z, r); + if (t != JZlib.Z_OK) { + if (t == JZlib.Z_DATA_ERROR) { + blens = null; + mode = BAD; } - codes.init(bl[0], bd[0], hufts, tl[0], hufts, td[0]); + r = t; + + bitb = b; + bitk = k; + z.avail_in = n; + z.total_in += p - z.next_in_index; + z.next_in_index = p; + write = q; + return inflate_flush(z, r); } + codes.init(bl[0], bd[0], hufts, tl[0], hufts, td[0]); + mode = CODES; case CODES: bitb = b; diff --git a/common/src/main/java/io/netty/util/internal/jzlib/InfCodes.java b/common/src/main/java/io/netty/util/internal/jzlib/InfCodes.java index ef979b57a3..6e6ea3ab2a 100644 --- a/common/src/main/java/io/netty/util/internal/jzlib/InfCodes.java +++ b/common/src/main/java/io/netty/util/internal/jzlib/InfCodes.java @@ -71,7 +71,7 @@ final class InfCodes { // mode dependent information private int len; private int[] tree; // pointer into tree - private int tree_index = 0; + private int tree_index; private int need; // bits needed private int lit; // if EXT or COPY, where and how much diff --git a/common/src/main/java/io/netty/util/internal/jzlib/InfTree.java b/common/src/main/java/io/netty/util/internal/jzlib/InfTree.java index 313c1789f6..67012e3dea 100644 --- a/common/src/main/java/io/netty/util/internal/jzlib/InfTree.java +++ b/common/src/main/java/io/netty/util/internal/jzlib/InfTree.java @@ -180,8 +180,8 @@ final class InfTree { int[] e, // list of extra bits for non-simple codes int[] t, // result: starting table int[] m, // maximum lookup bits, returns actual - int[] hp,// space for trees - int[] hn,// hufts used in space + int[] hp, // space for trees + int[] hn, // hufts used in space int[] v // working area: values in order of bit length ) { // Given a list of code lengths and a maximum table size, make a set of @@ -437,7 +437,7 @@ final class InfTree { static int inflate_trees_fixed(int[] bl, //literal desired/actual bit depth int[] bd, //distance desired/actual bit depth - int[][] tl,//literal/length tree result + int[][] tl, //literal/length tree result int[][] td //distance tree result ) { bl[0] = fixed_bl; diff --git a/common/src/main/java/io/netty/util/internal/jzlib/Inflate.java b/common/src/main/java/io/netty/util/internal/jzlib/Inflate.java index c496533203..c18dc1592f 100644 --- a/common/src/main/java/io/netty/util/internal/jzlib/Inflate.java +++ b/common/src/main/java/io/netty/util/internal/jzlib/Inflate.java @@ -76,7 +76,7 @@ final class Inflate { private static final int GZIP_FNAME = 21; private static final int GZIP_FCOMMENT = 22; private static final int GZIP_FHCRC = 23; - private static final int GZIP_CRC32= 24; + private static final int GZIP_CRC32 = 24; private static final int GZIP_ISIZE = 25; private int mode; // current inflate mode @@ -305,7 +305,7 @@ final class Inflate { break; } else if (z.istate.wrapperType == WrapperType.ZLIB) { z.istate.mode = CHECK4; - } else if (z.istate.wrapperType == WrapperType.GZIP){ + } else if (z.istate.wrapperType == WrapperType.GZIP) { gzipCRC32 = 0; gzipISize = 0; gzipBytesToRead = 4; diff --git a/common/src/main/java/io/netty/util/internal/jzlib/JZlib.java b/common/src/main/java/io/netty/util/internal/jzlib/JZlib.java index b97f96a878..5d74519f5b 100644 --- a/common/src/main/java/io/netty/util/internal/jzlib/JZlib.java +++ b/common/src/main/java/io/netty/util/internal/jzlib/JZlib.java @@ -105,4 +105,8 @@ public final class JZlib { enum WrapperType { NONE, ZLIB, GZIP, ZLIB_OR_NONE } + + private JZlib() { + // Utility class + } } diff --git a/common/src/main/java/io/netty/util/internal/jzlib/package-info.java b/common/src/main/java/io/netty/util/internal/jzlib/package-info.java new file mode 100644 index 0000000000..758bc4e1fe --- /dev/null +++ b/common/src/main/java/io/netty/util/internal/jzlib/package-info.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +/** + * Internal-use-only utilities which is not allowed to be used + * outside Netty. + */ +package io.netty.util.internal.jzlib; From 53112574908c6e244c9c1030d5d69408907e863e Mon Sep 17 00:00:00 2001 From: Trustin Lee Date: Fri, 2 Mar 2012 10:35:23 -0800 Subject: [PATCH 59/66] Upgrade to the latest netty-build version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e8b52f861f..f69676e699 100644 --- a/pom.xml +++ b/pom.xml @@ -286,7 +286,7 @@ ${project.groupId} netty-build - 6 + 7 From efe27dbce548f4def09e9829bfc57e99676c50d2 Mon Sep 17 00:00:00 2001 From: Trustin Lee Date: Fri, 2 Mar 2012 10:43:04 -0800 Subject: [PATCH 60/66] Rename SPDY header block (de)compressor classes --- .../handler/codec/spdy/SpdyHeaderBlockCompressor.java | 9 ++++++--- .../handler/codec/spdy/SpdyHeaderBlockDecompressor.java | 2 +- ...bEncoder.java => SpdyHeaderBlockJZlibCompressor.java} | 4 ++-- ...ecoder.java => SpdyHeaderBlockJZlibDecompressor.java} | 4 ++-- ...ibEncoder.java => SpdyHeaderBlockZlibCompressor.java} | 4 ++-- 5 files changed, 13 insertions(+), 10 deletions(-) rename codec-http/src/main/java/io/netty/handler/codec/spdy/{SpdyJZlibEncoder.java => SpdyHeaderBlockJZlibCompressor.java} (95%) rename codec-http/src/main/java/io/netty/handler/codec/spdy/{SpdyJZlibDecoder.java => SpdyHeaderBlockJZlibDecompressor.java} (94%) rename codec-http/src/main/java/io/netty/handler/codec/spdy/{SpdyZlibEncoder.java => SpdyHeaderBlockZlibCompressor.java} (92%) diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockCompressor.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockCompressor.java index 69b823d405..d52e7e17d9 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockCompressor.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockCompressor.java @@ -37,11 +37,14 @@ abstract class SpdyHeaderBlockCompressor { USE_ZLIB = java7; } - static SpdyHeaderBlockCompressor newInstance(int compressionLevel, int windowBits, int memLevel) { + static SpdyHeaderBlockCompressor newInstance( + int compressionLevel, int windowBits, int memLevel) { + if (USE_ZLIB) { - return new SpdyZlibEncoder(compressionLevel); + return new SpdyHeaderBlockZlibCompressor(compressionLevel); } else { - return new SpdyJZlibEncoder(compressionLevel, windowBits, memLevel); + return new SpdyHeaderBlockJZlibCompressor( + compressionLevel, windowBits, memLevel); } } diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockDecompressor.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockDecompressor.java index 76dc1acdf1..119ef46454 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockDecompressor.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockDecompressor.java @@ -20,7 +20,7 @@ import io.netty.buffer.ChannelBuffer; abstract class SpdyHeaderBlockDecompressor { static SpdyHeaderBlockDecompressor newInstance() { - return new SpdyJZlibDecoder(); + return new SpdyHeaderBlockJZlibDecompressor(); } abstract void setInput(ChannelBuffer compressed); diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyJZlibEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockJZlibCompressor.java similarity index 95% rename from codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyJZlibEncoder.java rename to codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockJZlibCompressor.java index c76d08b13b..dc6e7db1ff 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyJZlibEncoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockJZlibCompressor.java @@ -22,11 +22,11 @@ import io.netty.handler.codec.compression.CompressionException; import io.netty.util.internal.jzlib.JZlib; import io.netty.util.internal.jzlib.ZStream; -class SpdyJZlibEncoder extends SpdyHeaderBlockCompressor { +class SpdyHeaderBlockJZlibCompressor extends SpdyHeaderBlockCompressor { private final ZStream z = new ZStream(); - public SpdyJZlibEncoder(int compressionLevel, int windowBits, int memLevel) { + public SpdyHeaderBlockJZlibCompressor(int compressionLevel, int windowBits, int memLevel) { if (compressionLevel < 0 || compressionLevel > 9) { throw new IllegalArgumentException( "compressionLevel: " + compressionLevel + " (expected: 0-9)"); diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyJZlibDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockJZlibDecompressor.java similarity index 94% rename from codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyJZlibDecoder.java rename to codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockJZlibDecompressor.java index 30063d5285..21593aba40 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyJZlibDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockJZlibDecompressor.java @@ -22,12 +22,12 @@ import io.netty.util.internal.jzlib.ZStream; import static io.netty.handler.codec.spdy.SpdyCodecUtil.*; -class SpdyJZlibDecoder extends SpdyHeaderBlockDecompressor { +class SpdyHeaderBlockJZlibDecompressor extends SpdyHeaderBlockDecompressor { private final byte[] out = new byte[8192]; private final ZStream z = new ZStream(); - public SpdyJZlibDecoder() { + public SpdyHeaderBlockJZlibDecompressor() { int resultCode; resultCode = z.inflateInit(JZlib.W_ZLIB); if (resultCode != JZlib.Z_OK) { diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyZlibEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockZlibCompressor.java similarity index 92% rename from codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyZlibEncoder.java rename to codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockZlibCompressor.java index a57b9b7bf0..d00434b120 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyZlibEncoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockZlibCompressor.java @@ -21,12 +21,12 @@ import io.netty.buffer.ChannelBuffer; import static io.netty.handler.codec.spdy.SpdyCodecUtil.*; -class SpdyZlibEncoder extends SpdyHeaderBlockCompressor { +class SpdyHeaderBlockZlibCompressor extends SpdyHeaderBlockCompressor { private final byte[] out = new byte[8192]; private final Deflater compressor; - public SpdyZlibEncoder(int compressionLevel) { + public SpdyHeaderBlockZlibCompressor(int compressionLevel) { if (compressionLevel < 0 || compressionLevel > 9) { throw new IllegalArgumentException( "compressionLevel: " + compressionLevel + " (expected: 0-9)"); From b3606eb57a62317ef5147bb89e45e2c83d29d72a Mon Sep 17 00:00:00 2001 From: Trustin Lee Date: Fri, 2 Mar 2012 12:04:23 -0800 Subject: [PATCH 61/66] Remove unused library at the moment --- lib/apiviz-1.3.1.GA.jar | Bin 137643 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 lib/apiviz-1.3.1.GA.jar diff --git a/lib/apiviz-1.3.1.GA.jar b/lib/apiviz-1.3.1.GA.jar deleted file mode 100644 index 1e1edb2c2d8f572d9b167fbe86d14f5069698b73..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 137643 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�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�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